[vlc-devel] [RFC PATCH 3/7] audio_filter: add ebur128 "audio meter" plugin
Thomas Guillem
thomas at gllm.fr
Mon Aug 17 09:48:05 CEST 2020
On Mon, Aug 17, 2020, at 08:48, Steve Lhomme wrote:
> On 2020-08-14 15:18, Thomas Guillem wrote:
> > ---
> > configure.ac | 5 +
> > modules/audio_filter/Makefile.am | 9 +-
> > modules/audio_filter/libebur128.c | 290 ++++++++++++++++++++++++++++++
> > 3 files changed, 303 insertions(+), 1 deletion(-)
> > create mode 100644 modules/audio_filter/libebur128.c
> >
> > diff --git a/configure.ac b/configure.ac
> > index 149c7c926b4..862546e49f3 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -3919,6 +3919,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..bd5464dc6d1
> > --- /dev/null
> > +++ b/modules/audio_filter/libebur128.c
> > @@ -0,0 +1,290 @@
> > +/*****************************************************************************
> > + * 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)
> > +
> > +struct filter_sys
> > +{
> > + ebur128_state *state;
> > + vlc_tick_t last_update;
> > + bool new_frames;
> > +};
> > +
> > +static ebur128_state *
> > +CreateEbuR128State(filter_t *filter)
> > +{
> > + int mode = var_InheritBool(filter, "ebur128-fullmeter")
> > + ? EBUR128_MODE_TRUE_PEAK|EBUR128_MODE_LRA : EBUR128_MODE_M;
> > +
> > + 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_meter meter = { 0, 0, 0, 0, 0 };
> > +
> > + error = ebur128_loudness_momentary(sys->state, &meter.loudness_momentary);
> > + if (error != EBUR128_SUCCESS)
> > + return error;
> > +
> > + if ((sys->state->mode & EBUR128_MODE_S) == EBUR128_MODE_S)
> > + {
> > + error = ebur128_loudness_shortterm(sys->state, &meter.loudness_shortterm);
> > + if (error != EBUR128_SUCCESS)
> > + return error;
> > + }
> > + if ((sys->state->mode & EBUR128_MODE_I) == EBUR128_MODE_I)
> > + {
> > + error = ebur128_loudness_global(sys->state, &meter.loudness_integrated);
> > + if (error != EBUR128_SUCCESS)
> > + return error;
> > +
> > + }
> > + if ((sys->state->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA)
> > + {
> > + error = ebur128_loudness_range(sys->state, &meter.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 > meter.truepeak)
> > + meter.truepeak = truepeak;
> > + }
> > + }
> > +
> > + var_SetAddress(vlc_object_parent(VLC_OBJECT(filter)), "loudness-meter", &meter);
>
> I'm not a big fan of this. I'd prefer timed data attached to the audio
> blocks (or in a separate ES). This would provide more or less accurate
> values with each audio block. It could be used in the vout to blend in
> the UI. Here if there's a delay between the decoder and the playback the
> display also has this delay.
I can add a filter event for this type of plugin.
There is a big issue in this patch set, I didn't handle the date of loudness events. The core will need to use the value returned by aout_TImeGet() and add this play_date to the loudness meter structure. This date will always be in the future.
So, a UI receiving this event will have to spawn a timer, triggering at this date to change the UI.
>
> > +
> > + 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);
> > + if (sys->state == NULL)
> > + return out;
> > + }
> > +
> > + switch (filter->fmt_in.i_codec)
> > + {
> > + case VLC_CODEC_U8:
> > + {
> > + /* Convert to S16N */
> > + block_t *block_s16 = block_Alloc(block->i_buffer * 2);
> > + if (unlikely(block_s16 == NULL))
> > + return out;
> > +
> > + block_CopyProperties(block_s16, block);
> > + uint8_t *src = (uint8_t *)block->p_buffer;
> > + int16_t *dst = (int16_t *)block_s16->p_buffer;
> > + for (size_t i = block->i_buffer; i--;)
> > + *dst++ = ((*src++) << 8) - 0x8000;
> > +
> > + block = block_s16;
> > + }
> > + /* Fallthrough */
> > + 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;
> > + }
> > +
> > + if (var_Type(vlc_object_parent(this), "loudness-meter") != VLC_VAR_ADDRESS)
> > + return VLC_EGENERIC;
> > +
> > + struct filter_sys *sys = malloc(sizeof(*sys));
> > + if (sys == NULL)
> > + return VLC_ENOMEM;
> > +
> > + sys->last_update = VLC_TICK_INVALID;
> > + sys->new_frames = false;
> > + sys->state = CreateEbuR128State(filter);
> > + 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_bool("ebur128-fullmeter", false, NULL, NULL, false)
> > + set_capability("audio meter", 0)
> > + set_callbacks(Open, Close)
> > +vlc_module_end()
> > --
> > 2.28.0
> >
> > _______________________________________________
> > vlc-devel mailing list
> > To unsubscribe or modify your subscription options:
> > https://mailman.videolan.org/listinfo/vlc-devel
> >
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
More information about the vlc-devel
mailing list