Commit 23d5a2b8 authored by Desuwa's avatar Desuwa Committed by Max Kellermann

Support opus header gain tags and match opus playback volume to other tracks…

Support opus header gain tags and match opus playback volume to other tracks when ReplayGain is enabled.
parent 77153111
......@@ -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"
......
......@@ -76,6 +76,12 @@ class MPDOpusDecoder final : public OggDecoder {
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;
......
......@@ -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;
......
......@@ -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
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment