Merge branch 'v0.21.x' into master
This commit is contained in:
commit
7c8427b0f7
1
NEWS
1
NEWS
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue