[FFmpeg-devel] [PATCH] lavfi/af_ebur128: update filter to use new ebur128 API

Marton Balint cus at passwd.hu
Tue Nov 29 06:59:04 EET 2016


On Mon, 28 Nov 2016, Kyle Swanson wrote:

> On Thu, Nov 17, 2016 at 11:04 AM, Kyle Swanson <k at ylo.ph> wrote:
>> Hi,
>>
>> Here's a couple of patches which update the ebur128 filter to use the
>> recently added ebur128 API. This updated filter allows fine-tuned
>> control over which EBU R128 parameters are measured, and provides
>> modest speed increases over the previous ebur128 filter. Also
>> noteworthy: this removes the video output option of the ebur128
>> filter. This is extraneous for an ebur128 measurement filter IMHO, but
>> if we wanted to keep similar functionality in FFmpeg, we'd be better
>> served by a new video source filter where custom meters could be
>> created via exported frame metadata.
>>
>> The first patch adds true peak functionality to the ebur128 API using
>> swresample (this was already discussed a little bit:
>> http://ffmpeg.org/pipermail/ffmpeg-devel/2016-November/202583.html)
>> The second patch is an update to the ebur128 filter.
>>
>> Kyle
>
> Does anyone have any problems with the first patch?

> From 6912ed3a03cd19f46e96f1f4b9eb3aa69b7ce4df Mon Sep 17 00:00:00 2001
> From: Kyle Swanson <k at ylo.ph>
> Date: Thu, 17 Nov 2016 10:32:45 -0600
> Subject: [PATCH 1/2] lavfi/ebur128: add ebur128_check_true_peak()
> 
> Signed-off-by: Kyle Swanson <k at ylo.ph>
> ---
>  libavfilter/ebur128.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++++-
>  libavfilter/ebur128.h |  17 ++++++
>  2 files changed, 177 insertions(+), 2 deletions(-)
> 
> diff --git a/libavfilter/ebur128.c b/libavfilter/ebur128.c
> index a46692e..dc16647 100644
> --- a/libavfilter/ebur128.c
> +++ b/libavfilter/ebur128.c
> @@ -50,6 +50,9 @@
>  #include "libavutil/common.h"
>  #include "libavutil/mem.h"
>  #include "libavutil/thread.h"
> +#include "libavutil/channel_layout.h"
> +#include "libswresample/swresample.h"

Isn't this include needs an ifdef as well?

> +#include "libavutil/opt.h"
>
>  #define CHECK_ERROR(condition, errorcode, goto_point)                          \
>      if ((condition)) {                                                         \
> @@ -91,6 +94,16 @@ struct FFEBUR128StateInternal {
>      size_t short_term_frame_counter;
>      /** Maximum sample peak, one per channel */
>      double *sample_peak;
> +    /** Maximum true peak, one per channel */
> +    double* true_peak;
> +#if CONFIG_SWRESAMPLE
> +    SwrContext *resampler;
> +    size_t oversample_factor;
> +    float* resampler_buffer_input;
> +    size_t resampler_buffer_input_frames;
> +    float* resampler_buffer_output;
> +    size_t resampler_buffer_output_frames;
> +#endif
>      /** The maximum window duration in ms. */
>      unsigned long window;
>      /** Data pointer array for interleaved data */
> @@ -214,12 +227,78 @@ static inline void init_histogram(void)
>      }
>  }
> 
> +#if CONFIG_SWRESAMPLE
> +static int ebur128_init_resampler(FFEBUR128State* st) {
> +    int64_t channel_layout;
> +    int errcode;
> +
> +    if (st->samplerate < 96000) {
> +        st->d->oversample_factor = 4;
> +    } else if (st->samplerate < 192000) {
> +        st->d->oversample_factor = 2;
> +    } else {
> +        st->d->oversample_factor = 1;
> +        st->d->resampler_buffer_input = NULL;
> +        st->d->resampler_buffer_output = NULL;
> +        st->d->resampler = NULL;
> +    }
> +
> +    st->d->resampler_buffer_input_frames = st->d->samples_in_100ms * 4;
> +    st->d->resampler_buffer_input = av_malloc(st->d->resampler_buffer_input_frames *
> +                                              st->channels *
> +                                              sizeof(float));

av_malloc_array

> +    CHECK_ERROR(!st->d->resampler_buffer_input, 0, exit)
> +
> +    st->d->resampler_buffer_output_frames =
> +    st->d->resampler_buffer_input_frames *
> +    st->d->oversample_factor;
> +    st->d->resampler_buffer_output = av_malloc(st->d->resampler_buffer_output_frames *
> +                                               st->channels *
> +                                               sizeof(float));

av_malloc_array

> +    CHECK_ERROR(!st->d->resampler_buffer_output, 0, free_input)
> +
> +    st->d->resampler = swr_alloc();
> +    CHECK_ERROR(!st->d->resampler, 0, free_output)
> +
> +    channel_layout = av_get_default_channel_layout(st->channels);
> +
> +    av_opt_set_int(st->d->resampler, "in_channel_layout", channel_layout, 0);
> +    av_opt_set_int(st->d->resampler, "in_sample_rate", st->samplerate, 0);
> +    av_opt_set_sample_fmt(st->d->resampler, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
> +    av_opt_set_int(st->d->resampler, "out_channel_layout", channel_layout, 0);
> +    av_opt_set_int(st->d->resampler, "out_sample_rate", st->samplerate * st->d->oversample_factor, 0);
> +    av_opt_set_sample_fmt(st->d->resampler, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
> +
> +    swr_init(st->d->resampler);
> +    return 0;
> +
> +free_output:
> +    av_free(st->d->resampler_buffer_output);
> +    st->d->resampler_buffer_output = NULL;

av_freep

> +free_input:
> +    av_free(st->d->resampler_buffer_input);
> +    st->d->resampler_buffer_input = NULL;

av_freep

> +exit:
> +    return AVERROR(ENOMEM);
> +}
> +
> +static void ebur128_destroy_resampler(FFEBUR128State* st) {
> +    av_free(st->d->resampler_buffer_input);
> +    st->d->resampler_buffer_input = NULL;

av_freep

> +    av_free(st->d->resampler_buffer_output);
> +    st->d->resampler_buffer_output = NULL;

av_freep

> +    swr_free(&st->d->resampler);
> +    st->d->resampler = NULL;

swr_free already sets resampler to NULL.

> +}
> +#endif
> +
>  FFEBUR128State *ff_ebur128_init(unsigned int channels,
>                                  unsigned long samplerate,
>                                  unsigned long window, int mode)
>  {
>      int errcode;
>      FFEBUR128State *st;
> +    unsigned int i;
>
>      st = (FFEBUR128State *) av_malloc(sizeof(FFEBUR128State));
>      CHECK_ERROR(!st, 0, exit)
> @@ -233,6 +312,14 @@ FFEBUR128State *ff_ebur128_init(unsigned int channels,
>      st->d->sample_peak =
>          (double *) av_mallocz_array(channels, sizeof(double));
>      CHECK_ERROR(!st->d->sample_peak, 0, free_channel_map)
> +    st->d->true_peak =
> +        (double*) malloc(channels * sizeof(double));

av_mallocz_array

> +    CHECK_ERROR(!st->d->true_peak, 0, free_sample_peak)
> +
> +    for (i = 0; i < channels; ++i) {
> +        st->d->sample_peak[i] = 0.0;
> +        st->d->true_peak[i] = 0.0;
> +    }

Technically not portable (AFAIK), but we assume in a lot of places that a 
mallocz-ed double is 0.0, so this initialization is unneeded.

>
>      st->samplerate = samplerate;
>      st->d->samples_in_100ms = (st->samplerate + 5) / 10;
> @@ -242,7 +329,7 @@ FFEBUR128State *ff_ebur128_init(unsigned int channels,
>      } else if ((mode & FF_EBUR128_MODE_M) == FF_EBUR128_MODE_M) {
>          st->d->window = FFMAX(window, 400);
>      } else {
> -        goto free_sample_peak;
> +        goto free_true_peak;
>      }
>      st->d->audio_data_frames = st->samplerate * st->d->window / 1000;
>      if (st->d->audio_data_frames % st->d->samples_in_100ms) {
> @@ -254,7 +341,7 @@ FFEBUR128State *ff_ebur128_init(unsigned int channels,
>      st->d->audio_data =
>          (double *) av_mallocz_array(st->d->audio_data_frames,
>                                      st->channels * sizeof(double));
> -    CHECK_ERROR(!st->d->audio_data, 0, free_sample_peak)
> +    CHECK_ERROR(!st->d->audio_data, 0, free_true_peak)
>
>      ebur128_init_filter(st);
> 
> @@ -267,6 +354,11 @@ FFEBUR128State *ff_ebur128_init(unsigned int channels,
>                  free_block_energy_histogram)
>      st->d->short_term_frame_counter = 0;
> 
> +#if CONFIG_SWRESAMPLE
> +    unsigned int result = ebur128_init_resampler(st);

Why unsigned?

> +    CHECK_ERROR(result, 0, free_short_term_block_energy_histogram)
> +#endif
> +
>      /* the first block needs 400ms of audio data */
>      st->d->needed_frames = st->d->samples_in_100ms * 4;
>      /* start at the beginning of the buffer */
> @@ -287,6 +379,8 @@ free_block_energy_histogram:
>      av_free(st->d->block_energy_histogram);
>  free_audio_data:
>      av_free(st->d->audio_data);
> +free_true_peak:
> +    av_free(st->d->true_peak);
>  free_sample_peak:
>      av_free(st->d->sample_peak);
>  free_channel_map:
> @@ -306,12 +400,53 @@ void ff_ebur128_destroy(FFEBUR128State ** st)
>      av_free((*st)->d->audio_data);
>      av_free((*st)->d->channel_map);
>      av_free((*st)->d->sample_peak);
> +    av_free((*st)->d->true_peak);
>      av_free((*st)->d->data_ptrs);
> +#if CONFIG_SWRESAMPLE
> +  ebur128_destroy_resampler(*st);
> +#endif
>      av_free((*st)->d);
>      av_free(*st);
>      *st = NULL;
>  }
> 
> +static int ebur128_use_swresample(FFEBUR128State* st) {
> +#if CONFIG_SWRESAMPLE
> +    return ((st->mode & FF_EBUR128_MODE_TRUE_PEAK) == FF_EBUR128_MODE_TRUE_PEAK);
> +#else
> +    (void) st;
> +    return 0;
> +#endif
> +}
> +
> +static void ebur128_check_true_peak(FFEBUR128State* st, size_t frames) {
> +#if CONFIG_SWRESAMPLE
> +    size_t c, i;
> +
> +    const int in_len  = frames;
> +    const int out_len = st->d->resampler_buffer_output_frames;
> +    swr_convert(st->d->resampler, (uint8_t **)&st->d->resampler_buffer_output, out_len,
> +                (const uint8_t **)&st->d->resampler_buffer_input, in_len);
> +
> +    for (c = 0; c < st->channels; ++c) {
> +        for (i = 0; i < out_len; ++i) {
> +            if (st->d->resampler_buffer_output[i * st->channels + c] >
> +                                                               st->d->true_peak[c]) {
> +              st->d->true_peak[c] =
> +                  st->d->resampler_buffer_output[i * st->channels + c];
> +            } else if (-st->d->resampler_buffer_output[i * st->channels + c] >
> +                                                               st->d->true_peak[c]) {
> +              st->d->true_peak[c] =
> +                 -st->d->resampler_buffer_output[i * st->channels + c];
> +            }
> +        }
> +    }
> +#else
> +    (void) st; (void) frames;
> +#endif
> +}
> +
> +
>  #define EBUR128_FILTER(type, scaling_factor)                                       \
>  static void ebur128_filter_##type(FFEBUR128State* st, const type** srcs,           \
>                                    size_t src_index, size_t frames,                 \
> @@ -334,6 +469,15 @@ static void ebur128_filter_##type(FFEBUR128State* st, const type** srcs,
>              if (max > st->d->sample_peak[c]) st->d->sample_peak[c] = max;          \
>          }                                                                          \
>      }                                                                              \
> +    if (ebur128_use_swresample(st)) {                                              \
> +        for (c = 0; c < st->channels; ++c) {                                       \
> +            for (i = 0; i < frames; ++i) {                                         \
> +                st->d->resampler_buffer_input[i * st->channels + c] =              \
> +                    (float) (srcs[c][src_index + i * stride] / scaling_factor);    \
> +            }                                                                      \
> +        }                                                                          \
> +        ebur128_check_true_peak(st, frames);                                       \
> +    }                                                                              \
>      for (c = 0; c < st->channels; ++c) {                                           \
>          int ci = st->d->channel_map[c] - 1;                                        \
>          if (ci < 0) continue;                                                      \
> @@ -781,3 +925,17 @@ int ff_ebur128_sample_peak(FFEBUR128State * st,
>      *out = st->d->sample_peak[channel_number];
>      return 0;

Hmm, okay, I got a bit of a problem with this performance-wise. The way I 
see it first we convert everything to float, then we resample, then we 
find the maximum in an interleaved output. I'd say performance-wise it 
would be alot better if we could resample directly the input data to a 
planar float, and then measure the maximum there, so no intermediate 
conversions.

This can be a second step, if you are not interested in this now.

}
> +
> +int ff_ebur128_true_peak(FFEBUR128State * st,
> +                         unsigned int channel_number,
> +                         double* out) {
> +  if ((st->mode & FF_EBUR128_MODE_TRUE_PEAK) != FF_EBUR128_MODE_TRUE_PEAK) {
> +      return AVERROR(EINVAL);
> +  } else if (channel_number >= st->channels) {
> +      return AVERROR(EINVAL);
> +  }
> +  *out = st->d->true_peak[channel_number] > st->d->sample_peak[channel_number]
> +       ? st->d->true_peak[channel_number]
> +       : st->d->sample_peak[channel_number];
> +  return 0;
> +}
> diff --git a/libavfilter/ebur128.h b/libavfilter/ebur128.h
> index b94cd24..ca6dd62 100644
> --- a/libavfilter/ebur128.h
> +++ b/libavfilter/ebur128.h
> @@ -91,6 +91,9 @@ enum mode {
>      FF_EBUR128_MODE_LRA = (1 << 3) | FF_EBUR128_MODE_S,
>    /** can call ff_ebur128_sample_peak */
>      FF_EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | FF_EBUR128_MODE_M,
> +  /** can call ff_ebur128_true_peak */
> +    FF_EBUR128_MODE_TRUE_PEAK   = (1 << 5) | FF_EBUR128_MODE_M
> +                                           | FF_EBUR128_MODE_SAMPLE_PEAK

I'd rather not set implicitly MODE_M, because I don't want to give 
loudness measurement to the user, who only wants true peak.

>  };
>
>  /** forward declaration of FFEBUR128StateInternal */
> @@ -283,6 +286,20 @@ int ff_ebur128_loudness_range_multiple(FFEBUR128State ** sts,
>  int ff_ebur128_sample_peak(FFEBUR128State * st,
>                             unsigned int channel_number, double *out);
> 
> +/** \brief Get maximum true peak from all frames that have been processed.
> + *
> + *  @param st library state
> + *  @param channel_number channel to analyse
> + *  @param out maximum true peak in float format (1.0 is 0 dBTP)
> + *  @return
> + *    - 0 on success.
> + *    - AVERROR(EINVAL) if mode "FF_EBUR128_MODE_TRUE_PEAK" has not
> + *      been set.
> + *    - AVERROR(EINVAL) if invalid channel index.
> + */
> +int ff_ebur128_true_peak(FFEBUR128State* st,
> +                      unsigned int channel_number, double* out);
> +
>  /** \brief Get relative threshold in LUFS.
>   *
>   *  @param st library state
> -- 
> 2.10.1
>

Regards,
Marton


More information about the ffmpeg-devel mailing list