Commit 760238fe authored by Max Kellermann's avatar Max Kellermann

decoder/opus: apply pre-skip (RFC7845 4.2)

parent a99b4aba
...@@ -4,6 +4,8 @@ ver 0.21.25 (not yet released) ...@@ -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 { ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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, ...@@ -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 ...@@ -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;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment