From patchwork Sun Oct 16 19:32:03 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Marton Balint X-Patchwork-Id: 1022 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.140.133 with SMTP id o127csp81428vsd; Sun, 16 Oct 2016 12:32:31 -0700 (PDT) X-Received: by 10.28.158.209 with SMTP id h200mr6292959wme.54.1476646351052; Sun, 16 Oct 2016 12:32:31 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id d10si17679038wji.82.2016.10.16.12.32.30; Sun, 16 Oct 2016 12:32:30 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 1A73A689750; Sun, 16 Oct 2016 22:32:24 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from iq.passwd.hu (iq.passwd.hu [217.27.212.140]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8436E6891E7 for ; Sun, 16 Oct 2016 22:32:17 +0300 (EEST) Received: from localhost (localhost [127.0.0.1]) by iq.passwd.hu (Postfix) with ESMTP id 6DA3D101709; Sun, 16 Oct 2016 21:32:19 +0200 (CEST) X-Virus-Scanned: amavisd-new at passwd.hu Received: from iq.passwd.hu ([127.0.0.1]) by localhost (iq.passwd.hu [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id SbrBSZExm8H0; Sun, 16 Oct 2016 21:32:15 +0200 (CEST) Received: from bluegene.passwd.hu (localhost [127.0.0.1]) by iq.passwd.hu (Postfix) with ESMTP id A74B4100BD2; Sun, 16 Oct 2016 21:32:15 +0200 (CEST) From: Marton Balint To: ffmpeg-devel@ffmpeg.org Date: Sun, 16 Oct 2016 21:32:03 +0200 Message-Id: <1476646324-15429-2-git-send-email-cus@passwd.hu> X-Mailer: git-send-email 2.6.6 In-Reply-To: <1476646324-15429-1-git-send-email-cus@passwd.hu> References: <1476646324-15429-1-git-send-email-cus@passwd.hu> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 2/3] lavfi/loudnorm: add an internal libebur128 library X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Kyle Swanson , Marton Balint Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Also contains the following changes to the library: - add ff_ prefix to functions - remove cplusplus defines. - add FF_ prefix to contants and some structs - remove true peak calculation feature, since it uses its own resampler, and af_audnorm does not need it. - remove version info and some fprintf(stderr) functions - convert to use av_malloc - always use histogram mode for LRA calculation, otherwise LRA data is slowly consuming memory making af_loudnorm unfit for 24/7 operation. It also uses a BSD style linked list implementation which is probably not available on all platforms. So let's just remove the classic mode which not uses histogram. - add ff_thread_once for calculating static histogram tables - convert some functions to void which cannot fail - remove intrinsics and some unused headers - add support for planar audio Signed-off-by: Marton Balint --- configure | 5 - libavfilter/Makefile | 2 +- libavfilter/af_loudnorm.c | 60 ++-- libavfilter/ebur128.c | 850 ++++++++++++++++++++++++++++++++++++++++++++++ libavfilter/ebur128.h | 359 ++++++++++++++++++++ 5 files changed, 1240 insertions(+), 36 deletions(-) create mode 100644 libavfilter/ebur128.c create mode 100644 libavfilter/ebur128.h diff --git a/configure b/configure index 96f575f..24d1906 100755 --- a/configure +++ b/configure @@ -223,8 +223,6 @@ External library support: --enable-libcdio enable audio CD grabbing with libcdio [no] --enable-libdc1394 enable IIDC-1394 grabbing using libdc1394 and libraw1394 [no] - --enable-libebur128 enable libebur128 for EBU R128 measurement, - needed for loudnorm filter [no] --enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no] --enable-libflite enable flite (voice synthesis) support via libflite [no] --enable-libfontconfig enable libfontconfig, useful for drawtext filter [no] @@ -1486,7 +1484,6 @@ EXTERNAL_LIBRARY_LIST=" libcdio libcelt libdc1394 - libebur128 libfdk_aac libflite libfontconfig @@ -3045,7 +3042,6 @@ hqdn3d_filter_deps="gpl" interlace_filter_deps="gpl" kerndeint_filter_deps="gpl" ladspa_filter_deps="ladspa dlopen" -loudnorm_filter_deps="libebur128" mcdeint_filter_deps="avcodec gpl" movie_filter_deps="avcodec avformat" mpdecimate_filter_deps="gpl" @@ -5684,7 +5680,6 @@ enabled libcelt && require libcelt celt/celt.h celt_decode -lcelt0 && { check_lib celt/celt.h celt_decoder_create_custom -lcelt0 || die "ERROR: libcelt must be installed and version must be >= 0.11.0."; } enabled libcaca && require_pkg_config caca caca.h caca_create_canvas -enabled libebur128 && require ebur128 ebur128.h ebur128_relative_threshold -lebur128 enabled libfdk_aac && { use_pkg_config fdk-aac "fdk-aac/aacenc_lib.h" aacEncOpen || { require libfdk_aac fdk-aac/aacenc_lib.h aacEncOpen -lfdk-aac && warn "using libfdk without pkg-config"; } } diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 7ed4696..cdddb1b 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -93,7 +93,7 @@ OBJS-$(CONFIG_HDCD_FILTER) += af_hdcd.o OBJS-$(CONFIG_HIGHPASS_FILTER) += af_biquads.o OBJS-$(CONFIG_JOIN_FILTER) += af_join.o OBJS-$(CONFIG_LADSPA_FILTER) += af_ladspa.o -OBJS-$(CONFIG_LOUDNORM_FILTER) += af_loudnorm.o +OBJS-$(CONFIG_LOUDNORM_FILTER) += af_loudnorm.o ebur128.o OBJS-$(CONFIG_LOWPASS_FILTER) += af_biquads.o OBJS-$(CONFIG_PAN_FILTER) += af_pan.o OBJS-$(CONFIG_REPLAYGAIN_FILTER) += af_replaygain.o diff --git a/libavfilter/af_loudnorm.c b/libavfilter/af_loudnorm.c index 604697e..e470f03 100644 --- a/libavfilter/af_loudnorm.c +++ b/libavfilter/af_loudnorm.c @@ -24,7 +24,7 @@ #include "avfilter.h" #include "internal.h" #include "audio.h" -#include +#include "ebur128.h" enum FrameType { FIRST_FRAME, @@ -91,8 +91,8 @@ typedef struct LoudNormContext { int prev_nb_samples; int channels; - ebur128_state *r128_in; - ebur128_state *r128_out; + FFEBUR128State *r128_in; + FFEBUR128State *r128_out; } LoudNormContext; #define OFFSET(x) offsetof(LoudNormContext, x) @@ -437,15 +437,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) buf = s->buf; limiter_buf = s->limiter_buf; - ebur128_add_frames_double(s->r128_in, src, in->nb_samples); + ff_ebur128_add_il_frames_double(s->r128_in, src, in->nb_samples); if (s->frame_type == FIRST_FRAME && in->nb_samples < frame_size(inlink->sample_rate, 3000)) { double offset, offset_tp, true_peak; - ebur128_loudness_global(s->r128_in, &global); + ff_ebur128_loudness_global(s->r128_in, &global); for (c = 0; c < inlink->channels; c++) { double tmp; - ebur128_sample_peak(s->r128_in, c, &tmp); + ff_ebur128_sample_peak(s->r128_in, c, &tmp); if (c == 0 || tmp > true_peak) true_peak = tmp; } @@ -467,7 +467,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) s->buf_index += inlink->channels; } - ebur128_loudness_shortterm(s->r128_in, &shortterm); + ff_ebur128_loudness_shortterm(s->r128_in, &shortterm); if (shortterm < s->measured_thresh) { s->above_threshold = 0; @@ -497,7 +497,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) subframe_length = frame_size(inlink->sample_rate, 100); true_peak_limiter(s, dst, subframe_length, inlink->channels); - ebur128_add_frames_double(s->r128_out, dst, subframe_length); + ff_ebur128_add_il_frames_double(s->r128_out, dst, subframe_length); s->pts += out->nb_samples = @@ -536,12 +536,12 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) s->limiter_buf_index = s->limiter_buf_index + subframe_length < s->limiter_buf_size ? s->limiter_buf_index + subframe_length : s->limiter_buf_index + subframe_length - s->limiter_buf_size; true_peak_limiter(s, dst, in->nb_samples, inlink->channels); - ebur128_add_frames_double(s->r128_out, dst, in->nb_samples); + ff_ebur128_add_il_frames_double(s->r128_out, dst, in->nb_samples); - ebur128_loudness_range(s->r128_in, &lra); - ebur128_loudness_global(s->r128_in, &global); - ebur128_loudness_shortterm(s->r128_in, &shortterm); - ebur128_relative_threshold(s->r128_in, &relative_threshold); + ff_ebur128_loudness_range(s->r128_in, &lra); + ff_ebur128_loudness_global(s->r128_in, &global); + ff_ebur128_loudness_shortterm(s->r128_in, &shortterm); + ff_ebur128_relative_threshold(s->r128_in, &relative_threshold); if (s->above_threshold == 0) { double shortterm_out; @@ -549,7 +549,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) if (shortterm > s->measured_thresh) s->prev_delta *= 1.0058; - ebur128_loudness_shortterm(s->r128_out, &shortterm_out); + ff_ebur128_loudness_shortterm(s->r128_out, &shortterm_out); if (shortterm_out >= s->target_i) s->above_threshold = 1; } @@ -611,7 +611,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) } dst = (double *)out->data[0]; - ebur128_add_frames_double(s->r128_out, dst, in->nb_samples); + ff_ebur128_add_il_frames_double(s->r128_out, dst, in->nb_samples); break; case LINEAR_MODE: @@ -624,7 +624,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) } dst = (double *)out->data[0]; - ebur128_add_frames_double(s->r128_out, dst, in->nb_samples); + ff_ebur128_add_il_frames_double(s->r128_out, dst, in->nb_samples); s->pts += in->nb_samples; break; } @@ -725,17 +725,17 @@ static int config_input(AVFilterLink *inlink) AVFilterContext *ctx = inlink->dst; LoudNormContext *s = ctx->priv; - s->r128_in = ebur128_init(inlink->channels, inlink->sample_rate, EBUR128_MODE_I | EBUR128_MODE_S | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK); + s->r128_in = ff_ebur128_init(inlink->channels, inlink->sample_rate, FF_EBUR128_MODE_I | FF_EBUR128_MODE_S | FF_EBUR128_MODE_LRA | FF_EBUR128_MODE_SAMPLE_PEAK); if (!s->r128_in) return AVERROR(ENOMEM); - s->r128_out = ebur128_init(inlink->channels, inlink->sample_rate, EBUR128_MODE_I | EBUR128_MODE_S | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK); + s->r128_out = ff_ebur128_init(inlink->channels, inlink->sample_rate, FF_EBUR128_MODE_I | FF_EBUR128_MODE_S | FF_EBUR128_MODE_LRA | FF_EBUR128_MODE_SAMPLE_PEAK); if (!s->r128_out) return AVERROR(ENOMEM); if (inlink->channels == 1 && s->dual_mono) { - ebur128_set_channel(s->r128_in, 0, EBUR128_DUAL_MONO); - ebur128_set_channel(s->r128_out, 0, EBUR128_DUAL_MONO); + ff_ebur128_set_channel(s->r128_in, 0, FF_EBUR128_DUAL_MONO); + ff_ebur128_set_channel(s->r128_out, 0, FF_EBUR128_DUAL_MONO); } s->buf_size = frame_size(inlink->sample_rate, 3000) * inlink->channels; @@ -799,22 +799,22 @@ static av_cold void uninit(AVFilterContext *ctx) if (!s->r128_in || !s->r128_out) goto end; - ebur128_loudness_range(s->r128_in, &lra_in); - ebur128_loudness_global(s->r128_in, &i_in); - ebur128_relative_threshold(s->r128_in, &thresh_in); + ff_ebur128_loudness_range(s->r128_in, &lra_in); + ff_ebur128_loudness_global(s->r128_in, &i_in); + ff_ebur128_relative_threshold(s->r128_in, &thresh_in); for (c = 0; c < s->channels; c++) { double tmp; - ebur128_sample_peak(s->r128_in, c, &tmp); + ff_ebur128_sample_peak(s->r128_in, c, &tmp); if ((c == 0) || (tmp > tp_in)) tp_in = tmp; } - ebur128_loudness_range(s->r128_out, &lra_out); - ebur128_loudness_global(s->r128_out, &i_out); - ebur128_relative_threshold(s->r128_out, &thresh_out); + ff_ebur128_loudness_range(s->r128_out, &lra_out); + ff_ebur128_loudness_global(s->r128_out, &i_out); + ff_ebur128_relative_threshold(s->r128_out, &thresh_out); for (c = 0; c < s->channels; c++) { double tmp; - ebur128_sample_peak(s->r128_out, c, &tmp); + ff_ebur128_sample_peak(s->r128_out, c, &tmp); if ((c == 0) || (tmp > tp_out)) tp_out = tmp; } @@ -881,9 +881,9 @@ static av_cold void uninit(AVFilterContext *ctx) end: if (s->r128_in) - ebur128_destroy(&s->r128_in); + ff_ebur128_destroy(&s->r128_in); if (s->r128_out) - ebur128_destroy(&s->r128_out); + ff_ebur128_destroy(&s->r128_out); av_freep(&s->limiter_buf); av_freep(&s->prev_smp); av_freep(&s->buf); diff --git a/libavfilter/ebur128.c b/libavfilter/ebur128.c new file mode 100644 index 0000000..318da2f --- /dev/null +++ b/libavfilter/ebur128.c @@ -0,0 +1,850 @@ +/* + * Copyright (c) 2011 Jan Kokemüller + * + * 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 + * + * This file is based on libebur128 which is available at + * https://github.com/jiixyj/libebur128/ + * + * Libebur128 has the following copyright: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ + +#include "ebur128.h" + +#include +#include +#include /* You may have to define _USE_MATH_DEFINES if you use MSVC */ + +#include "libavutil/mem.h" +#include "libavutil/thread.h" + +#define CHECK_ERROR(condition, errorcode, goto_point) \ + if ((condition)) { \ + errcode = (errorcode); \ + goto goto_point; \ + } + +#define ALMOST_ZERO 0.000001 + +struct FFEBUR128StateInternal { + /** Filtered audio data (used as ring buffer). */ + double* audio_data; + /** Size of audio_data array. */ + size_t audio_data_frames; + /** Current index for audio_data. */ + size_t audio_data_index; + /** How many frames are needed for a gating block. Will correspond to 400ms + * of audio at initialization, and 100ms after the first block (75% overlap + * as specified in the 2011 revision of BS1770). */ + unsigned long needed_frames; + /** The channel map. Has as many elements as there are channels. */ + int* channel_map; + /** How many samples fit in 100ms (rounded). */ + unsigned long samples_in_100ms; + /** BS.1770 filter coefficients (nominator). */ + double b[5]; + /** BS.1770 filter coefficients (denominator). */ + double a[5]; + /** BS.1770 filter state. */ + double v[5][5]; + /** Histograms, used to calculate LRA. */ + unsigned long *block_energy_histogram; + unsigned long *short_term_block_energy_histogram; + /** Keeps track of when a new short term block is needed. */ + size_t short_term_frame_counter; + /** Maximum sample peak, one per channel */ + double* sample_peak; + /** The maximum window duration in ms. */ + unsigned long window; + /** Data pointer array for interleaved data */ + void **data_ptrs; +}; + +static double relative_gate = -10.0; + +static AVOnce histogram_init = AV_ONCE_INIT; +static double relative_gate_factor; +static double minus_twenty_decibels; +static double histogram_energies[1000]; +static double histogram_energy_boundaries[1001]; + +static void ebur128_init_filter(FFEBUR128State* st) { + int i, j; + + double f0 = 1681.974450955533; + double G = 3.999843853973347; + double Q = 0.7071752369554196; + + double K = tan(M_PI * f0 / (double) st->samplerate); + double Vh = pow(10.0, G / 20.0); + double Vb = pow(Vh, 0.4996667741545416); + + double pb[3] = {0.0, 0.0, 0.0}; + double pa[3] = {1.0, 0.0, 0.0}; + double rb[3] = {1.0, -2.0, 1.0}; + double ra[3] = {1.0, 0.0, 0.0}; + + double a0 = 1.0 + K / Q + K * K ; + pb[0] = (Vh + Vb * K / Q + K * K) / a0; + pb[1] = 2.0 * (K * K - Vh) / a0; + pb[2] = (Vh - Vb * K / Q + K * K) / a0; + pa[1] = 2.0 * (K * K - 1.0) / a0; + pa[2] = (1.0 - K / Q + K * K) / a0; + + f0 = 38.13547087602444; + Q = 0.5003270373238773; + K = tan(M_PI * f0 / (double) st->samplerate); + + ra[1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); + ra[2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); + + st->d->b[0] = pb[0] * rb[0]; + st->d->b[1] = pb[0] * rb[1] + pb[1] * rb[0]; + st->d->b[2] = pb[0] * rb[2] + pb[1] * rb[1] + pb[2] * rb[0]; + st->d->b[3] = pb[1] * rb[2] + pb[2] * rb[1]; + st->d->b[4] = pb[2] * rb[2]; + + st->d->a[0] = pa[0] * ra[0]; + st->d->a[1] = pa[0] * ra[1] + pa[1] * ra[0]; + st->d->a[2] = pa[0] * ra[2] + pa[1] * ra[1] + pa[2] * ra[0]; + st->d->a[3] = pa[1] * ra[2] + pa[2] * ra[1]; + st->d->a[4] = pa[2] * ra[2]; + + for (i = 0; i < 5; ++i) { + for (j = 0; j < 5; ++j) { + st->d->v[i][j] = 0.0; + } + } +} + +static int ebur128_init_channel_map(FFEBUR128State* st) { + size_t i; + st->d->channel_map = (int*) av_malloc_array(st->channels, sizeof(int)); + if (!st->d->channel_map) return FF_EBUR128_ERROR_NOMEM; + if (st->channels == 4) { + st->d->channel_map[0] = FF_EBUR128_LEFT; + st->d->channel_map[1] = FF_EBUR128_RIGHT; + st->d->channel_map[2] = FF_EBUR128_LEFT_SURROUND; + st->d->channel_map[3] = FF_EBUR128_RIGHT_SURROUND; + } else if (st->channels == 5) { + st->d->channel_map[0] = FF_EBUR128_LEFT; + st->d->channel_map[1] = FF_EBUR128_RIGHT; + st->d->channel_map[2] = FF_EBUR128_CENTER; + st->d->channel_map[3] = FF_EBUR128_LEFT_SURROUND; + st->d->channel_map[4] = FF_EBUR128_RIGHT_SURROUND; + } else { + for (i = 0; i < st->channels; ++i) { + switch (i) { + case 0: st->d->channel_map[i] = FF_EBUR128_LEFT; break; + case 1: st->d->channel_map[i] = FF_EBUR128_RIGHT; break; + case 2: st->d->channel_map[i] = FF_EBUR128_CENTER; break; + case 3: st->d->channel_map[i] = FF_EBUR128_UNUSED; break; + case 4: st->d->channel_map[i] = FF_EBUR128_LEFT_SURROUND; break; + case 5: st->d->channel_map[i] = FF_EBUR128_RIGHT_SURROUND; break; + default: st->d->channel_map[i] = FF_EBUR128_UNUSED; break; + } + } + } + return FF_EBUR128_SUCCESS; +} + +static inline void init_histogram(void) +{ + int i; + /* initialize static constants */ + relative_gate_factor = pow(10.0, relative_gate / 10.0); + minus_twenty_decibels = pow(10.0, -20.0 / 10.0); + histogram_energy_boundaries[0] = pow(10.0, (-70.0 + 0.691) / 10.0); + for (i = 0; i < 1000; ++i) { + histogram_energies[i] = pow(10.0, ((double) i / 10.0 - 69.95 + 0.691) / 10.0); + } + for (i = 1; i < 1001; ++i) { + histogram_energy_boundaries[i] = pow(10.0, ((double) i / 10.0 - 70.0 + 0.691) / 10.0); + } +} + +FFEBUR128State* ff_ebur128_init(unsigned int channels, + unsigned long samplerate, + int mode) { + int errcode; + FFEBUR128State* st; + unsigned int i; + size_t j; + + st = (FFEBUR128State*) av_malloc(sizeof(FFEBUR128State)); + CHECK_ERROR(!st, 0, exit) + st->d = (struct FFEBUR128StateInternal*) + av_malloc(sizeof(struct FFEBUR128StateInternal)); + CHECK_ERROR(!st->d, 0, free_state) + st->channels = channels; + errcode = ebur128_init_channel_map(st); + CHECK_ERROR(errcode, 0, free_internal) + + st->d->sample_peak = (double*) av_malloc_array(channels, sizeof(double)); + CHECK_ERROR(!st->d->sample_peak, 0, free_channel_map) + for (i = 0; i < channels; ++i) { + st->d->sample_peak[i] = 0.0; + } + + st->samplerate = samplerate; + st->d->samples_in_100ms = (st->samplerate + 5) / 10; + st->mode = mode; + if ((mode & FF_EBUR128_MODE_S) == FF_EBUR128_MODE_S) { + st->d->window = 3000; + } else if ((mode & FF_EBUR128_MODE_M) == FF_EBUR128_MODE_M) { + st->d->window = 400; + } else { + goto free_sample_peak; + } + st->d->audio_data_frames = st->samplerate * st->d->window / 1000; + if (st->d->audio_data_frames % st->d->samples_in_100ms) { + /* round up to multiple of samples_in_100ms */ + st->d->audio_data_frames = st->d->audio_data_frames + + st->d->samples_in_100ms + - (st->d->audio_data_frames % st->d->samples_in_100ms); + } + st->d->audio_data = (double*) av_malloc_array(st->d->audio_data_frames, + st->channels * sizeof(double)); + CHECK_ERROR(!st->d->audio_data, 0, free_sample_peak) + for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { + st->d->audio_data[i] = 0.0; + } + + ebur128_init_filter(st); + + st->d->block_energy_histogram = av_malloc(1000 * sizeof(unsigned long)); + CHECK_ERROR(!st->d->block_energy_histogram, 0, free_audio_data) + for (i = 0; i < 1000; ++i) { + st->d->block_energy_histogram[i] = 0; + } + st->d->short_term_block_energy_histogram = av_malloc(1000 * sizeof(unsigned long)); + CHECK_ERROR(!st->d->short_term_block_energy_histogram, 0, free_block_energy_histogram) + for (i = 0; i < 1000; ++i) { + st->d->short_term_block_energy_histogram[i] = 0; + } + st->d->short_term_frame_counter = 0; + + /* 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 */ + st->d->audio_data_index = 0; + + if (ff_thread_once(&histogram_init, &init_histogram) != 0) + goto free_short_term_block_energy_histogram; + + st->d->data_ptrs = av_malloc_array(channels, sizeof(void*)); + CHECK_ERROR(!st->d->data_ptrs, 0, free_short_term_block_energy_histogram); + + return st; + +free_short_term_block_energy_histogram: + av_free(st->d->short_term_block_energy_histogram); +free_block_energy_histogram: + av_free(st->d->block_energy_histogram); +free_audio_data: + av_free(st->d->audio_data); +free_sample_peak: + av_free(st->d->sample_peak); +free_channel_map: + av_free(st->d->channel_map); +free_internal: + av_free(st->d); +free_state: + av_free(st); +exit: + return NULL; +} + +void ff_ebur128_destroy(FFEBUR128State** st) { + av_free((*st)->d->block_energy_histogram); + av_free((*st)->d->short_term_block_energy_histogram); + av_free((*st)->d->audio_data); + av_free((*st)->d->channel_map); + av_free((*st)->d->sample_peak); + av_free((*st)->d->data_ptrs); + av_free((*st)->d); + av_free(*st); + *st = NULL; +} + +#define EBUR128_FILTER(type, min_scale, max_scale) \ +static void ebur128_filter_##type(FFEBUR128State* st, const type** srcs, \ + size_t src_index, size_t frames, \ + int stride) { \ + static double scaling_factor = -((double) min_scale) > (double) max_scale ? \ + -((double) min_scale) : (double) max_scale; \ + double* audio_data = st->d->audio_data + st->d->audio_data_index; \ + size_t i, c; \ + \ + if ((st->mode & FF_EBUR128_MODE_SAMPLE_PEAK) == FF_EBUR128_MODE_SAMPLE_PEAK) { \ + for (c = 0; c < st->channels; ++c) { \ + double max = 0.0; \ + for (i = 0; i < frames; ++i) { \ + type v = srcs[c][src_index + i * stride]; \ + if (v > max) { \ + max = v; \ + } else if (-v > max) { \ + max = -1.0 * v; \ + } \ + } \ + max /= scaling_factor; \ + if (max > st->d->sample_peak[c]) st->d->sample_peak[c] = max; \ + } \ + } \ + for (c = 0; c < st->channels; ++c) { \ + int ci = st->d->channel_map[c] - 1; \ + if (ci < 0) continue; \ + else if (ci == FF_EBUR128_DUAL_MONO - 1) ci = 0; /*dual mono */ \ + for (i = 0; i < frames; ++i) { \ + st->d->v[ci][0] = (double) (srcs[c][src_index + i * stride] / scaling_factor) \ + - st->d->a[1] * st->d->v[ci][1] \ + - st->d->a[2] * st->d->v[ci][2] \ + - st->d->a[3] * st->d->v[ci][3] \ + - st->d->a[4] * st->d->v[ci][4]; \ + audio_data[i * st->channels + c] = \ + st->d->b[0] * st->d->v[ci][0] \ + + st->d->b[1] * st->d->v[ci][1] \ + + st->d->b[2] * st->d->v[ci][2] \ + + st->d->b[3] * st->d->v[ci][3] \ + + st->d->b[4] * st->d->v[ci][4]; \ + st->d->v[ci][4] = st->d->v[ci][3]; \ + st->d->v[ci][3] = st->d->v[ci][2]; \ + st->d->v[ci][2] = st->d->v[ci][1]; \ + st->d->v[ci][1] = st->d->v[ci][0]; \ + } \ + st->d->v[ci][4] = fabs(st->d->v[ci][4]) < DBL_MIN ? 0.0 : st->d->v[ci][4]; \ + st->d->v[ci][3] = fabs(st->d->v[ci][3]) < DBL_MIN ? 0.0 : st->d->v[ci][3]; \ + st->d->v[ci][2] = fabs(st->d->v[ci][2]) < DBL_MIN ? 0.0 : st->d->v[ci][2]; \ + st->d->v[ci][1] = fabs(st->d->v[ci][1]) < DBL_MIN ? 0.0 : st->d->v[ci][1]; \ + } \ +} +EBUR128_FILTER(short, SHRT_MIN, SHRT_MAX) +EBUR128_FILTER(int, INT_MIN, INT_MAX) +EBUR128_FILTER(float, -1.0f, 1.0f) +EBUR128_FILTER(double, -1.0, 1.0) + +static double ebur128_energy_to_loudness(double energy) { + return 10 * (log(energy) / log(10.0)) - 0.691; +} + +static size_t find_histogram_index(double energy) { + size_t index_min = 0; + size_t index_max = 1000; + size_t index_mid; + + do { + index_mid = (index_min + index_max) / 2; + if (energy >= histogram_energy_boundaries[index_mid]) { + index_min = index_mid; + } else { + index_max = index_mid; + } + } while (index_max - index_min != 1); + + return index_min; +} + +static void ebur128_calc_gating_block(FFEBUR128State* st, size_t frames_per_block, + double* optional_output) { + size_t i, c; + double sum = 0.0; + double channel_sum; + for (c = 0; c < st->channels; ++c) { + if (st->d->channel_map[c] == FF_EBUR128_UNUSED) continue; + channel_sum = 0.0; + if (st->d->audio_data_index < frames_per_block * st->channels) { + for (i = 0; i < st->d->audio_data_index / st->channels; ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + for (i = st->d->audio_data_frames - + (frames_per_block - + st->d->audio_data_index / st->channels); + i < st->d->audio_data_frames; ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + } else { + for (i = st->d->audio_data_index / st->channels - frames_per_block; + i < st->d->audio_data_index / st->channels; + ++i) { + channel_sum += st->d->audio_data[i * st->channels + c] * + st->d->audio_data[i * st->channels + c]; + } + } + if (st->d->channel_map[c] == FF_EBUR128_Mp110 || + st->d->channel_map[c] == FF_EBUR128_Mm110 || + st->d->channel_map[c] == FF_EBUR128_Mp060 || + st->d->channel_map[c] == FF_EBUR128_Mm060 || + st->d->channel_map[c] == FF_EBUR128_Mp090 || + st->d->channel_map[c] == FF_EBUR128_Mm090) { + channel_sum *= 1.41; + } else if (st->d->channel_map[c] == FF_EBUR128_DUAL_MONO) { + channel_sum *= 2.0; + } + sum += channel_sum; + } + sum /= (double) frames_per_block; + if (optional_output) { + *optional_output = sum; + } else if (sum >= histogram_energy_boundaries[0]) { + ++st->d->block_energy_histogram[find_histogram_index(sum)]; + } +} + +int ff_ebur128_set_channel(FFEBUR128State* st, + unsigned int channel_number, + int value) { + if (channel_number >= st->channels) { + return 1; + } + if (value == FF_EBUR128_DUAL_MONO && + (st->channels != 1 || channel_number != 0)) { + return 1; + } + st->d->channel_map[channel_number] = value; + return 0; +} + +int ff_ebur128_change_parameters(FFEBUR128State* st, + unsigned int channels, + unsigned long samplerate) { + int errcode = FF_EBUR128_SUCCESS; + size_t j; + + if (channels == st->channels && + samplerate == st->samplerate) { + return FF_EBUR128_ERROR_NO_CHANGE; + } + av_free(st->d->audio_data); + st->d->audio_data = NULL; + + if (channels != st->channels) { + unsigned int i; + + av_free(st->d->channel_map); st->d->channel_map = NULL; + av_free(st->d->sample_peak); st->d->sample_peak = NULL; + st->channels = channels; + + errcode = ebur128_init_channel_map(st); + CHECK_ERROR(errcode, FF_EBUR128_ERROR_NOMEM, exit) + + st->d->sample_peak = (double*) av_malloc_array(channels, sizeof(double)); + CHECK_ERROR(!st->d->sample_peak, FF_EBUR128_ERROR_NOMEM, exit) + for (i = 0; i < channels; ++i) { + st->d->sample_peak[i] = 0.0; + } + } + if (samplerate != st->samplerate) { + st->samplerate = samplerate; + st->d->samples_in_100ms = (st->samplerate + 5) / 10; + ebur128_init_filter(st); + } + st->d->audio_data_frames = st->samplerate * st->d->window / 1000; + if (st->d->audio_data_frames % st->d->samples_in_100ms) { + /* round up to multiple of samples_in_100ms */ + st->d->audio_data_frames = st->d->audio_data_frames + + st->d->samples_in_100ms + - (st->d->audio_data_frames % st->d->samples_in_100ms); + } + st->d->audio_data = (double*) av_malloc_array(st->d->audio_data_frames, + st->channels * sizeof(double)); + CHECK_ERROR(!st->d->audio_data, FF_EBUR128_ERROR_NOMEM, exit) + for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { + st->d->audio_data[j] = 0.0; + } + + /* 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 */ + st->d->audio_data_index = 0; + /* reset short term frame counter */ + st->d->short_term_frame_counter = 0; + +exit: + return errcode; +} + +int ff_ebur128_set_max_window(FFEBUR128State* st, unsigned long window) +{ + int errcode = FF_EBUR128_SUCCESS; + size_t j; + + if ((st->mode & FF_EBUR128_MODE_S) == FF_EBUR128_MODE_S && window < 3000) { + window = 3000; + } else if ((st->mode & FF_EBUR128_MODE_M) == FF_EBUR128_MODE_M && window < 400) { + window = 400; + } + if (window == st->d->window) { + return FF_EBUR128_ERROR_NO_CHANGE; + } + + st->d->window = window; + av_free(st->d->audio_data); + st->d->audio_data = NULL; + st->d->audio_data_frames = st->samplerate * st->d->window / 1000; + if (st->d->audio_data_frames % st->d->samples_in_100ms) { + /* round up to multiple of samples_in_100ms */ + st->d->audio_data_frames = st->d->audio_data_frames + + st->d->samples_in_100ms + - (st->d->audio_data_frames % st->d->samples_in_100ms); + } + st->d->audio_data = (double*) av_malloc_array(st->d->audio_data_frames, + st->channels * sizeof(double)); + CHECK_ERROR(!st->d->audio_data, FF_EBUR128_ERROR_NOMEM, exit) + for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { + st->d->audio_data[j] = 0.0; + } + + /* 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 */ + st->d->audio_data_index = 0; + /* reset short term frame counter */ + st->d->short_term_frame_counter = 0; + +exit: + return errcode; +} + + +static int ebur128_energy_shortterm(FFEBUR128State* st, double* out); +#define FF_EBUR128_ADD_FRAMES(type) \ +void ff_ebur128_add_frames_##type(FFEBUR128State* st, const type** srcs, \ + size_t frames, int stride) { \ + size_t src_index = 0; \ + while (frames > 0) { \ + if (frames >= st->d->needed_frames) { \ + ebur128_filter_##type(st, srcs, src_index, st->d->needed_frames, stride);\ + src_index += st->d->needed_frames * stride; \ + frames -= st->d->needed_frames; \ + st->d->audio_data_index += st->d->needed_frames * st->channels; \ + /* calculate the new gating block */ \ + if ((st->mode & FF_EBUR128_MODE_I) == FF_EBUR128_MODE_I) { \ + ebur128_calc_gating_block(st, st->d->samples_in_100ms * 4, NULL); \ + } \ + if ((st->mode & FF_EBUR128_MODE_LRA) == FF_EBUR128_MODE_LRA) { \ + st->d->short_term_frame_counter += st->d->needed_frames; \ + if (st->d->short_term_frame_counter == st->d->samples_in_100ms * 30) { \ + double st_energy; \ + ebur128_energy_shortterm(st, &st_energy); \ + if (st_energy >= histogram_energy_boundaries[0]) { \ + ++st->d->short_term_block_energy_histogram[ \ + find_histogram_index(st_energy)]; \ + } \ + st->d->short_term_frame_counter = st->d->samples_in_100ms * 20; \ + } \ + } \ + /* 100ms are needed for all blocks besides the first one */ \ + st->d->needed_frames = st->d->samples_in_100ms; \ + /* reset audio_data_index when buffer full */ \ + if (st->d->audio_data_index == st->d->audio_data_frames * st->channels) {\ + st->d->audio_data_index = 0; \ + } \ + } else { \ + ebur128_filter_##type(st, srcs, src_index, frames, stride); \ + st->d->audio_data_index += frames * st->channels; \ + if ((st->mode & FF_EBUR128_MODE_LRA) == FF_EBUR128_MODE_LRA) { \ + st->d->short_term_frame_counter += frames; \ + } \ + st->d->needed_frames -= frames; \ + frames = 0; \ + } \ + } \ +} +FF_EBUR128_ADD_FRAMES(short) +FF_EBUR128_ADD_FRAMES(int) +FF_EBUR128_ADD_FRAMES(float) +FF_EBUR128_ADD_FRAMES(double) + +#define FF_EBUR128_ADD_IL_FRAMES(type) \ +void ff_ebur128_add_il_frames_##type(FFEBUR128State* st, const type* src, \ + size_t frames) { \ + int i; \ + const type **buf = (const type**)st->d->data_ptrs; \ + for (i = 0; i < st->channels; i++) \ + buf[i] = src + i; \ + ff_ebur128_add_frames_##type(st, buf, frames, st->channels); \ +} +FF_EBUR128_ADD_IL_FRAMES(short) +FF_EBUR128_ADD_IL_FRAMES(int) +FF_EBUR128_ADD_IL_FRAMES(float) +FF_EBUR128_ADD_IL_FRAMES(double) + +static int ebur128_calc_relative_threshold(FFEBUR128State* st, + size_t* above_thresh_counter, + double* relative_threshold) { + size_t i; + *relative_threshold = 0.0; + *above_thresh_counter = 0; + + for (i = 0; i < 1000; ++i) { + *relative_threshold += st->d->block_energy_histogram[i] * + histogram_energies[i]; + *above_thresh_counter += st->d->block_energy_histogram[i]; + } + + if (*above_thresh_counter != 0) { + *relative_threshold /= (double) *above_thresh_counter; + *relative_threshold *= relative_gate_factor; + } + + return FF_EBUR128_SUCCESS; +} + +static int ebur128_gated_loudness(FFEBUR128State** sts, size_t size, + double* out) { + double gated_loudness = 0.0; + double relative_threshold; + size_t above_thresh_counter; + size_t i, j, start_index; + + for (i = 0; i < size; i++) { + if (sts[i] && (sts[i]->mode & FF_EBUR128_MODE_I) != FF_EBUR128_MODE_I) { + return FF_EBUR128_ERROR_INVALID_MODE; + } + } + + for (i = 0; i < size; i++) { + if (!sts[i]) continue; + ebur128_calc_relative_threshold(sts[i], &above_thresh_counter, &relative_threshold); + } + if (!above_thresh_counter) { + *out = -HUGE_VAL; + return FF_EBUR128_SUCCESS; + } + + above_thresh_counter = 0; + if (relative_threshold < histogram_energy_boundaries[0]) { + start_index = 0; + } else { + start_index = find_histogram_index(relative_threshold); + if (relative_threshold > histogram_energies[start_index]) { + ++start_index; + } + } + for (i = 0; i < size; i++) { + if (!sts[i]) continue; + for (j = start_index; j < 1000; ++j) { + gated_loudness += sts[i]->d->block_energy_histogram[j] * + histogram_energies[j]; + above_thresh_counter += sts[i]->d->block_energy_histogram[j]; + } + } + if (!above_thresh_counter) { + *out = -HUGE_VAL; + return FF_EBUR128_SUCCESS; + } + gated_loudness /= (double) above_thresh_counter; + *out = ebur128_energy_to_loudness(gated_loudness); + return FF_EBUR128_SUCCESS; +} + +int ff_ebur128_relative_threshold(FFEBUR128State* st, double* out) { + double relative_threshold; + size_t above_thresh_counter; + + if (st && (st->mode & FF_EBUR128_MODE_I) != FF_EBUR128_MODE_I) + return FF_EBUR128_ERROR_INVALID_MODE; + + ebur128_calc_relative_threshold(st, &above_thresh_counter, &relative_threshold); + + if (!above_thresh_counter) { + *out = -70.0; + return FF_EBUR128_SUCCESS; + } + + *out = ebur128_energy_to_loudness(relative_threshold); + return FF_EBUR128_SUCCESS; +} + +int ff_ebur128_loudness_global(FFEBUR128State* st, double* out) { + return ebur128_gated_loudness(&st, 1, out); +} + +int ff_ebur128_loudness_global_multiple(FFEBUR128State** sts, size_t size, + double* out) { + return ebur128_gated_loudness(sts, size, out); +} + +static int ebur128_energy_in_interval(FFEBUR128State* st, + size_t interval_frames, + double* out) { + if (interval_frames > st->d->audio_data_frames) { + return FF_EBUR128_ERROR_INVALID_MODE; + } + ebur128_calc_gating_block(st, interval_frames, out); + return FF_EBUR128_SUCCESS; +} + +static int ebur128_energy_shortterm(FFEBUR128State* st, double* out) { + return ebur128_energy_in_interval(st, st->d->samples_in_100ms * 30, out); +} + +int ff_ebur128_loudness_momentary(FFEBUR128State* st, double* out) { + double energy; + int error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4, + &energy); + if (error) { + return error; + } else if (energy <= 0.0) { + *out = -HUGE_VAL; + return FF_EBUR128_SUCCESS; + } + *out = ebur128_energy_to_loudness(energy); + return FF_EBUR128_SUCCESS; +} + +int ff_ebur128_loudness_shortterm(FFEBUR128State* st, double* out) { + double energy; + int error = ebur128_energy_shortterm(st, &energy); + if (error) { + return error; + } else if (energy <= 0.0) { + *out = -HUGE_VAL; + return FF_EBUR128_SUCCESS; + } + *out = ebur128_energy_to_loudness(energy); + return FF_EBUR128_SUCCESS; +} + +int ff_ebur128_loudness_window(FFEBUR128State* st, + unsigned long window, + double* out) { + double energy; + size_t interval_frames = st->samplerate * window / 1000; + int error = ebur128_energy_in_interval(st, interval_frames, &energy); + if (error) { + return error; + } else if (energy <= 0.0) { + *out = -HUGE_VAL; + return FF_EBUR128_SUCCESS; + } + *out = ebur128_energy_to_loudness(energy); + return FF_EBUR128_SUCCESS; +} + +/* EBU - TECH 3342 */ +int ff_ebur128_loudness_range_multiple(FFEBUR128State** sts, size_t size, + double* out) { + size_t i, j; + size_t stl_size; + double stl_power, stl_integrated; + /* High and low percentile energy */ + double h_en, l_en; + + for (i = 0; i < size; ++i) { + if (sts[i]) { + if ((sts[i]->mode & FF_EBUR128_MODE_LRA) != FF_EBUR128_MODE_LRA) { + return FF_EBUR128_ERROR_INVALID_MODE; + } + } + } + + { + unsigned long hist[1000] = { 0 }; + size_t percentile_low, percentile_high; + size_t index; + + stl_size = 0; + stl_power = 0.0; + for (i = 0; i < size; ++i) { + if (!sts[i]) continue; + for (j = 0; j < 1000; ++j) { + hist[j] += sts[i]->d->short_term_block_energy_histogram[j]; + stl_size += sts[i]->d->short_term_block_energy_histogram[j]; + stl_power += sts[i]->d->short_term_block_energy_histogram[j] + * histogram_energies[j]; + } + } + if (!stl_size) { + *out = 0.0; + return FF_EBUR128_SUCCESS; + } + + stl_power /= stl_size; + stl_integrated = minus_twenty_decibels * stl_power; + + if (stl_integrated < histogram_energy_boundaries[0]) { + index = 0; + } else { + index = find_histogram_index(stl_integrated); + if (stl_integrated > histogram_energies[index]) { + ++index; + } + } + stl_size = 0; + for (j = index; j < 1000; ++j) { + stl_size += hist[j]; + } + if (!stl_size) { + *out = 0.0; + return FF_EBUR128_SUCCESS; + } + + percentile_low = (size_t) ((stl_size - 1) * 0.1 + 0.5); + percentile_high = (size_t) ((stl_size - 1) * 0.95 + 0.5); + + stl_size = 0; + j = index; + while (stl_size <= percentile_low) { + stl_size += hist[j++]; + } + l_en = histogram_energies[j - 1]; + while (stl_size <= percentile_high) { + stl_size += hist[j++]; + } + h_en = histogram_energies[j - 1]; + *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); + return FF_EBUR128_SUCCESS; + } +} + +int ff_ebur128_loudness_range(FFEBUR128State* st, double* out) { + return ff_ebur128_loudness_range_multiple(&st, 1, out); +} + +int ff_ebur128_sample_peak(FFEBUR128State* st, + unsigned int channel_number, + double* out) { + if ((st->mode & FF_EBUR128_MODE_SAMPLE_PEAK) != FF_EBUR128_MODE_SAMPLE_PEAK) { + return FF_EBUR128_ERROR_INVALID_MODE; + } else if (channel_number >= st->channels) { + return FF_EBUR128_ERROR_INVALID_CHANNEL_INDEX; + } + *out = st->d->sample_peak[channel_number]; + return FF_EBUR128_SUCCESS; +} + diff --git a/libavfilter/ebur128.h b/libavfilter/ebur128.h new file mode 100644 index 0000000..8541e05 --- /dev/null +++ b/libavfilter/ebur128.h @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2011 Jan Kokemüller + * + * 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 + * + * This file is based on libebur128 which is available at + * https://github.com/jiixyj/libebur128/ + * +*/ + +#ifndef AVFILTER_EBUR128_H +#define AVFILTER_EBUR128_H + +/** \file ebur128.h + * \brief libebur128 - a library for loudness measurement according to + * the EBU R128 standard. + */ + +#include /* for size_t */ + +/** \enum channel + * Use these values when setting the channel map with ebur128_set_channel(). + * See definitions in ITU R-REC-BS 1770-4 + */ +enum channel { + FF_EBUR128_UNUSED = 0, /**< unused channel (for example LFE channel) */ + FF_EBUR128_LEFT, + FF_EBUR128_Mp030 = 1, /**< itu M+030 */ + FF_EBUR128_RIGHT, + FF_EBUR128_Mm030 = 2, /**< itu M-030 */ + FF_EBUR128_CENTER, + FF_EBUR128_Mp000 = 3, /**< itu M+000 */ + FF_EBUR128_LEFT_SURROUND, + FF_EBUR128_Mp110 = 4, /**< itu M+110 */ + FF_EBUR128_RIGHT_SURROUND, + FF_EBUR128_Mm110 = 5, /**< itu M-110 */ + FF_EBUR128_DUAL_MONO, /**< a channel that is counted twice */ + FF_EBUR128_MpSC, /**< itu M+SC */ + FF_EBUR128_MmSC, /**< itu M-SC */ + FF_EBUR128_Mp060, /**< itu M+060 */ + FF_EBUR128_Mm060, /**< itu M-060 */ + FF_EBUR128_Mp090, /**< itu M+090 */ + FF_EBUR128_Mm090, /**< itu M-090 */ + FF_EBUR128_Mp135, /**< itu M+135 */ + FF_EBUR128_Mm135, /**< itu M-135 */ + FF_EBUR128_Mp180, /**< itu M+180 */ + FF_EBUR128_Up000, /**< itu U+000 */ + FF_EBUR128_Up030, /**< itu U+030 */ + FF_EBUR128_Um030, /**< itu U-030 */ + FF_EBUR128_Up045, /**< itu U+045 */ + FF_EBUR128_Um045, /**< itu U-030 */ + FF_EBUR128_Up090, /**< itu U+090 */ + FF_EBUR128_Um090, /**< itu U-090 */ + FF_EBUR128_Up110, /**< itu U+110 */ + FF_EBUR128_Um110, /**< itu U-110 */ + FF_EBUR128_Up135, /**< itu U+135 */ + FF_EBUR128_Um135, /**< itu U-135 */ + FF_EBUR128_Up180, /**< itu U+180 */ + FF_EBUR128_Tp000, /**< itu T+000 */ + FF_EBUR128_Bp000, /**< itu B+000 */ + FF_EBUR128_Bp045, /**< itu B+045 */ + FF_EBUR128_Bm045 /**< itu B-045 */ +}; + +/** \enum error + * Error return values. + */ +enum error { + FF_EBUR128_SUCCESS = 0, + FF_EBUR128_ERROR_NOMEM, + FF_EBUR128_ERROR_INVALID_MODE, + FF_EBUR128_ERROR_INVALID_CHANNEL_INDEX, + FF_EBUR128_ERROR_NO_CHANGE +}; + +/** \enum mode + * Use these values in ebur128_init (or'ed). Try to use the lowest possible + * modes that suit your needs, as performance will be better. + */ +enum mode { + /** can call ebur128_loudness_momentary */ + FF_EBUR128_MODE_M = (1 << 0), + /** can call ebur128_loudness_shortterm */ + FF_EBUR128_MODE_S = (1 << 1) | FF_EBUR128_MODE_M, + /** can call ebur128_loudness_global_* and ebur128_relative_threshold */ + FF_EBUR128_MODE_I = (1 << 2) | FF_EBUR128_MODE_M, + /** can call ebur128_loudness_range */ + FF_EBUR128_MODE_LRA = (1 << 3) | FF_EBUR128_MODE_S, + /** can call ebur128_sample_peak */ + FF_EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | FF_EBUR128_MODE_M, +}; + +/** forward declaration of FFEBUR128StateInternal */ +struct FFEBUR128StateInternal; + +/** \brief Contains information about the state of a loudness measurement. + * + * You should not need to modify this struct directly. + */ +typedef struct { + int mode; /**< The current mode. */ + unsigned int channels; /**< The number of channels. */ + unsigned long samplerate; /**< The sample rate. */ + struct FFEBUR128StateInternal* d; /**< Internal state. */ +} FFEBUR128State; + +/** \brief Initialize library state. + * + * @param channels the number of channels. + * @param samplerate the sample rate. + * @param mode see the mode enum for possible values. + * @return an initialized library state. + */ +FFEBUR128State* ff_ebur128_init(unsigned int channels, + unsigned long samplerate, + int mode); + +/** \brief Destroy library state. + * + * @param st pointer to a library state. + */ +void ff_ebur128_destroy(FFEBUR128State** st); + +/** \brief Set channel type. + * + * The default is: + * - 0 -> FF_EBUR128_LEFT + * - 1 -> FF_EBUR128_RIGHT + * - 2 -> FF_EBUR128_CENTER + * - 3 -> FF_EBUR128_UNUSED + * - 4 -> FF_EBUR128_LEFT_SURROUND + * - 5 -> FF_EBUR128_RIGHT_SURROUND + * + * @param st library state. + * @param channel_number zero based channel index. + * @param value channel type from the "channel" enum. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. + */ +int ff_ebur128_set_channel(FFEBUR128State* st, + unsigned int channel_number, + int value); + +/** \brief Change library parameters. + * + * Note that the channel map will be reset when setting a different number of + * channels. The current unfinished block will be lost. + * + * @param st library state. + * @param channels new number of channels. + * @param samplerate new sample rate. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_NOMEM on memory allocation error. The state will be + * invalid and must be destroyed. + * - FF_EBUR128_ERROR_NO_CHANGE if channels and sample rate were not changed. + */ +int ff_ebur128_change_parameters(FFEBUR128State* st, + unsigned int channels, + unsigned long samplerate); + +/** \brief Set the maximum window duration. + * + * Set the maximum duration that will be used for ebur128_window_loudness(). + * Note that this destroys the current content of the audio buffer. + * + * @param st library state. + * @param window duration of the window in ms. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_NOMEM on memory allocation error. The state will be + * invalid and must be destroyed. + * - FF_EBUR128_ERROR_NO_CHANGE if window duration not changed. + */ +int ff_ebur128_set_max_window(FFEBUR128State* st, unsigned long window); + +/** \brief Add frames to be processed. + * + * @param st library state. + * @param src array of source frames. Channels must be interleaved. + * @param frames number of frames. Not number of samples! + */ +void ff_ebur128_add_il_frames_short(FFEBUR128State* st, + const short* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_il_frames_int(FFEBUR128State* st, + const int* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_il_frames_float(FFEBUR128State* st, + const float* src, + size_t frames); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_il_frames_double(FFEBUR128State* st, + const double* src, + size_t frames); + +/** \brief Add frames to be processed. + * + * @param st library state. + * @param srcs array of source frame channel data pointers + * @param frames number of frames. Not number of samples! + * @param stride number of samples to skip to for the next sample of the same channel + */ +void ff_ebur128_add_frames_short(FFEBUR128State* st, + const short** srcs, + size_t frames, + int stride); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_frames_int(FFEBUR128State* st, + const int** srcs, + size_t frames, + int stride); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_frames_float(FFEBUR128State* st, + const float** srcs, + size_t frames, + int stride); +/** \brief See \ref ebur128_add_frames_short */ +void ff_ebur128_add_frames_double(FFEBUR128State* st, + const double** srcs, + size_t frames, + int stride); + +/** \brief Get global integrated loudness in LUFS. + * + * @param st library state. + * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_I" has not been set. + */ +int ff_ebur128_loudness_global(FFEBUR128State* st, double* out); +/** \brief Get global integrated loudness in LUFS across multiple instances. + * + * @param sts array of library states. + * @param size length of sts + * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_I" has not been set. + */ +int ff_ebur128_loudness_global_multiple(FFEBUR128State** sts, + size_t size, + double* out); + +/** \brief Get momentary loudness (last 400ms) in LUFS. + * + * @param st library state. + * @param out momentary loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - FF_EBUR128_SUCCESS on success. + */ +int ff_ebur128_loudness_momentary(FFEBUR128State* st, double* out); +/** \brief Get short-term loudness (last 3s) in LUFS. + * + * @param st library state. + * @param out short-term loudness in LUFS. -HUGE_VAL if result is negative + * infinity. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_S" has not been set. + */ +int ff_ebur128_loudness_shortterm(FFEBUR128State* st, double* out); + +/** \brief Get loudness of the specified window in LUFS. + * + * window must not be larger than the current window set in st. + * The current window can be changed by calling ebur128_set_max_window(). + * + * @param st library state. + * @param window window in ms to calculate loudness. + * @param out loudness in LUFS. -HUGE_VAL if result is negative infinity. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if window larger than current window in st. + */ +int ff_ebur128_loudness_window(FFEBUR128State* st, + unsigned long window, + double* out); + +/** \brief Get loudness range (LRA) of programme in LU. + * + * Calculates loudness range according to EBU 3342. + * + * @param st library state. + * @param out loudness range (LRA) in LU. Will not be changed in case of + * error. FF_EBUR128_ERROR_NOMEM or FF_EBUR128_ERROR_INVALID_MODE will be + * returned in this case. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_NOMEM in case of memory allocation error. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_LRA" has not been set. + */ +int ff_ebur128_loudness_range(FFEBUR128State* st, double* out); +/** \brief Get loudness range (LRA) in LU across multiple instances. + * + * Calculates loudness range according to EBU 3342. + * + * @param sts array of library states. + * @param size length of sts + * @param out loudness range (LRA) in LU. Will not be changed in case of + * error. FF_EBUR128_ERROR_NOMEM or FF_EBUR128_ERROR_INVALID_MODE will be + * returned in this case. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_NOMEM in case of memory allocation error. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_LRA" has not been set. + */ +int ff_ebur128_loudness_range_multiple(FFEBUR128State** sts, + size_t size, + double* out); + +/** \brief Get maximum sample peak of selected channel in float format. + * + * @param st library state + * @param channel_number channel to analyse + * @param out maximum sample peak in float format (1.0 is 0 dBFS) + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_SAMPLE_PEAK" has not + * been set. + * - FF_EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. + */ +int ff_ebur128_sample_peak(FFEBUR128State* st, + unsigned int channel_number, + double* out); + +/** \brief Get relative threshold in LUFS. + * + * @param st library state + * @param out relative threshold in LUFS. + * @return + * - FF_EBUR128_SUCCESS on success. + * - FF_EBUR128_ERROR_INVALID_MODE if mode "FF_EBUR128_MODE_I" has not + * been set. + */ +int ff_ebur128_relative_threshold(FFEBUR128State* st, double* out); + +#endif /* AVFILTER_EBUR128_H */