From 23d5a2b8620cea69958d087fc7e13fe1e5adb83d Mon Sep 17 00:00:00 2001 From: Desuwa Date: Fri, 18 Sep 2020 03:46:37 -0700 Subject: [PATCH] Support opus header gain tags and match opus playback volume to other tracks when ReplayGain is enabled. --- NEWS | 1 + src/decoder/plugins/OpusDecoderPlugin.cxx | 24 +++++++++++++++++++---- src/decoder/plugins/OpusHead.cxx | 5 ++++- src/decoder/plugins/OpusHead.hxx | 2 +- src/decoder/plugins/OpusTags.cxx | 4 ++-- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index aeb7eb5bf..9ece34870 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ ver 0.21.26 (not yet released) * decoder - ffmpeg: remove "rtsp://" from the list of supported protocols - ffmpeg: add "hls+http://" to the list of supported protocols + - opus: support the gain value from the Opus header - sndfile: fix lost samples at end of file * fix "single" mode bug after resuming playback * the default log_level is "default", not "info" diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx index 5284ac9a1..b1ed9c4e9 100644 --- a/src/decoder/plugins/OpusDecoderPlugin.cxx +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -75,6 +75,12 @@ class MPDOpusDecoder final : public OggDecoder { OpusDecoder *opus_decoder = nullptr; opus_int16 *output_buffer = nullptr; + /** + * The output gain from the Opus header. Initialized by + * OnOggBeginning(). + */ + signed output_gain; + /** * The pre-skip value from the Opus header. Initialized by * OnOggBeginning(). @@ -164,7 +170,7 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet) throw std::runtime_error("BOS packet must be OpusHead"); unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels, pre_skip) || + if (!ScanOpusHeader(packet.packet, packet.bytes, channels, output_gain, pre_skip) || !audio_valid_channel_count(channels)) throw std::runtime_error("Malformed BOS packet"); @@ -239,6 +245,15 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet) ReplayGainInfo rgi; rgi.Clear(); + /** + * Output gain is a Q7.8 fixed point number in dB that should be, + * applied unconditionally, but is often used specifically for + * ReplayGain. Add 5dB to compensate for the different + * reference levels between ReplayGain (89dB) and EBU R128 (-23 LUFS). + */ + rgi.track.gain = float(output_gain) / 256.0f + 5; + rgi.album.gain = float(output_gain) / 256.0f + 5; + TagBuilder tag_builder; AddTagHandler h(tag_builder); @@ -384,14 +399,14 @@ mpd_opus_stream_decode(DecoderClient &client, static bool ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream, - unsigned &channels, unsigned &pre_skip) + unsigned &channels, signed &output_gain, unsigned &pre_skip) { ogg_packet packet; return OggReadPacket(sync, stream, packet) && packet.b_o_s && IsOpusHead(packet) && ScanOpusHeader(packet.packet, packet.bytes, channels, - pre_skip) && + output_gain, pre_skip) && audio_valid_channel_count(channels); } @@ -436,7 +451,8 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) OggStreamState os(first_page); unsigned channels, pre_skip; - if (!ReadAndParseOpusHead(oy, os, channels, pre_skip) || + signed output_gain; + if (!ReadAndParseOpusHead(oy, os, channels, output_gain, pre_skip) || !ReadAndVisitOpusTags(oy, os, handler)) return false; diff --git a/src/decoder/plugins/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx index 30d959f04..7a1c766a9 100644 --- a/src/decoder/plugins/OpusHead.cxx +++ b/src/decoder/plugins/OpusHead.cxx @@ -33,12 +33,15 @@ struct OpusHead { bool ScanOpusHeader(const void *data, size_t size, unsigned &channels_r, - unsigned &pre_skip_r) + signed &output_gain_r, unsigned &pre_skip_r) { const OpusHead *h = (const OpusHead *)data; if (size < 19 || (h->version & 0xf0) != 0) return false; + uint16_t gain_bits = FromLE16(h->output_gain); + output_gain_r = (gain_bits & 0x8000) ? gain_bits - 0x10000 : gain_bits; + channels_r = h->channels; pre_skip_r = FromLE16(h->pre_skip); return true; diff --git a/src/decoder/plugins/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx index 39b5f832a..4fada6459 100644 --- a/src/decoder/plugins/OpusHead.hxx +++ b/src/decoder/plugins/OpusHead.hxx @@ -24,6 +24,6 @@ bool ScanOpusHeader(const void *data, size_t size, unsigned &channels_r, - unsigned &pre_skip_r); + signed &output_gain_r, unsigned &pre_skip_r); #endif diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx index 70e452860..3133dcc4d 100644 --- a/src/decoder/plugins/OpusTags.cxx +++ b/src/decoder/plugins/OpusTags.cxx @@ -53,7 +53,7 @@ ScanOneOpusTag(const char *name, const char *value, char *endptr; long l = strtol(value, &endptr, 10); if (endptr > value && *endptr == 0) - rgi->track.gain = float(l) / 256.0f; + rgi->track.gain += float(l) / 256.0f; } else if (rgi != nullptr && StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) { /* R128_ALBUM_GAIN is a Q7.8 fixed point number in @@ -62,7 +62,7 @@ ScanOneOpusTag(const char *name, const char *value, char *endptr; long l = strtol(value, &endptr, 10); if (endptr > value && *endptr == 0) - rgi->album.gain = float(l) / 256.0f; + rgi->album.gain += float(l) / 256.0f; } handler.OnPair(name, value);