Merge branch 'v0.22.x' into master
This commit is contained in:
commit
4f0e0af319
3
NEWS
3
NEWS
|
@ -3,6 +3,9 @@ ver 0.23 (not yet released)
|
||||||
- new command "getvol"
|
- new command "getvol"
|
||||||
|
|
||||||
ver 0.22.1 (not yet released)
|
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
|
* output
|
||||||
- alsa: don't deadlock when the ALSA driver is buggy
|
- alsa: don't deadlock when the ALSA driver is buggy
|
||||||
- jack, pulse: reduce the delay when stopping or pausing playback
|
- jack, pulse: reduce the delay when stopping or pausing playback
|
||||||
|
|
|
@ -377,8 +377,8 @@ ffmpeg = FfmpegProject(
|
||||||
)
|
)
|
||||||
|
|
||||||
curl = AutotoolsProject(
|
curl = AutotoolsProject(
|
||||||
'http://curl.haxx.se/download/curl-7.72.0.tar.xz',
|
'http://curl.haxx.se/download/curl-7.73.0.tar.xz',
|
||||||
'0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713',
|
'7c4c7ca4ea88abe00fea4740dcf81075c031b1d0bb23aff2d5efde20a3c2408a',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
|
|
|
@ -63,6 +63,17 @@ IsOpusTags(const ogg_packet &packet) noexcept
|
||||||
return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
|
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
|
bool
|
||||||
mpd_opus_init([[maybe_unused]] const ConfigBlock &block)
|
mpd_opus_init([[maybe_unused]] const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
|
@ -76,10 +87,11 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||||
opus_int16 *output_buffer = nullptr;
|
opus_int16 *output_buffer = nullptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The output gain from the Opus header. Initialized by
|
* The output gain from the Opus header in dB that should be
|
||||||
* OnOggBeginning().
|
* 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
|
* The pre-skip value from the Opus header. Initialized by
|
||||||
|
@ -111,6 +123,14 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||||
*/
|
*/
|
||||||
ogg_int64_t granulepos;
|
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:
|
public:
|
||||||
explicit MPDOpusDecoder(DecoderReader &reader)
|
explicit MPDOpusDecoder(DecoderReader &reader)
|
||||||
:OggDecoder(reader) {}
|
:OggDecoder(reader) {}
|
||||||
|
@ -170,10 +190,14 @@ 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, 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))
|
!audio_valid_channel_count(channels))
|
||||||
throw std::runtime_error("Malformed BOS packet");
|
throw std::runtime_error("Malformed BOS packet");
|
||||||
|
|
||||||
|
/* convert Q7.8 fixed-point to float */
|
||||||
|
output_gain = float(output_gain_i) / 256.0f;
|
||||||
|
|
||||||
granulepos = 0;
|
granulepos = 0;
|
||||||
skip = pre_skip;
|
skip = pre_skip;
|
||||||
|
|
||||||
|
@ -245,22 +269,22 @@ 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);
|
||||||
|
|
||||||
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
|
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
|
||||||
return;
|
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()) {
|
if (!tag_builder.empty()) {
|
||||||
Tag tag = tag_builder.Commit();
|
Tag tag = tag_builder.Commit();
|
||||||
|
@ -275,6 +299,18 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||||
{
|
{
|
||||||
assert(opus_decoder != nullptr);
|
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,
|
int nframes = opus_decode(opus_decoder,
|
||||||
(const unsigned char*)packet.packet,
|
(const unsigned char*)packet.packet,
|
||||||
packet.bytes,
|
packet.bytes,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue