[vlc-devel] [PATCHv2 5/9] audio_filter: add ebur128 "audio meter" plugin

Thomas Guillem thomas at gllm.fr
Wed Aug 19 21:25:59 CEST 2020


---
 configure.ac                      |   5 +
 modules/audio_filter/Makefile.am  |   9 +-
 modules/audio_filter/libebur128.c | 306 ++++++++++++++++++++++++++++++
 3 files changed, 319 insertions(+), 1 deletion(-)
 create mode 100644 modules/audio_filter/libebur128.c

diff --git a/configure.ac b/configure.ac
index 4f7a46349ba..7589b451137 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3926,6 +3926,11 @@ dnl  soxr module
 dnl
 PKG_ENABLE_MODULES_VLC([SOXR], [], [soxr >= 0.1.2], [SoX Resampler library], [auto])
 
+dnl
+dnl  libebur128 module
+dnl
+PKG_ENABLE_MODULES_VLC([EBUR128], [], [libebur128 >= 1.2.4], [EBU R 128 standard for loudness normalisation], [auto])
+
 dnl
 dnl  OS/2 KAI plugin
 dnl
diff --git a/modules/audio_filter/Makefile.am b/modules/audio_filter/Makefile.am
index 279be02040f..028b188bfe2 100644
--- a/modules/audio_filter/Makefile.am
+++ b/modules/audio_filter/Makefile.am
@@ -125,14 +125,21 @@ libsoxr_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(SOXR_CFLAGS)
 libsoxr_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(audio_filterdir)'
 libsoxr_plugin_la_LIBADD = $(SOXR_LIBS) $(LIBM)
 
+libebur128_plugin_la_SOURCES = audio_filter/libebur128.c
+libebur128_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(EBUR128_CFLAGS)
+libebur128_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(audio_filterdir)'
+libebur128_plugin_la_LIBADD = $(EBUR128_LIBS)
+
 audio_filter_LTLIBRARIES += \
 	$(LTLIBsamplerate) \
 	$(LTLIBsoxr) \
+	$(LTLIBebur128) \
 	libugly_resampler_plugin.la
 EXTRA_LTLIBRARIES += \
 	libbandlimited_resampler_plugin.la \
 	libsamplerate_plugin.la \
-	libsoxr_plugin.la
+	libsoxr_plugin.la \
+	libebur128_plugin.la
 
 libspeex_resampler_plugin_la_SOURCES = audio_filter/resampler/speex.c
 libspeex_resampler_plugin_la_CFLAGS = $(AM_CFLAGS) $(SPEEXDSP_CFLAGS)
diff --git a/modules/audio_filter/libebur128.c b/modules/audio_filter/libebur128.c
new file mode 100644
index 00000000000..dc7667753c9
--- /dev/null
+++ b/modules/audio_filter/libebur128.c
@@ -0,0 +1,306 @@
+/*****************************************************************************
+ * libebur128.c : libebur128 filter
+ *****************************************************************************
+ * Copyright © 2020 Videolabs
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_aout.h>
+#include <vlc_filter.h>
+#include <vlc_modules.h>
+#include <vlc_plugin.h>
+
+#include <ebur128.h>
+
+#define UPDATE_INTERVAL VLC_TICK_FROM_MS(400)
+
+#define CFG_PREFIX "ebur128-"
+
+struct filter_sys
+{
+    int mode;
+    ebur128_state *state;
+    vlc_tick_t last_update;
+    bool new_frames;
+};
+
+static ebur128_state *
+CreateEbuR128State(filter_t *filter, int mode)
+{
+    ebur128_state *state =
+        ebur128_init(filter->fmt_in.audio.i_channels, filter->fmt_in.audio.i_rate, mode);
+    if (state == NULL)
+        return NULL;
+
+    /* TODO: improve */
+    unsigned channels_set = 2;
+    int error;
+    if (filter->fmt_in.audio.i_physical_channels == AOUT_CHANS_5_1
+     || filter->fmt_in.audio.i_physical_channels == AOUT_CHANS_7_1)
+    {
+        error = ebur128_set_channel(state, 2, EBUR128_LEFT_SURROUND);
+        if (error != EBUR128_SUCCESS)
+            goto error;
+        ebur128_set_channel(state, 3, EBUR128_RIGHT_SURROUND);
+        if (error != EBUR128_SUCCESS)
+            goto error;
+        ebur128_set_channel(state, 4, EBUR128_CENTER);
+        if (error != EBUR128_SUCCESS)
+            goto error;
+
+        channels_set += 3;
+    }
+
+    for (unsigned i = channels_set; i < filter->fmt_in.audio.i_channels; ++i)
+    {
+        error = ebur128_set_channel(state, i, EBUR128_UNUSED);
+        if (error != EBUR128_SUCCESS)
+            goto error;
+    }
+
+    return state;
+error:
+    ebur128_destroy(&state);
+    return NULL;
+}
+
+static int
+SendLoudnessMeter(filter_t *filter)
+{
+    struct filter_sys *sys = filter->p_sys;
+
+    int error;
+    struct vlc_audio_loudness loudness = { 0, 0, 0, 0, 0 };
+
+    error = ebur128_loudness_momentary(sys->state, &loudness.loudness_momentary);
+    if (error != EBUR128_SUCCESS)
+        return error;
+
+    if ((sys->state->mode & EBUR128_MODE_S) == EBUR128_MODE_S)
+    {
+        error = ebur128_loudness_shortterm(sys->state, &loudness.loudness_shortterm);
+        if (error != EBUR128_SUCCESS)
+            return error;
+    }
+    if ((sys->state->mode & EBUR128_MODE_I) == EBUR128_MODE_I)
+    {
+        error = ebur128_loudness_global(sys->state, &loudness.loudness_integrated);
+        if (error != EBUR128_SUCCESS)
+            return error;
+
+    }
+    if ((sys->state->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA)
+    {
+        error = ebur128_loudness_range(sys->state, &loudness.loudness_range);
+        if (error != EBUR128_SUCCESS)
+            return error;
+    }
+    if ((sys->state->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK)
+    {
+        for (unsigned i = 0; i < filter->fmt_in.audio.i_channels; ++i)
+        {
+            double truepeak;
+            error = ebur128_true_peak(sys->state, 0, &truepeak);
+            if (error != EBUR128_SUCCESS)
+                return error;
+            if (truepeak > loudness.truepeak)
+                loudness.truepeak = truepeak;
+        }
+    }
+
+    filter_SendAudioLoudness(filter, &loudness);
+
+    return EBUR128_SUCCESS;
+}
+
+static block_t *
+Process(filter_t *filter, block_t *block)
+{
+    struct filter_sys *sys = filter->p_sys;
+    int error;
+    block_t *out = block;
+
+    if (unlikely(sys->state == NULL))
+    {
+        /* Can happen after a flush */
+        sys->state = CreateEbuR128State(filter, sys->mode);
+        if (sys->state == NULL)
+            return out;
+    }
+
+    switch (filter->fmt_in.i_codec)
+    {
+        case VLC_CODEC_U8:
+        {
+            /* Convert to S16N */
+            short *data_s16 = malloc(block->i_buffer * 2);
+            if (unlikely(data_s16 == NULL))
+                return out;
+
+            uint8_t *src = (uint8_t *)block->p_buffer;
+            short *dst = data_s16;
+            for (size_t i = block->i_buffer; i--;)
+                *dst++ = ((*src++) << 8) - 0x8000;
+
+            error = ebur128_add_frames_short(sys->state, data_s16,
+                                             block->i_nb_samples);
+            free(data_s16);
+            break;
+        }
+        case VLC_CODEC_S16N:
+            error = ebur128_add_frames_short(sys->state,
+                                             (const short *)block->p_buffer,
+                                             block->i_nb_samples);
+            break;
+        case VLC_CODEC_S32N:
+            error = ebur128_add_frames_int(sys->state,
+                                           (const int *) block->p_buffer,
+                                           block->i_nb_samples);
+            break;
+        case VLC_CODEC_FL32:
+            error = ebur128_add_frames_float(sys->state,
+                                             (const float *) block->p_buffer,
+                                             block->i_nb_samples);
+            break;
+        case VLC_CODEC_FL64:
+            error = ebur128_add_frames_double(sys->state,
+                                              (const double *) block->p_buffer,
+                                              block->i_nb_samples);
+            break;
+        default: vlc_assert_unreachable();
+    }
+
+    if (error != EBUR128_SUCCESS)
+    {
+        msg_Warn(filter, "ebur128_add_frames_*() failed: %d\n", error);
+        return out;
+    }
+
+    if (sys->last_update == VLC_TICK_INVALID)
+        sys->last_update = out->i_pts;
+
+    if (out->i_pts + out->i_length - sys->last_update >= UPDATE_INTERVAL)
+    {
+        error = SendLoudnessMeter(filter);
+        if (error == EBUR128_SUCCESS)
+        {
+            sys->last_update = out->i_pts + out->i_length;
+            sys->new_frames = false;
+        }
+    }
+    else
+        sys->new_frames = true;
+
+    return out;
+}
+
+static void
+Flush(filter_t *filter)
+{
+    struct filter_sys *sys = filter->p_sys;
+
+    if (sys->state != NULL)
+    {
+        if (sys->new_frames)
+        {
+            SendLoudnessMeter(filter);
+            sys->new_frames = false;
+        }
+        sys->last_update = VLC_TICK_INVALID;
+
+        ebur128_destroy(&sys->state);
+    }
+}
+
+static int Open(vlc_object_t *this)
+{
+    filter_t *filter = (filter_t *) this;
+
+    switch (filter->fmt_in.i_codec)
+    {
+        case VLC_CODEC_U8:
+        case VLC_CODEC_S16N:
+        case VLC_CODEC_S32N:
+        case VLC_CODEC_FL32:
+        case VLC_CODEC_FL64:
+            break;
+        default:
+            return VLC_EGENERIC;
+    }
+
+    static const char *const options[] = {
+        "mode", NULL
+    };
+    config_ChainParse(filter, CFG_PREFIX, options, filter->p_cfg);
+
+    struct filter_sys *sys = malloc(sizeof(*sys));
+    if (sys == NULL)
+        return VLC_ENOMEM;
+
+    int plugin_mode = var_InheritInteger(filter, CFG_PREFIX "mode");
+    sys->mode = EBUR128_MODE_M;
+    switch (plugin_mode)
+    {
+        case 4: sys->mode |= EBUR128_MODE_TRUE_PEAK;/* fall-through */
+        case 3: sys->mode |= EBUR128_MODE_LRA;      /* fall-through */
+        case 2: sys->mode |= EBUR128_MODE_I;        /* fall-through */
+        case 1: sys->mode |= EBUR128_MODE_S;        /* fall-through */
+        case 0: break;
+        default: vlc_assert_unreachable();
+    }
+
+
+    sys->last_update = VLC_TICK_INVALID;
+    sys->new_frames = false;
+    sys->state = CreateEbuR128State(filter, sys->mode);
+    if (sys->state == NULL)
+    {
+        free(sys);
+        return VLC_EGENERIC;
+    }
+
+    filter->p_sys = sys;
+    filter->fmt_out.audio = filter->fmt_in.audio;
+    filter->pf_audio_filter = Process;
+    filter->pf_flush = Flush;
+    return VLC_SUCCESS;
+}
+
+static void
+Close(vlc_object_t *this)
+{
+    filter_t *filter = (filter_t*) this;
+    struct filter_sys *sys = filter->p_sys;
+
+    if (sys->state != NULL)
+        ebur128_destroy(&sys->state);
+    free(filter->p_sys);
+}
+
+vlc_module_begin()
+    set_shortname("EBU R 128")
+    set_description("EBU R128 standard for loudness normalisation")
+    set_category(CAT_AUDIO)
+    set_subcategory(SUBCAT_AUDIO_AFILTER)
+    add_integer_with_range(CFG_PREFIX "mode", 0, 0, 4, NULL, NULL, false)
+    set_capability("audio meter", 0)
+    set_callbacks(Open, Close)
+vlc_module_end()
-- 
2.28.0



More information about the vlc-devel mailing list