decoder/opus: apply pre-skip (RFC7845 4.2)
Fixes the first part of https://github.com/MusicPlayerDaemon/MPD/issues/867
This commit is contained in:
parent
a99b4abae8
commit
760238fe16
2
NEWS
2
NEWS
@ -4,6 +4,8 @@ ver 0.21.25 (not yet released)
|
|||||||
* input
|
* input
|
||||||
- file: detect premature end of file
|
- file: detect premature end of file
|
||||||
- smbclient: don't send credentials to MPD clients
|
- smbclient: don't send credentials to MPD clients
|
||||||
|
* decoder
|
||||||
|
- opus: apply pre-skip
|
||||||
* output
|
* output
|
||||||
- osx: improve sample rate selection
|
- osx: improve sample rate selection
|
||||||
* Windows/Android:
|
* Windows/Android:
|
||||||
|
@ -75,6 +75,8 @@ class MPDOpusDecoder final : public OggDecoder {
|
|||||||
OpusDecoder *opus_decoder = nullptr;
|
OpusDecoder *opus_decoder = nullptr;
|
||||||
opus_int16 *output_buffer = nullptr;
|
opus_int16 *output_buffer = nullptr;
|
||||||
|
|
||||||
|
unsigned pre_skip, skip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If non-zero, then a previous Opus stream has been found
|
* If non-zero, then a previous Opus stream has been found
|
||||||
* already with this number of channels. If opus_decoder is
|
* already with this number of channels. If opus_decoder is
|
||||||
@ -136,11 +138,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
|||||||
if (opus_decoder != nullptr || !IsOpusHead(packet))
|
if (opus_decoder != nullptr || !IsOpusHead(packet))
|
||||||
throw std::runtime_error("BOS packet must be OpusHead");
|
throw std::runtime_error("BOS packet must be OpusHead");
|
||||||
|
|
||||||
unsigned channels, pre_skip;
|
unsigned channels;
|
||||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, pre_skip) ||
|
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, 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");
|
||||||
|
|
||||||
|
skip = pre_skip;
|
||||||
|
|
||||||
assert(opus_decoder == nullptr);
|
assert(opus_decoder == nullptr);
|
||||||
assert(IsInitialized() == (output_buffer != nullptr));
|
assert(IsInitialized() == (output_buffer != nullptr));
|
||||||
|
|
||||||
@ -236,15 +240,25 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
|||||||
opus_strerror(nframes));
|
opus_strerror(nframes));
|
||||||
|
|
||||||
if (nframes > 0) {
|
if (nframes > 0) {
|
||||||
|
if (skip >= (unsigned)nframes) {
|
||||||
|
skip -= nframes;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opus_int16 *data = output_buffer;
|
||||||
|
data += skip * previous_channels;
|
||||||
|
nframes -= skip;
|
||||||
|
skip = 0;
|
||||||
|
|
||||||
const size_t nbytes = nframes * frame_size;
|
const size_t nbytes = nframes * frame_size;
|
||||||
auto cmd = client.SubmitData(input_stream,
|
auto cmd = client.SubmitData(input_stream,
|
||||||
output_buffer, nbytes,
|
data, nbytes,
|
||||||
0);
|
0);
|
||||||
if (cmd != DecoderCommand::NONE)
|
if (cmd != DecoderCommand::NONE)
|
||||||
throw cmd;
|
throw cmd;
|
||||||
|
|
||||||
if (packet.granulepos > 0)
|
if (packet.granulepos > 0)
|
||||||
client.SubmitTimestamp(FloatDuration(packet.granulepos)
|
client.SubmitTimestamp(FloatDuration(packet.granulepos - pre_skip)
|
||||||
/ opus_sample_rate);
|
/ opus_sample_rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,6 +274,7 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
SeekGranulePos(where_granulepos);
|
SeekGranulePos(where_granulepos);
|
||||||
|
skip = pre_skip;
|
||||||
return true;
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return false;
|
return false;
|
||||||
@ -302,10 +317,9 @@ mpd_opus_stream_decode(DecoderClient &client,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
||||||
unsigned &channels)
|
unsigned &channels, unsigned &pre_skip)
|
||||||
{
|
{
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
unsigned pre_skip;
|
|
||||||
|
|
||||||
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
||||||
IsOpusHead(packet) &&
|
IsOpusHead(packet) &&
|
||||||
@ -329,11 +343,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||||
TagHandler &handler)
|
ogg_int64_t pre_skip, TagHandler &handler)
|
||||||
{
|
{
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
|
|
||||||
if (OggSeekFindEOS(sync, stream, packet, is)) {
|
if (OggSeekFindEOS(sync, stream, packet, is) &&
|
||||||
|
packet.granulepos >= pre_skip) {
|
||||||
const auto duration =
|
const auto duration =
|
||||||
SongTime::FromScale<uint64_t>(packet.granulepos,
|
SongTime::FromScale<uint64_t>(packet.granulepos,
|
||||||
opus_sample_rate);
|
opus_sample_rate);
|
||||||
@ -353,15 +368,15 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
|||||||
|
|
||||||
OggStreamState os(first_page);
|
OggStreamState os(first_page);
|
||||||
|
|
||||||
unsigned channels;
|
unsigned channels, pre_skip;
|
||||||
if (!ReadAndParseOpusHead(oy, os, channels) ||
|
if (!ReadAndParseOpusHead(oy, os, channels, pre_skip) ||
|
||||||
!ReadAndVisitOpusTags(oy, os, handler))
|
!ReadAndVisitOpusTags(oy, os, handler))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
||||||
SampleFormat::S16, channels));
|
SampleFormat::S16, channels));
|
||||||
|
|
||||||
VisitOpusDuration(is, oy, os, handler);
|
VisitOpusDuration(is, oy, os, pre_skip, handler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user