From 7f42bfad5d763f06d6fb6e91a8cef78f7fad9bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 25 Jan 2014 23:19:05 +0100 Subject: lavfi/ebur128: add sample and true peak metering. Metadata injection and logging. Not yet present visually. Signed-off-by: Jean First --- libavfilter/f_ebur128.c | 140 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 12 deletions(-) (limited to 'libavfilter/f_ebur128.c') diff --git a/libavfilter/f_ebur128.c b/libavfilter/f_ebur128.c index a2c616098e..503d87ac32 100644 --- a/libavfilter/f_ebur128.c +++ b/libavfilter/f_ebur128.c @@ -37,6 +37,7 @@ #include "libavutil/xga_font_data.h" #include "libavutil/opt.h" #include "libavutil/timestamp.h" +#include "libswresample/swresample.h" #include "audio.h" #include "avfilter.h" #include "formats.h" @@ -92,6 +93,16 @@ struct rect { int x, y, w, h; }; typedef struct { const AVClass *class; ///< AVClass context for log and options purpose + /* peak metering */ + int peak_mode; ///< enabled peak modes + double *true_peaks; ///< true peaks per channel + double *sample_peaks; ///< sample peaks per channel +#if CONFIG_SWRESAMPLE + SwrContext *swr_ctx; ///< over-sampling context for true peak metering + double *swr_buf; ///< resampled audio data for true peak metering + int swr_linesize; +#endif + /* video */ int do_video; ///< 1 if video output enabled, 0 otherwise int w, h; ///< size of the video output @@ -130,6 +141,12 @@ typedef struct { int metadata; ///< whether or not to inject loudness results in frames } EBUR128Context; +enum { + PEAK_MODE_NONE = 0, + PEAK_MODE_SAMPLES_PEAKS = 1<<1, + PEAK_MODE_TRUE_PEAKS = 1<<2, +}; + #define OFFSET(x) offsetof(EBUR128Context, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define V AV_OPT_FLAG_VIDEO_PARAM @@ -142,7 +159,11 @@ static const AVOption ebur128_options[] = { { "info", "information logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_INFO}, INT_MIN, INT_MAX, A|V|F, "level" }, { "verbose", "verbose logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_VERBOSE}, INT_MIN, INT_MAX, A|V|F, "level" }, { "metadata", "inject metadata in the filtergraph", OFFSET(metadata), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, A|V|F }, - { NULL } + { "peak", "set peak mode", OFFSET(peak_mode), AV_OPT_TYPE_FLAGS, {.i64 = PEAK_MODE_NONE}, 0, INT_MAX, A|F, "mode" }, + { "none", "disable any peak mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_NONE}, INT_MIN, INT_MAX, A|F, "mode" }, + { "sample", "enable peak-sample mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_SAMPLES_PEAKS}, INT_MIN, INT_MAX, A|F, "mode" }, + { "true", "enable true-peak mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_TRUE_PEAKS}, INT_MIN, INT_MAX, A|F, "mode" }, + { NULL }, }; AVFILTER_DEFINE_CLASS(ebur128); @@ -326,9 +347,13 @@ static int config_audio_input(AVFilterLink *inlink) AVFilterContext *ctx = inlink->dst; EBUR128Context *ebur128 = ctx->priv; - /* force 100ms framing in case of metadata injection: the frames must have - * a granularity of the window overlap to be accurately exploited */ - if (ebur128->metadata) + /* Force 100ms framing in case of metadata injection: the frames must have + * a granularity of the window overlap to be accurately exploited. + * As for the true peaks mode, it just simplifies the resampling buffer + * allocation and the lookup in it (since sample buffers differ in size, it + * can be more complex to integrate in the one-sample loop of + * filter_frame()). */ + if (ebur128->metadata || (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS)) inlink->min_samples = inlink->max_samples = inlink->partial_buf_size = inlink->sample_rate / 10; @@ -383,6 +408,36 @@ static int config_audio_output(AVFilterLink *outlink) outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP; +#if CONFIG_SWRESAMPLE + if (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS) { + int ret; + + ebur128->swr_buf = av_malloc(19200 * nb_channels * sizeof(double)); + ebur128->true_peaks = av_calloc(nb_channels, sizeof(*ebur128->true_peaks)); + ebur128->swr_ctx = swr_alloc(); + if (!ebur128->swr_buf || !ebur128->true_peaks || !ebur128->swr_ctx) + return AVERROR(ENOMEM); + + av_opt_set_int(ebur128->swr_ctx, "in_channel_layout", outlink->channel_layout, 0); + av_opt_set_int(ebur128->swr_ctx, "in_sample_rate", outlink->sample_rate, 0); + av_opt_set_sample_fmt(ebur128->swr_ctx, "in_sample_fmt", outlink->format, 0); + + av_opt_set_int(ebur128->swr_ctx, "out_channel_layout", outlink->channel_layout, 0); + av_opt_set_int(ebur128->swr_ctx, "out_sample_rate", 192000, 0); + av_opt_set_sample_fmt(ebur128->swr_ctx, "out_sample_fmt", outlink->format, 0); + + ret = swr_init(ebur128->swr_ctx); + if (ret < 0) + return ret; + } +#endif + + if (ebur128->peak_mode & PEAK_MODE_SAMPLES_PEAKS) { + ebur128->sample_peaks = av_calloc(nb_channels, sizeof(*ebur128->sample_peaks)); + if (!ebur128->sample_peaks) + return AVERROR(ENOMEM); + } + return 0; } @@ -416,6 +471,12 @@ static av_cold int init(AVFilterContext *ctx) ebur128->loglevel = AV_LOG_INFO; } + if (!CONFIG_SWRESAMPLE && (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS)) { + av_log(ctx, AV_LOG_ERROR, + "True-peak mode requires libswresample to be performed\n"); + return AVERROR(EINVAL); + } + // if meter is +9 scale, scale range is from -18 LU to +9 LU (or 3*9) // if meter is +18 scale, scale range is from -36 LU to +18 LU (or 3*18) ebur128->scale_range = 3 * ebur128->meter; @@ -491,6 +552,22 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) const double *samples = (double *)insamples->data[0]; AVFrame *pic = ebur128->outpicref; +#if CONFIG_SWRESAMPLE + if (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS) { + const double *swr_samples = ebur128->swr_buf; + int ret = swr_convert(ebur128->swr_ctx, (uint8_t**)&ebur128->swr_buf, 19200, + (const uint8_t **)insamples->data, nb_samples); + if (ret < 0) + return ret; + for (idx_insample = 0; idx_insample < ret; idx_insample++) { + for (ch = 0; ch < nb_channels; ch++) { + ebur128->true_peaks[ch] = FFMAX(ebur128->true_peaks[ch], FFABS(*swr_samples)); + swr_samples++; + } + } + } +#endif + for (idx_insample = 0; idx_insample < nb_samples; idx_insample++) { const int bin_id_400 = ebur128->i400.cache_pos; const int bin_id_3000 = ebur128->i3000.cache_pos; @@ -509,6 +586,9 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) for (ch = 0; ch < nb_channels; ch++) { double bin; + if (ebur128->peak_mode & PEAK_MODE_SAMPLES_PEAKS) + ebur128->sample_peaks[ch] = FFMAX(ebur128->sample_peaks[ch], FFABS(*samples)); + ebur128->x[ch * 3] = *samples++; // set X[i] if (!ebur128->ch_weighting[ch]) @@ -677,22 +757,52 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) if (ebur128->metadata) { /* happens only once per filter_frame call */ char metabuf[128]; +#define META_PREFIX "lavfi.r128." + #define SET_META(name, var) do { \ snprintf(metabuf, sizeof(metabuf), "%.3f", var); \ - av_dict_set(&insamples->metadata, "lavfi.r128." name, metabuf, 0); \ + av_dict_set(&insamples->metadata, name, metabuf, 0); \ } while (0) - SET_META("M", loudness_400); - SET_META("S", loudness_3000); - SET_META("I", ebur128->integrated_loudness); - SET_META("LRA", ebur128->loudness_range); - SET_META("LRA.low", ebur128->lra_low); - SET_META("LRA.high", ebur128->lra_high); + +#define SET_META_PEAK(name, ptype) do { \ + if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ + char key[64]; \ + for (ch = 0; ch < nb_channels; ch++) { \ + snprintf(key, sizeof(key), \ + META_PREFIX AV_STRINGIFY(name) "_peaks_ch%d", ch); \ + SET_META(key, ebur128->name##_peaks[ch]); \ + } \ + } \ +} while (0) + + SET_META(META_PREFIX "M", loudness_400); + SET_META(META_PREFIX "S", loudness_3000); + SET_META(META_PREFIX "I", ebur128->integrated_loudness); + SET_META(META_PREFIX "LRA", ebur128->loudness_range); + SET_META(META_PREFIX "LRA.low", ebur128->lra_low); + SET_META(META_PREFIX "LRA.high", ebur128->lra_high); + + SET_META_PEAK(sample, SAMPLES); + SET_META_PEAK(true, TRUE); } - av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT "\n", + av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT, av_ts2timestr(pts, &outlink->time_base), loudness_400, loudness_3000, ebur128->integrated_loudness, ebur128->loudness_range); + +#define PRINT_PEAKS(str, sp, ptype) do { \ + if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ + av_log(ctx, ebur128->loglevel, " [" str ":"); \ + for (ch = 0; ch < nb_channels; ch++) \ + av_log(ctx, ebur128->loglevel, " %.5f", sp[ch]); \ + av_log(ctx, ebur128->loglevel, "]"); \ + } \ +} while (0) + + PRINT_PEAKS("SPK", ebur128->sample_peaks, SAMPLES); + PRINT_PEAKS("TPK", ebur128->true_peaks, TRUE); + av_log(ctx, ebur128->loglevel, "\n"); } } @@ -764,6 +874,8 @@ static av_cold void uninit(AVFilterContext *ctx) av_freep(&ebur128->y_line_ref); av_freep(&ebur128->ch_weighting); + av_freep(&ebur128->true_peaks); + av_freep(&ebur128->sample_peaks); av_freep(&ebur128->i400.histogram); av_freep(&ebur128->i3000.histogram); for (i = 0; i < ebur128->nb_channels; i++) { @@ -773,6 +885,10 @@ static av_cold void uninit(AVFilterContext *ctx) for (i = 0; i < ctx->nb_outputs; i++) av_freep(&ctx->output_pads[i].name); av_frame_free(&ebur128->outpicref); +#if CONFIG_SWRESAMPLE + av_freep(&ebur128->swr_buf); + swr_free(&ebur128->swr_ctx); +#endif } static const AVFilterPad ebur128_inputs[] = { -- cgit v1.2.1