[FFmpeg-devel] [PATCH] libavfilter: added atempo filter (revised patch)

Stefano Sabatini stefasab at gmail.com
Thu Jun 7 11:00:32 CEST 2012


Nit: commit title may be:
libavfilter: add atempo filter

or also:
lavfi: add atempo filter

in other words we use the present tense rather than past tense (that
is we're used to say what the patch does, not what it "did").

Metanote: you're free to ignore all the nit remarks, some of the them
may be eventually fixed by the committer.

On date Wednesday 2012-06-06 10:17:28 -0600, pkoshevoy at gmail.com encoded:
> From: Pavel Koshevoy <pkoshevoy at gmail.com>
> 
> Added atempo audio filter for adjusting audio tempo without affecting
> pitch. This filter implements WSOLA algorithm with fast cross
> correlation calculation in frequency domain.
> 
> Signed-off-by: Pavel Koshevoy <pavel at homestead.aragog.com>
> ---
>  Changelog                |    2 +-
>  configure                |    1 +
>  doc/filters.texi         |   17 +
>  libavfilter/Makefile     |    2 +
>  libavfilter/af_atempo.c  | 1245 ++++++++++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c |    1 +
>  libavfilter/version.h    |    2 +-
>  7 files changed, 1268 insertions(+), 2 deletions(-)
>  create mode 100644 libavfilter/af_atempo.c
> 
> diff --git a/Changelog b/Changelog
> index 41b0bdc..cc25c9b 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -5,7 +5,7 @@ version next:
>  - INI and flat output in ffprobe
>  - Scene detection in libavfilter
>  - Indeo Audio decoder
> -
> +- atempo filter
>  
>  version 0.11:
>  
> diff --git a/configure b/configure
> index 33bd439..7b82b64 100755
> --- a/configure
> +++ b/configure
> @@ -1687,6 +1687,7 @@ amovie_filter_deps="avcodec avformat"
>  aresample_filter_deps="swresample"
>  ass_filter_deps="libass"
>  asyncts_filter_deps="avresample"
> +atempo_filter_deps="avcodec"
>  blackframe_filter_deps="gpl"
>  boxblur_filter_deps="gpl"
>  colormatrix_filter_deps="gpl"
> diff --git a/doc/filters.texi b/doc/filters.texi
> index d9d503f..0b7dc8e 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -271,6 +271,23 @@ For example, to resample the input audio to 44100Hz:
>  aresample=44100
>  @end example
>  
> + at section atempo
> +
> +Adjust audio tempo.
> +
> +The filter accepts exactly one parameter, the audio tempo. If not
> +specified then the filter will assume nominal tempo.
> +
> +For example, to slow down audio to 80% tempo:
> + at example
> +atempo=0.8
> + at end example
> +
> +For example, to speed up audio to 125% tempo:
> + at example
> +atempo=1.25
> + at end example
> +
>  @section ashowinfo
>  
>  Show a line containing various information for each input audio frame.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 29345fc..a1ced51 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -8,6 +8,7 @@ FFLIBS-$(CONFIG_RESAMPLE_FILTER) += avresample
>  FFLIBS-$(CONFIG_ACONVERT_FILTER)             += swresample
>  FFLIBS-$(CONFIG_AMOVIE_FILTER)               += avformat avcodec
>  FFLIBS-$(CONFIG_ARESAMPLE_FILTER)            += swresample
> +FFLIBS-$(CONFIG_ATEMPO_FILTER)               += avcodec
>  FFLIBS-$(CONFIG_MOVIE_FILTER)                += avformat avcodec
>  FFLIBS-$(CONFIG_PAN_FILTER)                  += swresample
>  FFLIBS-$(CONFIG_REMOVELOGO_FILTER)           += avformat avcodec
> @@ -54,6 +55,7 @@ OBJS-$(CONFIG_ASHOWINFO_FILTER)              += af_ashowinfo.o
>  OBJS-$(CONFIG_ASPLIT_FILTER)                 += split.o
>  OBJS-$(CONFIG_ASTREAMSYNC_FILTER)            += af_astreamsync.o
>  OBJS-$(CONFIG_ASYNCTS_FILTER)                += af_asyncts.o
> +OBJS-$(CONFIG_ATEMPO_FILTER)                 += af_atempo.o
>  OBJS-$(CONFIG_EARWAX_FILTER)                 += af_earwax.o
>  OBJS-$(CONFIG_PAN_FILTER)                    += af_pan.o
>  OBJS-$(CONFIG_RESAMPLE_FILTER)               += af_resample.o
> diff --git a/libavfilter/af_atempo.c b/libavfilter/af_atempo.c
> new file mode 100644
> index 0000000..1866d0a
> --- /dev/null
> +++ b/libavfilter/af_atempo.c
> @@ -0,0 +1,1245 @@
> +/*
> + * Copyright (c) 2012 Pavel Koshevoy
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +

> +/**
> + * @file
> + * tempo scaling audio filter -- an implementation of WSOLA algorithm
> + */

please mention the original source, if the filter was based on another
implementation

> +
> +#include <float.h>
> +#include "libavcodec/avfft.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/eval.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +#include "avfilter.h"
> +#include "audio.h"
> +#include "internal.h"
> +
> +/**
> + * A fragment of audio waveform
> + */
> +typedef struct {
> +    // index of the first sample of this fragment in the overall waveform;
> +    // 0: input sample position
> +    // 1: output sample position
> +    int64_t position[2];
> +
> +    // original packed multi-channel samples:
> +    unsigned char *data;
> +
> +    // number of samples in this fragment:
> +    int nsamples;
> +
> +    // FFT transform of the downmixed mono fragment, used for
> +    // fast waveform alignment via correlation in frequency domain:
> +    FFTComplex *xdat;
> +
> +} TAudioFragment;
> +
> +/**
> + * Filter state machine states
> + */

> +typedef enum {
> +    kLoadFragment     = 0,
> +    kAdjustPosition   = 1,
> +    kReloadFragment   = 2,
> +    kOutputOverlapAdd = 3,
> +    kFlushOutput      = 4

what's the "k" good for?

> +
> +} TState;

the "T" in TAudioFragment and TState is weird.

> +
> +/**
> + * Filter state machine
> + */
> +typedef struct {
> +    // ring-buffer of input samples, necessary because some times
> +    // input fragment position may be adjusted backwards:
> +    unsigned char *buffer;
> +
> +    // ring-buffer maximum capacity,
> +    // expressed as number of multi-channel sample units;
> +    //
> +    // for example, given stereo data 1 multi-channel sample unit
> +    // refers to 2 samples for left/right channels:
> +    int ring;
> +
> +    // ring-buffer house keeping:
> +    int size;
> +    int head;
> +    int tail;
> +
> +    // 0: input sample position corresponding to the ring buffer tail
> +    // 1: output sample position
> +    int64_t position[2];
> +
> +    // sample format:
> +    enum AVSampleFormat format;
> +
> +    // number of channels:
> +    int channels;
> +
> +    // row of bytes to skip from one sample to next, across multple channels;
> +    // stride = (number-of-channels * bits-per-sample-per-channel) / 8
> +    int stride;
> +
> +    // fragment window size, power-of-two integer:
> +    int window;
> +
> +    // Hann window coefficients, for feathering
> +    // (blending) the overlapping fragment region:
> +    float *hann;
> +
> +    // tempo scaling factor:
> +    double tempo;
> +
> +    // cumulative alignment drift:
> +    int drift;
> +
> +    // current/previous fragment ring-buffer:
> +    TAudioFragment frag[2];
> +
> +    // current fragment index:
> +    uint64_t nfrag;
> +
> +    // current state:
> +    TState state;
> +
> +    // for fast correlation calculation in frequency domain:
> +    FFTContext *fft_forward;
> +    FFTContext *fft_inverse;
> +    FFTComplex *correlation;
> +
> +    // for managing AVFilterPad::request_frame and AVFilterPad::filter_samples
> +    int request_fulfilled;
> +    AVFilterBufferRef *dst_buffer;
> +    unsigned char *dst;
> +    unsigned char *dst_end;
> +    uint64_t nsamples_in;
> +    uint64_t nsamples_out;
> +
> +} ATempoContext;
> +

> +/**
> + * Initialize filter state.
> + */
> +static void yae_constructor(ATempoContext *atempo)

what "yae" stands for?

Nit: the name "yae_constructor" sounds weird, the predominant
convention is to use a verbal form rather than a noun for naming a
function (like: yae_build or yae_init).

> +{
> +    atempo->ring = 0;
> +    atempo->size = 0;
> +    atempo->head = 0;
> +    atempo->tail = 0;
> +
> +    atempo->format = AV_SAMPLE_FMT_NONE;
> +    atempo->channels = 0;
> +
> +    atempo->window = 0;
> +    atempo->tempo = 1.0;
> +    atempo->drift = 0;
> +
> +    memset(&atempo->frag[0], 0, sizeof(atempo->frag));
> +
> +    atempo->nfrag = 0;
> +    atempo->state = kLoadFragment;
> +
> +    atempo->position[0] = 0;
> +    atempo->position[1] = 0;
> +
> +    atempo->fft_forward = NULL;
> +    atempo->fft_inverse = NULL;
> +    atempo->correlation = NULL;
> +
> +    atempo->request_fulfilled = 0;
> +    atempo->dst_buffer = NULL;
> +    atempo->dst = NULL;
> +    atempo->dst_end = NULL;
> +    atempo->nsamples_in = 0;
> +    atempo->nsamples_out = 0;

I think a memset to 0, followed by the explicit initialization of the
few non-zero fields will be simpler.

> +}
> +
> +/**
> + * Deallocate filter buffers.
> + */
> +static void yae_destructor(ATempoContext *atempo)
> +{
> +    av_freep(&atempo->frag[0].data);
> +    av_freep(&atempo->frag[1].data);
> +    av_freep(&atempo->frag[0].xdat);
> +    av_freep(&atempo->frag[1].xdat);
> +
> +    av_freep(&atempo->buffer);
> +    av_freep(&atempo->hann);
> +    av_freep(&atempo->correlation);
> +
> +    if (atempo->fft_forward) {
> +        av_fft_end(atempo->fft_forward);
> +        atempo->fft_forward = NULL;
> +    }
> +
> +    if (atempo->fft_inverse) {
> +        av_fft_end(atempo->fft_inverse);
> +        atempo->fft_inverse = NULL;
> +    }
> +
> +}
> +
> +/**
> + * Reset given fragment to initial state
> + */
> +static void yae_clear_frag(TAudioFragment *frag)
> +{
> +    frag->position[0] = 0;
> +    frag->position[1] = 0;
> +    frag->nsamples    = 0;
> +}
> +
> +/**
> + * Reset filter to initial state
> + */
> +static void yae_clear(ATempoContext *atempo)
> +{
> +    atempo->size = 0;
> +    atempo->head = 0;
> +    atempo->tail = 0;
> +
> +    atempo->drift = 0;
> +    atempo->nfrag = 0;
> +    atempo->state = kLoadFragment;
> +
> +    atempo->position[0] = 0;
> +    atempo->position[1] = 0;
> +
> +    yae_clear_frag(&atempo->frag[0]);
> +    yae_clear_frag(&atempo->frag[1]);
> +
> +    // shift left position of 1st fragment by half a window
> +    // so that no re-normalization would be required for
> +    // the left half of the 1st fragment:
> +    atempo->frag[0].position[0] = -(int64_t)(atempo->window / 2);
> +    atempo->frag[0].position[1] = -(int64_t)(atempo->window / 2);
> +
> +    if (atempo->dst_buffer) {
> +        avfilter_unref_buffer(atempo->dst_buffer);
> +        atempo->dst_buffer = NULL;
> +        atempo->dst        = NULL;
> +        atempo->dst_end    = NULL;
> +    }
> +
> +    atempo->request_fulfilled = 0;
> +    atempo->nsamples_in       = 0;
> +    atempo->nsamples_out      = 0;
> +}

again this can be simplified / factorized with yae_constructor

> +
> +/**
> + * Prepare filter for processing audio data of given format,
> + * sample rate and number of channels.
> + */
> +static void yae_reset(ATempoContext *atempo,
> +                      enum AVSampleFormat format,
> +                      int sample_rate,
> +                      int channels)
> +{
> +    const int sample_size = av_get_bytes_per_sample(format);
> +    unsigned int nlevels  = 0;
> +    unsigned int pot;
> +
> +    atempo->format   = format;
> +    atempo->channels = channels;
> +    atempo->stride   = sample_size * channels;
> +
> +    // pick a segment window size:
> +    atempo->window = sample_rate / 24;
> +
> +    // adjust window size to be a power-of-two integer:
> +    nlevels = av_log2_c(atempo->window);
> +    pot = 1 << nlevels;
> +    av_assert0(pot <= atempo->window);
> +
> +    if (pot < atempo->window) {
> +        atempo->window = pot * 2;
> +        nlevels++;
> +    }
> +
> +    atempo->frag[0].data = av_realloc(atempo->frag[0].data,
> +                                      atempo->window * atempo->stride);
> +
> +    atempo->frag[1].data = av_realloc(atempo->frag[1].data,
> +                                      atempo->window * atempo->stride);
> +
> +    atempo->frag[0].xdat = av_realloc(atempo->frag[0].xdat,
> +                                      atempo->window * 2 *
> +                                      sizeof(FFTComplex));
> +
> +    atempo->frag[1].xdat = av_realloc(atempo->frag[1].xdat,
> +                                      atempo->window * 2 *
> +                                      sizeof(FFTComplex));
> +
> +    // initialize FFT contexts:
> +    if (atempo->fft_forward) {
> +        av_fft_end(atempo->fft_forward);
> +    }
> +
> +    if (atempo->fft_inverse) {
> +        av_fft_end(atempo->fft_inverse);
> +    }
> +
> +    atempo->fft_forward = av_fft_init(nlevels + 1, 0);
> +    atempo->fft_inverse = av_fft_init(nlevels + 1, 1);
> +    atempo->correlation = (FFTComplex *)av_realloc(atempo->correlation,
> +                                                   atempo->window * 2 *
> +                                                   sizeof(FFTComplex));
> +
> +    atempo->ring = atempo->window * 3;
> +    atempo->buffer = av_realloc(atempo->buffer, atempo->ring * atempo->stride);
> +
> +    // sample the Hann window function:
> +    atempo->hann = av_realloc(atempo->hann, atempo->window * sizeof(float));
> +    for (int i = 0; i < atempo->window; i++) {
> +        double t = (double)i / (double)(atempo->window - 1);
> +        double h = 0.5 * (1.0 - cos(2.0 * M_PI * t));
> +        atempo->hann[i] = (float)h;
> +    }
> +
> +    yae_clear(atempo);
> +}
> +
> +static int yae_set_tempo(ATempoContext *atempo,
> +                         double tempo,
> +                         AVFilterContext *ctx)
> +{
> +    if (tempo < 0.5 || tempo > 2.0) {
> +        av_log(ctx, AV_LOG_ERROR, "tempo value %f exceeds [0.5, 2.0] range\n",
> +               tempo);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    atempo->tempo = tempo;
> +    return 0;
> +}
> +

> +inline static TAudioFragment * yae_curr_frag(ATempoContext *atempo)

nit: *yae_curr_frag, const ATempoContext *atempo

> +{
> +    return &atempo->frag[atempo->nfrag % 2];
> +}
> +

> +inline static TAudioFragment * yae_prev_frag(ATempoContext *atempo)

ditto

> +{
> +    return &atempo->frag[(atempo->nfrag + 1) % 2];
> +}
> +
> +/**
> + * Find the minimum of two scalars
> + */
> +#define yae_min(TScalar, a, b)                          \

Uh? where was TScalar defined?
Also can't you use FFMAX/FFMIN for such purposes?

> +    ((TScalar)a < (TScalar)b ?                          \
> +     (TScalar)a :                                       \
> +     (TScalar)b)
> +
> +/**
> + * Find the maximum of two scalars
> + */
> +#define yae_max(TScalar, a, b)                          \
> +    ((TScalar)a < (TScalar)b ?                          \
> +     (TScalar)b :                                       \
> +     (TScalar)a)
> +
> +

> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> +    ATempoContext *atempo = ctx->priv;
> +    yae_constructor(atempo);
> +
> +    if (args) {
> +        char   *tail = NULL;
> +        double tempo = av_strtod(args, &tail);

please check that tail is NULL & fail otherwise like "Invalid scale
factor '%s' provided, good for catching typos.

> +        return yae_set_tempo(atempo, tempo, ctx);
> +    }
> +
> +    return 0;
> +}
> +

> +/**
> + * Deallocate any memory held by the filter,
> + * release any buffer references, etc.
> + *
> + * This does not need to deallocate the AVFilterContext::priv memory itself.
> + */

Nit: generic comments are good in template code (and maybe we need
it), but they are usually a bit annoying to deal with in working code
(e.g. when you update API).

> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    ATempoContext *atempo = ctx->priv;
> +    yae_destructor(atempo);
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    AVFilterChannelLayouts *layouts = NULL;
> +    AVFilterFormats        *formats = NULL;
> +
> +    enum AVSampleFormat sample_fmts[] = {
> +        AV_SAMPLE_FMT_U8,
> +        AV_SAMPLE_FMT_S16,
> +        AV_SAMPLE_FMT_S32,
> +        AV_SAMPLE_FMT_FLT,
> +        AV_SAMPLE_FMT_DBL,
> +        AV_SAMPLE_FMT_NONE
> +    };
> +
> +    layouts = ff_all_channel_layouts();
> +    if (!layouts) {
> +        return AVERROR(ENOMEM);
> +    }
> +    ff_set_common_channel_layouts(ctx, layouts);
> +
> +    formats = avfilter_make_format_list(sample_fmts);
> +    if (!formats) {
> +        return AVERROR(ENOMEM);
> +    }
> +    avfilter_set_common_sample_formats(ctx, formats);
> +
> +    formats = ff_all_samplerates();
> +    if (!formats) {
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    ff_set_common_samplerates(ctx, formats);
> +    return 0;
> +}
> +
> +/**
> + * Check configuration properties of the input link
> + * and update filters internal state as necessary.
> + *
> + * Return zero on success, AVERRROR(..) on failure.
> + */
> +static int
> +config_props(AVFilterLink *inlink)
> +{
> +    AVFilterContext  *ctx = inlink->dst;
> +    ATempoContext *atempo = ctx->priv;
> +

> +    enum AVSampleFormat format = (enum AVSampleFormat)inlink->format;

unnecessary cast?

> +    int sample_rate = (int)inlink->sample_rate;
> +    int channels = av_get_channel_layout_nb_channels(inlink->channel_layout);
> +
> +    yae_reset(atempo, format, sample_rate, channels);
> +    return 0;
> +}
> +

> +/**
> + * Samples filtering callback that.
> + * This is where a filter receives audio data and processes it.
> + */

same comment as above

[...]
> diff --git a/libavfilter/version.h b/libavfilter/version.h
> index 76f649e..c90b4ad 100644
> --- a/libavfilter/version.h
> +++ b/libavfilter/version.h
> @@ -30,7 +30,7 @@
>  
>  #define LIBAVFILTER_VERSION_MAJOR  2
>  #define LIBAVFILTER_VERSION_MINOR 78
> -#define LIBAVFILTER_VERSION_MICRO 100
> +#define LIBAVFILTER_VERSION_MICRO 101

Minor bump, but leave this to the committer so you won't have to
update again and again your patch.

Thanks for the cool patch.
-- 
FFmpeg = Fundamentalist Friendly Mythic Pure Evil Generator


More information about the ffmpeg-devel mailing list