Merge branch 'v0.21.x' into master

This commit is contained in:
Max Kellermann 2020-09-21 11:37:30 +02:00
commit 7c8427b0f7
7 changed files with 39 additions and 8 deletions

1
NEWS
View File

@ -54,6 +54,7 @@ ver 0.21.26 (not yet released)
* decoder * decoder
- ffmpeg: remove "rtsp://" from the list of supported protocols - ffmpeg: remove "rtsp://" from the list of supported protocols
- ffmpeg: add "hls+http://" to 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 - sndfile: fix lost samples at end of file
* fix "single" mode bug after resuming playback * fix "single" mode bug after resuming playback
* the default log_level is "default", not "info" * the default log_level is "default", not "info"

View File

@ -75,6 +75,12 @@ class MPDOpusDecoder final : public OggDecoder {
OpusDecoder *opus_decoder = nullptr; OpusDecoder *opus_decoder = nullptr;
opus_int16 *output_buffer = 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 * The pre-skip value from the Opus header. Initialized by
* OnOggBeginning(). * OnOggBeginning().
@ -164,7 +170,7 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
throw std::runtime_error("BOS packet must be OpusHead"); throw std::runtime_error("BOS packet must be OpusHead");
unsigned channels; 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)) !audio_valid_channel_count(channels))
throw std::runtime_error("Malformed BOS packet"); throw std::runtime_error("Malformed BOS packet");
@ -239,6 +245,15 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
ReplayGainInfo rgi; ReplayGainInfo rgi;
rgi.Clear(); 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; TagBuilder tag_builder;
AddTagHandler h(tag_builder); AddTagHandler h(tag_builder);
@ -384,14 +399,14 @@ mpd_opus_stream_decode(DecoderClient &client,
bool bool
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream, ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
unsigned &channels, unsigned &pre_skip) unsigned &channels, signed &output_gain, unsigned &pre_skip)
{ {
ogg_packet packet; ogg_packet packet;
return OggReadPacket(sync, stream, packet) && packet.b_o_s && return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
IsOpusHead(packet) && IsOpusHead(packet) &&
ScanOpusHeader(packet.packet, packet.bytes, channels, ScanOpusHeader(packet.packet, packet.bytes, channels,
pre_skip) && output_gain, pre_skip) &&
audio_valid_channel_count(channels); audio_valid_channel_count(channels);
} }
@ -436,7 +451,8 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
OggStreamState os(first_page); OggStreamState os(first_page);
unsigned channels, pre_skip; 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)) !ReadAndVisitOpusTags(oy, os, handler))
return false; return false;

View File

@ -33,12 +33,14 @@ struct OpusHead {
bool bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r, 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; const auto *h = (const OpusHead *)data;
if (size < 19 || (h->version & 0xf0) != 0) if (size < 19 || (h->version & 0xf0) != 0)
return false; return false;
output_gain_r = FromLE16S(h->output_gain);
channels_r = h->channels; channels_r = h->channels;
pre_skip_r = FromLE16(h->pre_skip); pre_skip_r = FromLE16(h->pre_skip);
return true; return true;

View File

@ -24,6 +24,6 @@
bool bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r, ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
unsigned &pre_skip_r); signed &output_gain_r, unsigned &pre_skip_r);
#endif #endif

View File

@ -61,7 +61,7 @@ ScanOneOpusTag(StringView name, StringView value,
const char *endptr; const char *endptr;
const auto l = ParseInt64(value, &endptr, 10); const auto l = ParseInt64(value, &endptr, 10);
if (endptr > value.begin() && endptr == value.end()) 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 && } else if (rgi != nullptr &&
name.EqualsIgnoreCase("R128_ALBUM_GAIN")) { name.EqualsIgnoreCase("R128_ALBUM_GAIN")) {
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in /* R128_ALBUM_GAIN is a Q7.8 fixed point number in
@ -70,7 +70,7 @@ ScanOneOpusTag(StringView name, StringView value,
const char *endptr; const char *endptr;
const auto l = ParseInt64(value, &endptr, 10); const auto l = ParseInt64(value, &endptr, 10);
if (endptr > value.begin() && endptr == value.end()) 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); handler.OnPair(name, value);

View File

@ -27,6 +27,7 @@
#include "Dither.cxx" // including the .cxx file to get inlined templates #include "Dither.cxx" // including the .cxx file to get inlined templates
#include <cassert> #include <cassert>
#include <cmath>
template<SampleFormat F, class Traits=SampleTraits<F>> template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::value_type static typename Traits::value_type

View File

@ -243,4 +243,15 @@ ToLE64(uint64_t value) noexcept
return IsLittleEndian() ? value : ByteSwap64(value); 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 #endif