diff --git a/NEWS b/NEWS index 43f6b8382..72db01fc4 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,11 @@ ver 0.21 (not yet released) - sndio: new mixer plugin * require GCC 5.0 +ver 0.20.18 (not yet released) +* decoder + - flac: improve seeking precision +* fix gapless CUE song transitions + ver 0.20.17 (2018/02/11) * output - alsa: fix crash bug with 8 channels diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 713c136bf..afa8c8475 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="17" + android:versionName="0.20.18"> diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx index d6138777e..d8495de92 100644 --- a/src/decoder/Bridge.cxx +++ b/src/decoder/Bridge.cxx @@ -293,6 +293,7 @@ DecoderBridge::CommandFinished() initial_seek_running = false; timestamp = dc.start_time.ToDoubleS(); + absolute_frame = dc.start_time.ToScale(dc.in_audio_format.sample_rate); return; } @@ -312,6 +313,7 @@ DecoderBridge::CommandFinished() convert->Reset(); timestamp = dc.seek_time.ToDoubleS(); + absolute_frame = dc.seek_time.ToScale(dc.in_audio_format.sample_rate); } dc.command = DecoderCommand::NONE; @@ -420,6 +422,7 @@ DecoderBridge::SubmitTimestamp(double t) assert(t >= 0); timestamp = t; + absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate); } DecoderCommand @@ -455,6 +458,29 @@ DecoderBridge::SubmitData(InputStream *is, return cmd; } + cmd = DecoderCommand::NONE; + + const size_t frame_size = dc.in_audio_format.GetFrameSize(); + size_t data_frames = length / frame_size; + + if (dc.end_time.IsPositive()) { + /* enforce the given end time */ + + const uint64_t end_frame = + dc.end_time.ToScale(dc.in_audio_format.sample_rate); + if (absolute_frame >= end_frame) + return DecoderCommand::STOP; + + const uint64_t remaining_frames = end_frame - absolute_frame; + if (data_frames >= remaining_frames) { + /* past the end of the range: truncate this + data submission and stop the decoder */ + data_frames = remaining_frames; + length = data_frames * frame_size; + cmd = DecoderCommand::STOP; + } + } + if (convert != nullptr) { assert(dc.in_audio_format != dc.out_audio_format); @@ -512,15 +538,11 @@ DecoderBridge::SubmitData(InputStream *is, timestamp += (double)nbytes / dc.out_audio_format.GetTimeToSize(); - - if (dc.end_time.IsPositive() && - timestamp >= dc.end_time.ToDoubleS()) - /* the end of this range has been reached: - stop decoding */ - return DecoderCommand::STOP; } - return DecoderCommand::NONE; + absolute_frame += data_frames; + + return cmd; } DecoderCommand diff --git a/src/decoder/Bridge.hxx b/src/decoder/Bridge.hxx index a2f333f65..1750e444b 100644 --- a/src/decoder/Bridge.hxx +++ b/src/decoder/Bridge.hxx @@ -50,6 +50,11 @@ public: */ double timestamp = 0; + /** + * The time stamp of the next data chunk, in PCM frames. + */ + uint64_t absolute_frame = 0; + /** * Is the initial seek (to the start position of the sub-song) * pending, or has it been performed already? diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx index 504096bd3..233ef0d99 100644 --- a/src/decoder/plugins/FlacCommon.cxx +++ b/src/decoder/plugins/FlacCommon.cxx @@ -24,7 +24,6 @@ #include "config.h" #include "FlacCommon.hxx" #include "FlacMetadata.hxx" -#include "util/ConstBuffer.hxx" #include "Log.hxx" #include @@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame, if (!initialized && !OnFirstFrame(frame.header)) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - const auto data = pcm_import.Import(buf, frame.header.blocksize); + chunk = pcm_import.Import(buf, frame.header.blocksize); - unsigned bit_rate = nbytes * 8 * frame.header.sample_rate / + kbit_rate = nbytes * 8 * frame.header.sample_rate / (1000 * frame.header.blocksize); - auto cmd = GetClient()->SubmitData(GetInputStream(), - data.data, data.size, - bit_rate); - switch (cmd) { - case DecoderCommand::NONE: - case DecoderCommand::START: - break; - - case DecoderCommand::STOP: - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DecoderCommand::SEEK: - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } diff --git a/src/decoder/plugins/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx index c33ca6e47..5f8c21b5e 100644 --- a/src/decoder/plugins/FlacCommon.hxx +++ b/src/decoder/plugins/FlacCommon.hxx @@ -27,6 +27,7 @@ #include "FlacInput.hxx" #include "FlacPcm.hxx" #include "../DecoderAPI.hxx" +#include "util/ConstBuffer.hxx" #include @@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput { */ bool unsupported = false; + /** + * The kbit_rate parameter for the next + * DecoderBridge::SubmitData() call. + */ + uint16_t kbit_rate; + FlacPcmImport pcm_import; /** @@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput { Tag tag; + /** + * Decoded PCM data obtained by our libFLAC write callback. + * If this is non-empty, then DecoderBridge::SubmitData() + * should be called. + */ + ConstBuffer chunk = nullptr; + FlacDecoder(DecoderClient &_client, InputStream &_input_stream) :FlacInput(_input_stream, &_client) {} diff --git a/src/decoder/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx index 2b3059382..012d28f92 100644 --- a/src/decoder/plugins/FlacDecoderPlugin.cxx +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) return data->initialized; } +static DecoderCommand +FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept +{ + if (d.tag.IsEmpty() && d.chunk.empty()) + return client.GetCommand(); + + if (!d.tag.IsEmpty()) { + auto cmd = client.SubmitTag(d.GetInputStream(), + std::move(d.tag)); + d.tag.Clear(); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (!d.chunk.empty()) { + auto cmd = client.SubmitData(d.GetInputStream(), + d.chunk.data, + d.chunk.size, + d.kbit_rate); + d.chunk = nullptr; + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + static void flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec) { DecoderClient &client = *data->GetClient(); while (true) { - DecoderCommand cmd; - if (!data->tag.IsEmpty()) { - cmd = client.SubmitTag(data->GetInputStream(), - std::move(data->tag)); - data->tag.Clear(); - } else - cmd = client.GetCommand(); + DecoderCommand cmd = FlacSubmitToClient(client, *data); if (cmd == DecoderCommand::SEEK) { FLAC__uint64 seek_sample = client.GetSeekFrame(); @@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec) client.CommandFinished(); } else client.SeekError(); + + /* FLAC__stream_decoder_seek_absolute() + decodes one frame and may have provided + data to be submitted to the client */ + continue; } else if (cmd == DecoderCommand::STOP) break; diff --git a/src/encoder/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx index 4796c7fe2..b4419882c 100644 --- a/src/encoder/plugins/OpusEncoderPlugin.cxx +++ b/src/encoder/plugins/OpusEncoderPlugin.cxx @@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder { ogg_int64_t packetno = 0; - ogg_int64_t granulepos; + ogg_int64_t granulepos = 0; public: OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);