summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2020-09-21 11:37:30 +0200
committerMax Kellermann <max@musicpd.org>2020-09-21 11:37:50 +0200
commit7c8427b0f7d34bc7078736ce3fa7142cce5cc0e6 (patch)
tree1e09a2fa0e2a578e523e2d3d1f4686a73e1eb0f9
parent7552f70c8db7874e855626f1b24729fcc788e6eb (diff)
parentb72801abf3bba84565bb040215071a21182e698c (diff)
Merge branch 'v0.21.x' into master
-rw-r--r--NEWS1
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.cxx24
-rw-r--r--src/decoder/plugins/OpusHead.cxx4
-rw-r--r--src/decoder/plugins/OpusHead.hxx2
-rw-r--r--src/decoder/plugins/OpusTags.cxx4
-rw-r--r--src/pcm/Mix.cxx1
-rw-r--r--src/util/ByteOrder.hxx11
7 files changed, 39 insertions, 8 deletions
diff --git a/NEWS b/NEWS
index 19df7e53e..da37166ec 100644
--- a/NEWS
+++ b/NEWS
@@ -54,6 +54,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 ba5a2bff4..a0cf02ba7 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -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,
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 596ef186a..eb7edf121 100644
--- a/src/decoder/plugins/OpusHead.cxx
+++ b/src/decoder/plugins/OpusHead.cxx
@@ -33,12 +33,14 @@ 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 auto *h = (const OpusHead *)data;
if (size < 19 || (h->version & 0xf0) != 0)
return false;
+ output_gain_r = FromLE16S(h->output_gain);
+
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 d140cee21..cbacf0c48 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 ee1d0cb22..c78586065 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);
diff --git a/src/pcm/Mix.cxx b/src/pcm/Mix.cxx
index ed2b0f8db..9126f60f6 100644
--- a/src/pcm/Mix.cxx
+++ b/src/pcm/Mix.cxx
@@ -27,6 +27,7 @@
#include "Dither.cxx" // including the .cxx file to get inlined templates
#include <cassert>
+#include <cmath>
template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::value_type
diff --git a/src/util/ByteOrder.hxx b/src/util/ByteOrder.hxx
index 2c145a262..48222854e 100644
--- a/src/util/ByteOrder.hxx
+++ b/src/util/ByteOrder.hxx
@@ -243,4 +243,15 @@ ToLE64(uint64_t value) noexcept
return IsLittleEndian() ? value : ByteSwap64(value);
}
+/**
+ * Converts a 16 bit integer from little endian to the host byte order
+ * and returns it as a signed integer.
+ */
+constexpr int16_t
+FromLE16S(uint16_t value) noexcept
+{
+ /* assuming two's complement representation */
+ return static_cast<int16_t>(FromLE16(value));
+}
+
#endif