From f81816b0cda2fb50b656cc02b663b0613c6eeb0b Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 7 Jul 2023 17:34:53 +0300 Subject: [PATCH] EBUR128Analysis: handle channel map Loudness measurement is channel-dependent. This perhaps matters most for mono audio. --- src/engine/ebur128analysis.cpp | 109 ++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/engine/ebur128analysis.cpp b/src/engine/ebur128analysis.cpp index 9246b98aa..527ad2071 100644 --- a/src/engine/ebur128analysis.cpp +++ b/src/engine/ebur128analysis.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,100 @@ struct GstSampleDeleter { void operator()(GstSample *s) const { gst_sample_unref(s); }; }; +// Remap from the channels defined in SMPTE 2036-2-2008 +// to the channels defined in ITU R-REC-BS 1770-4. +// +// As specified in ITU R-REC-BS 1770-4, TABLE 4, +// "Position-dependent weightings of the channels", +// if the nominal position of the speaker is: +// * |Elevation (phi)| < 30deg +// * and 60deg <= |Azimuth (theta)| <= 120° (i.e. +-90deg +- 30deg) +// ... then the channel is weighted at +1.5 dB. +// +// ITU R-REC-BS 1770-4 uppper and bottom position channels are at +-45deg, +// So only the middle-position channels are affected. +channel gst_channel_to_ebur_channel(GstAudioChannelPosition pos) { + + switch (pos) { + case GST_AUDIO_CHANNEL_POSITION_NONE: + case GST_AUDIO_CHANNEL_POSITION_INVALID: + case GST_AUDIO_CHANNEL_POSITION_LFE1: + case GST_AUDIO_CHANNEL_POSITION_LFE2: + return EBUR128_UNUSED; + + case GST_AUDIO_CHANNEL_POSITION_MONO: + return EBUR128_DUAL_MONO; // +6 dB + + case GST_AUDIO_CHANNEL_POSITION_SURROUND_LEFT: + return EBUR128_LEFT_SURROUND; + case GST_AUDIO_CHANNEL_POSITION_SURROUND_RIGHT: + return EBUR128_RIGHT_SURROUND; + + case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_CENTER: + return EBUR128_Bp000; + + case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_LEFT: + return EBUR128_Bm045; + case GST_AUDIO_CHANNEL_POSITION_BOTTOM_FRONT_RIGHT: + return EBUR128_Bp045; + + case GST_AUDIO_CHANNEL_POSITION_TOP_CENTER: + case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER: + return EBUR128_Up000; + + case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER: + return EBUR128_Up180; + + case GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_LEFT: + return EBUR128_Um090; + case GST_AUDIO_CHANNEL_POSITION_TOP_SIDE_RIGHT: + return EBUR128_Up090; + + case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT: + return EBUR128_Um045; + case GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT: + return EBUR128_Up045; + + case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT: + return EBUR128_Um135; + case GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT: + return EBUR128_Up135; + + case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER: + return EBUR128_Mp000; + + case GST_AUDIO_CHANNEL_POSITION_REAR_CENTER: + return EBUR128_Mp180; + + case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT: + return EBUR128_Mm030; + case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT: + return EBUR128_Mp030; + + case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: + return EBUR128_MmSC; + case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: + return EBUR128_MpSC; + + case GST_AUDIO_CHANNEL_POSITION_WIDE_LEFT: + return EBUR128_Mm060; // +1.5 dB + case GST_AUDIO_CHANNEL_POSITION_WIDE_RIGHT: + return EBUR128_Mp060; // +1.5 dB + + case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT: + return EBUR128_Mm090; // +1.5 dB + case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT: + return EBUR128_Mp090; // +1.5 dB + + case GST_AUDIO_CHANNEL_POSITION_REAR_LEFT: + return EBUR128_Mm110; // +1.5 dB + case GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT: + return EBUR128_Mp110; // +1.5 dB + } + Q_UNREACHABLE(); + +} + struct FrameFormat { enum class DataFormat { S16, @@ -63,6 +158,7 @@ struct FrameFormat { }; int channels; + guint64 channel_mask; int samplerate; DataFormat format; @@ -110,6 +206,7 @@ FrameFormat::FrameFormat(GstCaps *caps) { QString format_str = gst_structure_get_string(structure, "format"); gst_structure_get_int(structure, "rate", &samplerate); gst_structure_get_int(structure, "channels", &channels); + channel_mask = gst_value_get_bitmask(gst_structure_get_value(structure, "channel-mask")); if (format_str == "S16LE") { format = DataFormat::S16; @@ -132,7 +229,7 @@ FrameFormat::FrameFormat(GstCaps *caps) { bool operator==(const FrameFormat &lhs, const FrameFormat &rhs) { - return std::tie(lhs.channels, lhs.samplerate, lhs.format) == std::tie(rhs.channels, rhs.samplerate, rhs.format); + return std::tie(lhs.channels, lhs.channel_mask, lhs.samplerate, lhs.format) == std::tie(rhs.channels, rhs.channel_mask, rhs.samplerate, rhs.format); } bool operator!=(const FrameFormat &lhs, const FrameFormat &rhs) { @@ -146,6 +243,16 @@ EBUR128State::EBUR128State(FrameFormat dsc_) : dsc(dsc_) { st.reset(ebur128_init(dsc.channels, dsc.samplerate, EBUR128_MODE_I | EBUR128_MODE_LRA)); Q_ASSERT(st); + std::vector positions(dsc.channels, GST_AUDIO_CHANNEL_POSITION_INVALID); + gboolean success = gst_audio_channel_positions_from_mask(dsc.channels, dsc.channel_mask, positions.data()); + Q_ASSERT(success); + + // Propagate our knowledge of audio channel mapping to libebur128, doing so + // is important because loudness measurement is channel-position dependent. + for (int channel_number = 0; channel_number != dsc.channels; ++channel_number) { + ebur128_set_channel(&*st, channel_number, gst_channel_to_ebur_channel(positions[channel_number])); + } + }; void EBUR128State::AddFrames(const char *data, size_t size) {