[FFmpeg-devel] [PATCH] libavfilter: add atempo filter (revised patch v3)
Stefano Sabatini
stefasab at gmail.com
Sun Jun 10 21:17:42 CEST 2012
On date Friday 2012-06-08 09:16:36 -0600, Pavel Koshevoy encoded:
> Add 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 | 1 +
> configure | 1 +
> doc/filters.texi | 18 +
> libavfilter/Makefile | 2 +
> libavfilter/af_atempo.c | 1218 ++++++++++++++++++++++++++++++++++++++++++++++
> libavfilter/allfilters.c | 1 +
> 6 files changed, 1241 insertions(+), 0 deletions(-)
> create mode 100644 libavfilter/af_atempo.c
>
> diff --git a/Changelog b/Changelog
> index 41b0bdc..a639c71 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -5,6 +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 7a60f29..2eac85f 100755
> --- a/configure
> +++ b/configure
> @@ -1690,6 +1690,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 150bde3..c10d133 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -273,6 +273,24 @@ 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 1.0 tempo. Tempo must
> +be in the [0.5, 2.0] range.
> +
> +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..4c59b34
> --- /dev/null
> +++ b/libavfilter/af_atempo.c
> @@ -0,0 +1,1218 @@
> +/*
> + * Copyright (c) 2012 Pavel Koshevoy <pkoshevoy at gmail.com>
> + *
> + * 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
> + *
> + * Based on MIT licensed yaeAudioTempoFilter.h and yaeAudioFragment.h
> + * from Apprentice Video player by Pavel Koshevoy.
> + * https://sourceforge.net/projects/apprenticevideo/
> + *
> + * An explanation of SOLA algorithm is available at
> + * http://www.surina.net/article/time-and-pitch-scaling.html
> + *
> + * WSOLA is very similar to SOLA, only one major difference exists between
> + * these algorithms. SOLA shifts audio fragments along the output stream,
> + * where as WSOLA shifts audio fragments along the input stream.
> + *
> + * The advantage of WSOLA algorithm is that the overlap region size is
> + * always the same, therefore the blending function is constant and
> + * can be precomputed.
> + */
> +
> +#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:
> + uint8_t *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;
> +
> +} AudioFragment;
> +
> +/**
> + * Filter state machine states
> + */
> +typedef enum {
> + YAE_LOAD_FRAGMENT = 0,
> + YAE_ADJUST_POSITION = 1,
> + YAE_RELOAD_FRAGMENT = 2,
> + YAE_OUTPUT_OVERLAP_ADD = 3,
> + YAE_FLUSH_OUTPUT = 4,
> +
> +} FilterState;
Nit: no need to explicitely enumerate, C compiler will deal with it
starting at 0. YAE_ prefixes still look weird but I won't insist if
you want to keep them.
> +/**
> + * Filter state machine
> + */
> +typedef struct {
> + // ring-buffer of input samples, necessary because some times
> + // input fragment position may be adjusted backwards:
> + uint8_t *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;
Also note that we have several buffer implementations, in particular
libavutil/fifo.h and audio_fifo.h, don't know if they could be used in
this particular case.
> + // 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:
> + AudioFragment frag[2];
> +
> + // current fragment index:
> + uint64_t nfrag;
> +
> + // current state:
> + FilterState 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;
> + uint8_t *dst;
> + uint8_t *dst_end;
> + uint64_t nsamples_in;
> + uint64_t nsamples_out;
> +
> +} ATempoContext;
> +
> +/**
> + * This resets filter to initial state, does not deallocate existing buffers.
> + */
Nit++: Reset filter to initial state, do not deallocate ...
> +static void yae_clear(ATempoContext *atempo)
> +{
> + atempo->size = 0;
> + atempo->head = 0;
> + atempo->tail = 0;
> +
> + atempo->drift = 0;
> + atempo->nfrag = 0;
> + atempo->state = YAE_LOAD_FRAGMENT;
> +
> + atempo->position[0] = 0;
> + atempo->position[1] = 0;
> +
> + atempo->frag[0].position[0] = 0;
> + atempo->frag[0].position[1] = 0;
> + atempo->frag[0].nsamples = 0;
> +
> + atempo->frag[1].position[0] = 0;
> + atempo->frag[1].position[1] = 0;
> + atempo->frag[1].nsamples = 0;
> +
>
> + // 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);
> +
> + avfilter_unref_bufferp(&atempo->dst_buffer);
>
> + atempo->dst = NULL;
> + atempo->dst_end = NULL;
> +
> + atempo->request_fulfilled = 0;
> + atempo->nsamples_in = 0;
> + atempo->nsamples_out = 0;
> +}
> +
> +/**
> + * Prepare filter for processing audio data of given format,
> + * sample rate and number of channels.
> + */
> +static int yae_reset(ATempoContext *atempo,
> + enum AVSampleFormat format,
> + int sample_rate,
> + int channels)
> +{
> + const int sample_size = av_get_bytes_per_sample(format);
> + uint32_t nlevels = 0;
> + uint32_t pot;
> + int i;
> +
> + 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(atempo->window);
> + pot = 1 << nlevels;
> + av_assert0(pot <= atempo->window);
> +
> + if (pot < atempo->window) {
> + atempo->window = pot * 2;
> + nlevels++;
> + }
> +
> + // initialize audio fragment buffers:
> + atempo->frag[0].data = av_realloc(atempo->frag[0].data,
> + atempo->window * atempo->stride);
> + if (!atempo->frag[0].data) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->frag[1].data = av_realloc(atempo->frag[1].data,
> + atempo->window * atempo->stride);
> + if (!atempo->frag[1].data) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->frag[0].xdat = av_realloc(atempo->frag[0].xdat,
> + atempo->window * 2 *
> + sizeof(FFTComplex));
> + if (!atempo->frag[0].xdat) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->frag[1].xdat = av_realloc(atempo->frag[1].xdat,
> + atempo->window * 2 *
> + sizeof(FFTComplex));
> + if (!atempo->frag[1].xdat) {
> + return AVERROR(ENOMEM);
> + }
> +
> + // initialize FFT contexts:
> + av_fft_end(atempo->fft_forward);
> + av_fft_end(atempo->fft_inverse);
> +
> + atempo->fft_forward = av_fft_init(nlevels + 1, 0);
> + if (!atempo->fft_forward) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->fft_inverse = av_fft_init(nlevels + 1, 1);
> + if (!atempo->fft_inverse) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->correlation = av_realloc(atempo->correlation,
> + atempo->window * 2 *
> + sizeof(FFTComplex));
> + if (!atempo->correlation) {
> + return AVERROR(ENOMEM);
> + }
> +
> + atempo->ring = atempo->window * 3;
> + atempo->buffer = av_realloc(atempo->buffer, atempo->ring * atempo->stride);
> + if (!atempo->buffer) {
> + return AVERROR(ENOMEM);
> + }
> +
> + // initialize the Hann window function:
> + atempo->hann = av_realloc(atempo->hann, atempo->window * sizeof(float));
> + if (!atempo->hann) {
> + return AVERROR(ENOMEM);
> + }
> +
> + for (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);
> + return 0;
> +}
> +
> +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 AudioFragment *yae_curr_frag(ATempoContext *atempo)
> +{
> + return &atempo->frag[atempo->nfrag % 2];
> +}
> +
> +inline static AudioFragment *yae_prev_frag(ATempoContext *atempo)
> +{
> + return &atempo->frag[(atempo->nfrag + 1) % 2];
> +}
> +
> +inline static void yae_transform(FFTComplex *xdat, FFTContext *fft)
> +{
> + av_fft_permute(fft, xdat);
> + av_fft_calc(fft, xdat);
> +}
> +
> +/**
> + * A helper macro for initializing complex data buffer with scalar data
> + * of a given type.
> + */
> +#define yae_init_xdat(TScalar, scalar_max) \
nit+: TScaler -> scalar_type
> + do { \
> + const uint8_t *src_end = \
> + src + frag->nsamples * atempo->channels * sizeof(TScalar); \
> + \
> + FFTComplex *xdat = frag->xdat; \
> + TScalar tmp; \
> + \
> + if (atempo->channels == 1) { \
> + for (; src < src_end; blend++) { \
> + tmp = *(const TScalar *)src; \
> + src += sizeof(TScalar); \
> + \
> + xdat->re = (FFTSample)tmp; \
> + xdat->im = 0; \
> + xdat++; \
> + } \
> + } else { \
> + FFTSample s; \
> + FFTSample max; \
> + FFTSample ti; \
> + FFTSample si; \
nit++: this can be put on a single line, save some vertical space
> + int i; \
> + \
> + for (; src < src_end; blend++) { \
> + tmp = *(const TScalar *)src; \
> + src += sizeof(TScalar); \
> + \
> + max = (FFTSample)tmp; \
> + s = FFMIN((FFTSample)scalar_max, \
> + (FFTSample)fabsf(max)); \
> + \
> + for (i = 1; i < atempo->channels; i++) { \
> + tmp = *(const TScalar *)src; \
> + src += sizeof(TScalar); \
> + \
> + ti = (FFTSample)tmp; \
> + si = FFMIN((FFTSample)scalar_max, \
> + (FFTSample)fabsf(ti)); \
> + \
> + if (s < si) { \
> + s = si; \
> + max = ti; \
> + } \
> + } \
> + \
> + xdat->re = max; \
> + xdat->im = 0; \
> + xdat++; \
> + } \
> + } \
> + } while (0)
> +
> +/**
> + * Initialize complex data buffer of a given audio fragment
> + * with down-mixed mono data of appropriate scalar type.
> + */
> +static void yae_downmix(ATempoContext *atempo, AudioFragment *frag)
> +{
> + // shortcuts:
> + const uint8_t *src = frag->data;
> + const float *blend = atempo->hann;
> +
> + // init complex data buffer used for FFT and Correlation:
> + memset(frag->xdat, 0, sizeof(FFTComplex) * atempo->window * 2);
> +
> + if (atempo->format == AV_SAMPLE_FMT_U8) {
> + yae_init_xdat(uint8_t, 127);
> + } else if (atempo->format == AV_SAMPLE_FMT_S16) {
> + yae_init_xdat(int16_t, 32767);
> + } else if (atempo->format == AV_SAMPLE_FMT_S32) {
> + yae_init_xdat(int, 2147483647);
> + } else if (atempo->format == AV_SAMPLE_FMT_FLT) {
> + yae_init_xdat(float, 1);
> + } else if (atempo->format == AV_SAMPLE_FMT_DBL) {
> + yae_init_xdat(double, 1);
> + }
> +}
> +
> +/**
> + * Populate the internal data buffer on as-needed basis.
> + *
> + * @return 0 if requested data was already available or was loaded.
> + * @return AVERROR(EAGAIN) if more input data is required.
nit+++: avoid double @return, a single paragraph is fine (not that it
gains much since this is internal)
> + */
> +static int yae_load_data(ATempoContext *atempo,
> + const uint8_t **src_ref,
> + const uint8_t *src_end,
> + int64_t stop_here)
> +{
> + // shortcut:
> + const uint8_t *src = *src_ref;
> + const int read_size = stop_here - atempo->position[0];
> +
> + if (stop_here <= atempo->position[0]) {
> + return 0;
> + }
> +
> + // samples are not expected to be skipped:
> + av_assert0(read_size <= atempo->ring);
> +
> + while (atempo->position[0] < stop_here && src < src_end) {
> + int src_samples = (src_end - src) / atempo->stride;
> +
> + // load data piece-wise, in order to avoid complicating the logic:
> + int nsamples = FFMIN(read_size, src_samples);
> + int na;
> + int nb;
> +
> + nsamples = FFMIN(nsamples, atempo->ring);
> + na = FFMIN(nsamples, atempo->ring - atempo->tail);
> + nb = FFMIN(nsamples - na, atempo->ring);
> +
> + if (na) {
> + uint8_t *a = atempo->buffer + atempo->tail * atempo->stride;
> + memcpy(a, src, na * atempo->stride);
> +
> + src += na * atempo->stride;
> + atempo->position[0] += na;
> +
> + atempo->size = FFMIN(atempo->size + na, atempo->ring);
> + atempo->tail = (atempo->tail + na) % atempo->ring;
> + atempo->head =
> + atempo->size < atempo->ring ?
> + atempo->tail - atempo->size :
> + atempo->tail;
> + }
> +
> + if (nb) {
> + uint8_t *b = atempo->buffer;
> + memcpy(b, src, nb * atempo->stride);
> +
> + src += nb * atempo->stride;
> + atempo->position[0] += nb;
> +
> + atempo->size = FFMIN(atempo->size + nb, atempo->ring);
> + atempo->tail = (atempo->tail + nb) % atempo->ring;
> + atempo->head =
> + atempo->size < atempo->ring ?
> + atempo->tail - atempo->size :
> + atempo->tail;
> + }
> + }
> +
> + // pass back the updated source buffer pointer:
> + *src_ref = src;
> +
> + // sanity check:
> + av_assert0(atempo->position[0] <= stop_here);
> +
> + return atempo->position[0] == stop_here ? 0 : AVERROR(EAGAIN);
> +}
> +
> +/**
> + * Populate current audio fragment data buffer.
> + *
> + * @return 0 when the fragment is ready.
> + * @return AVERROR(EAGAIN) if more input data is required.
ditto
> + */
> +static int yae_load_frag(ATempoContext *atempo,
> + const uint8_t **src_ref,
> + const uint8_t *src_end)
> +{
> + // shortcuts:
> + AudioFragment *frag = yae_curr_frag(atempo);
> + uint8_t *dst;
> + int64_t missing;
> + uint32_t nsamples;
> + int64_t start;
> + int64_t zeros;
> + int na;
> + int nb;
> + const uint8_t *a;
> + const uint8_t *b;
> + int i0;
> + int i1;
> + int n0;
> + int n1;
nit++: try to group variables together, so we get a shorter
declaration list (easier to read)
[...]
> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> + ATempoContext *atempo = ctx->priv;
> + double tempo = 1.0;
> +
> + // NOTE: this assumes that the caller has memset ctx->priv to 0:
> + atempo->format = AV_SAMPLE_FMT_NONE;
> + atempo->tempo = 1.0;
> + atempo->state = YAE_LOAD_FRAGMENT;
> +
> + if (args) {
> + char *tail = NULL;
> + tempo = av_strtod(args, &tail);
> +
> + if (tail && *tail) {
> + av_log(ctx, AV_LOG_ERROR, "invalid tempo value '%s'\n", args);
Nit+++: "Invalid..."
> + return AVERROR(EINVAL);
> + }
> + }
> +
> + return yae_set_tempo(atempo, tempo, ctx);
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> + ATempoContext *atempo = ctx->priv;
> + yae_clear(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);
> +
> + av_fft_end(atempo->fft_forward);
> + atempo->fft_forward = NULL;
> +
> + av_fft_end(atempo->fft_inverse);
> + atempo->fft_inverse = NULL;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> + AVFilterChannelLayouts *layouts = NULL;
> + AVFilterFormats *formats = NULL;
> +
> + // WSOLA necessitates an internal sliding window ring buffer
> + // for incoming audio stream.
> + //
> + // Planar sample formats are too cumbersome to store in a ring buffer,
> + // therefore planar sample formats are not supported.
> + //
> + 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);
Nit++++: remove the previous empty line, so you group all the instructions
> + return 0;
> +}
> +
> +/**
> + * Check configuration properties of the input link
> + * and update filters internal state as necessary.
> + *
> + * Return zero on success, AVERRROR(..) on failure.
> + */
feel free to drop this doxy
> +static int
> +config_props(AVFilterLink *inlink)
> +{
> + AVFilterContext *ctx = inlink->dst;
> + ATempoContext *atempo = ctx->priv;
> +
> + enum AVSampleFormat format = inlink->format;
> + int sample_rate = (int)inlink->sample_rate;
> + int channels = av_get_channel_layout_nb_channels(inlink->channel_layout);
> +
> + return yae_reset(atempo, format, sample_rate, channels);
> +}
> +
> +static void filter_samples(AVFilterLink *inlink,
> + AVFilterBufferRef *src_buffer)
> +{
> + AVFilterContext *ctx = inlink->dst;
> + ATempoContext *atempo = ctx->priv;
> + AVFilterLink *outlink = ctx->outputs[0];
> +
> + int n_in = src_buffer->audio->nb_samples;
> + int n_out = (int)(0.5 + ((double)n_in) / atempo->tempo);
> +
> + const uint8_t *src = src_buffer->data[0];
> + const uint8_t *src_end = src + n_in * atempo->stride;
> +
> + while (src < src_end) {
> + if (!atempo->dst_buffer) {
> + atempo->dst_buffer = ff_get_audio_buffer(outlink,
> + AV_PERM_WRITE,
> + n_out);
> + avfilter_copy_buffer_ref_props(atempo->dst_buffer, src_buffer);
> +
> + atempo->dst = atempo->dst_buffer->data[0];
> + atempo->dst_end = atempo->dst + n_out * atempo->stride;
> + }
> +
> + yae_apply(atempo, &src, src_end, &atempo->dst, atempo->dst_end);
> +
> + if (atempo->dst == atempo->dst_end) {
> + atempo->dst_buffer->audio->sample_rate = outlink->sample_rate;
> + atempo->dst_buffer->audio->nb_samples = n_out;
> +
> + // adjust the PTS:
> + atempo->dst_buffer->pts =
> + av_rescale(outlink->time_base.den,
> + atempo->nsamples_out,
> + outlink->time_base.num * outlink->sample_rate);
not sure but maybe av_rescale_q(atempo->nsamples_out, (AVRational){1, outlink->sample_rate}, outlink->time_base)
may be safer, as it avoids a possible int*int -> int64_t overflow:
outlink->time_base.num * outlink->sample_rate
> +
> + ff_filter_samples(outlink, atempo->dst_buffer);
> + atempo->dst_buffer = NULL;
> + atempo->dst = NULL;
> + atempo->dst_end = NULL;
> +
> + atempo->nsamples_out += n_out;
> + atempo->request_fulfilled = 1;
> + }
> + }
> +
> + atempo->nsamples_in += n_in;
> + avfilter_unref_buffer(src_buffer);
avfilter_unref_bufferp should be safer
> +}
> +
> +/**
> + * Frame request callback.
> + *
> + * A call to this should result in at least one frame
> + * being output over the given link.
> + *
> + * @return 0 on success.
> + */
> +static int request_frame(AVFilterLink *outlink)
> +{
> + AVFilterContext *ctx = outlink->src;
> + ATempoContext *atempo = ctx->priv;
> + int ret;
> +
> + atempo->request_fulfilled = 0;
> + do {
> + ret = avfilter_request_frame(ctx->inputs[0]);
> + }
> + while (!atempo->request_fulfilled && ret >= 0);
> +
> + if (ret == AVERROR_EOF) {
> + // flush the filter:
> + int n_max = atempo->ring;
> + int n_out;
> + int err = AVERROR(EAGAIN);
> +
> + while (err == AVERROR(EAGAIN)) {
> + if (!atempo->dst_buffer) {
> + atempo->dst_buffer = ff_get_audio_buffer(outlink,
> + AV_PERM_WRITE,
> + n_max);
> +
> + atempo->dst = atempo->dst_buffer->data[0];
> + atempo->dst_end = atempo->dst + n_max * atempo->stride;
> + }
> +
> + err = yae_flush(atempo, &atempo->dst, atempo->dst_end);
> +
> + n_out = ((atempo->dst - atempo->dst_buffer->data[0]) /
> + atempo->stride);
> +
> + if (n_out) {
> + atempo->dst_buffer->audio->sample_rate = outlink->sample_rate;
> + atempo->dst_buffer->audio->nb_samples = n_out;
> +
> + // adjust the PTS:
> + atempo->dst_buffer->pts =
> + av_rescale(outlink->time_base.den,
> + atempo->nsamples_out,
> + outlink->time_base.num * outlink->sample_rate);
> +
> + ff_filter_samples(outlink, atempo->dst_buffer);
> + atempo->dst_buffer = NULL;
> + atempo->dst = NULL;
> + atempo->dst_end = NULL;
> +
> + atempo->nsamples_out += n_out;
> + }
> + }
> +
> + avfilter_unref_bufferp(&atempo->dst_buffer);
> + atempo->dst = NULL;
> + atempo->dst_end = NULL;
> +
> + return AVERROR_EOF;
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * Make the filter instance process a command.
> + *
> + * @param cmd -- an alphanumeric command to process
> + * @param arg -- the argument for the command
> + * @param res -- a buffer with size res_size for filter response
> + * @param flags -- command flags
> + *
> + * When AVFILTER_CMD_FLAG_FAST flag is set time consuming commands
> + * should be treated as unsupported.
> + *
> + * @return 0 on success, otherwise an error code.
> + * @return AVERROR(ENOSYS) on unsupported commands.
> + */
same I'd prefer to skip this doxy, so I won't have to udpate this when
the API changes...
> +static int process_command(AVFilterContext *ctx,
> + const char *cmd,
> + const char *arg,
> + char *res,
> + int res_len,
> + int flags)
> +{
> + ATempoContext *atempo = ctx->priv;
> +
> + if (strcmp(cmd, "tempo") == 0) {
> + char *tail = NULL;
> + double tempo = av_strtod(arg, &tail);
> +
> + if (tail && *tail) {
> + av_log(ctx, AV_LOG_ERROR, "invalid tempo value '%s'\n", arg);
> + return AVERROR(EINVAL);
> + }
> +
> + return yae_set_tempo(atempo, tempo, ctx);
> + }
you could change the signature of yae_set_tempo to
static int yae_set_tempo(AVFilterContext *ctx, const char *tempo)
and factorize some code in init().
> +
> + return AVERROR(ENOSYS);
> +}
> +
> +AVFilter avfilter_af_atempo = {
> + .name = "atempo",
> + .description = NULL_IF_CONFIG_SMALL("Adjust audio tempo."),
> + .init = &init,
> + .uninit = &uninit,
> + .query_formats = &query_formats,
> + .process_command = &process_command,
nit: "&" can be skipped
[...]
I'd love to be able to comment on the more mathematical/algorithmical
part of the patch, but if noone will do I suppose we can assume that
your code has been sufficiently tested.
BTW add your name as maintainer for this filter in MAINTAINERS if you
feel like so.
--
FFmpeg = Fierce and Fantastic Meaningful Political Explosive God
More information about the ffmpeg-devel
mailing list