diff --git a/NEWS b/NEWS index a14cbd699..9a32eec22 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ ver 0.23 (not yet released) - new command "getvol" ver 0.22.1 (not yet released) +* decoder + - opus: apply the OpusHead output gain even if there is no EBU R128 tag + - opus: fix track/album ReplayGain fallback * output - alsa: don't deadlock when the ALSA driver is buggy - jack, pulse: reduce the delay when stopping or pausing playback diff --git a/python/build/libs.py b/python/build/libs.py index 4886e8e19..7584caa16 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -377,8 +377,8 @@ ffmpeg = FfmpegProject( ) curl = AutotoolsProject( - 'http://curl.haxx.se/download/curl-7.72.0.tar.xz', - '0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713', + 'http://curl.haxx.se/download/curl-7.73.0.tar.xz', + '7c4c7ca4ea88abe00fea4740dcf81075c031b1d0bb23aff2d5efde20a3c2408a', 'lib/libcurl.a', [ '--disable-shared', '--enable-static', diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx index a0cf02ba7..0343e201c 100644 --- a/src/decoder/plugins/OpusDecoderPlugin.cxx +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -63,6 +63,17 @@ IsOpusTags(const ogg_packet &packet) noexcept return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; } +/** + * Convert an EBU R128 value to ReplayGain. + */ +constexpr float +EbuR128ToReplayGain(float ebu_r128) noexcept +{ + /* add 5dB to compensate for the different reference levels + between ReplayGain (89dB) and EBU R128 (-23 LUFS) */ + return ebu_r128 + 5; +} + bool mpd_opus_init([[maybe_unused]] const ConfigBlock &block) { @@ -76,10 +87,11 @@ class MPDOpusDecoder final : public OggDecoder { opus_int16 *output_buffer = nullptr; /** - * The output gain from the Opus header. Initialized by - * OnOggBeginning(). + * The output gain from the Opus header in dB that should be + * applied unconditionally, but is often used specifically for + * ReplayGain. Initialized by OnOggBeginning(). */ - signed output_gain; + float output_gain; /** * The pre-skip value from the Opus header. Initialized by @@ -111,6 +123,14 @@ class MPDOpusDecoder final : public OggDecoder { */ ogg_int64_t granulepos; + /** + * Was DecoderClient::SubmitReplayGain() called? We need to + * keep track of this, because it will usually be called by + * HandleTags(), but if there is no OpusTags packet, we need + * to submit our #output_gain value from the OpusHead. + */ + bool submitted_replay_gain = false; + public: explicit MPDOpusDecoder(DecoderReader &reader) :OggDecoder(reader) {} @@ -170,10 +190,14 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet) throw std::runtime_error("BOS packet must be OpusHead"); unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels, output_gain, pre_skip) || + signed output_gain_i; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels, output_gain_i, pre_skip) || !audio_valid_channel_count(channels)) throw std::runtime_error("Malformed BOS packet"); + /* convert Q7.8 fixed-point to float */ + output_gain = float(output_gain_i) / 256.0f; + granulepos = 0; skip = pre_skip; @@ -245,22 +269,22 @@ 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); if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h)) return; - client.SubmitReplayGain(&rgi); + if (rgi.IsDefined()) { + /* submit all valid EBU R128 values with output_gain + applied */ + if (rgi.track.IsDefined()) + rgi.track.gain += EbuR128ToReplayGain(output_gain); + if (rgi.album.IsDefined()) + rgi.album.gain += EbuR128ToReplayGain(output_gain); + client.SubmitReplayGain(&rgi); + submitted_replay_gain = true; + } if (!tag_builder.empty()) { Tag tag = tag_builder.Commit(); @@ -275,6 +299,18 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet) { assert(opus_decoder != nullptr); + if (!submitted_replay_gain) { + /* if we didn't see an OpusTags packet with EBU R128 + values, we still need to apply the output gain + value from the OpusHead packet; submit it as "track + gain" value */ + ReplayGainInfo rgi; + rgi.Clear(); + rgi.track.gain = EbuR128ToReplayGain(output_gain); + client.SubmitReplayGain(&rgi); + submitted_replay_gain = true; + } + int nframes = opus_decode(opus_decoder, (const unsigned char*)packet.packet, packet.bytes, diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx index c78586065..ee1d0cb22 100644 --- a/src/decoder/plugins/OpusTags.cxx +++ b/src/decoder/plugins/OpusTags.cxx @@ -61,7 +61,7 @@ ScanOneOpusTag(StringView name, StringView value, const char *endptr; const auto l = ParseInt64(value, &endptr, 10); if (endptr > value.begin() && endptr == value.end()) - rgi->track.gain += float(l) / 256.0f; + rgi->track.gain = float(l) / 256.0f; } else if (rgi != nullptr && name.EqualsIgnoreCase("R128_ALBUM_GAIN")) { /* R128_ALBUM_GAIN is a Q7.8 fixed point number in @@ -70,7 +70,7 @@ ScanOneOpusTag(StringView name, StringView value, const char *endptr; const auto l = ParseInt64(value, &endptr, 10); if (endptr > value.begin() && endptr == value.end()) - rgi->album.gain += float(l) / 256.0f; + rgi->album.gain = float(l) / 256.0f; } handler.OnPair(name, value);