From 79981f3cdae074890e5709fb59d46e9a1dfe94dd Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Feb 2018 01:21:46 +0100 Subject: [PATCH 1/6] increment version number to 0.20.18 --- NEWS | 2 ++ android/AndroidManifest.xml | 4 ++-- configure.ac | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index fd91dea42..888e9d082 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +ver 0.20.18 (not yet released) + 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/configure.ac b/configure.ac index 800baa048..d1ec1fe9e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,10 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.20.17, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.20.18, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 VERSION_MINOR=20 -VERSION_REVISION=17 +VERSION_REVISION=18 VERSION_EXTRA=0 AC_CONFIG_SRCDIR([src/Main.cxx]) From c43ea74b3005b97b9e494ba62d86d0e02ef39d55 Mon Sep 17 00:00:00 2001 From: cathugger Date: Sat, 17 Feb 2018 00:18:03 +0000 Subject: [PATCH 2/6] encoder/opus: initialize granulepos to 0 it was uninitialized before --- src/encoder/plugins/OpusEncoderPlugin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 986ec877b04ed33f38068430c60c817efec851bb Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Feb 2018 13:10:00 +0100 Subject: [PATCH 3/6] decoder/Bridge: truncate last chunk at the exact end_time Instead of passing whole chunks to the MusicPipe and checking the end_time after each chunk, truncate the last chunk if it would exceed the end_time. This requires keeping track of the absolute PCM frame number. This fixes a problem with gapless CUE song transitions: a small part of the following song was always played twice. Closes #113 --- NEWS | 1 + src/decoder/Bridge.cxx | 36 +++++++++++++++++++++++++++++------- src/decoder/Bridge.hxx | 5 +++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 888e9d082..0fac9e169 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ ver 0.20.18 (not yet released) +* fix gapless CUE song transitions ver 0.20.17 (2018/02/11) * output diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx index 37fcb2bf9..73a396e23 100644 --- a/src/decoder/Bridge.cxx +++ b/src/decoder/Bridge.cxx @@ -300,6 +300,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; } @@ -319,6 +320,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; @@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t) assert(t >= 0); timestamp = t; + absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate); } DecoderCommand @@ -464,6 +467,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); @@ -521,15 +547,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 3b354c063..c34c95941 100644 --- a/src/decoder/Bridge.hxx +++ b/src/decoder/Bridge.hxx @@ -49,6 +49,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? From 2aad0153925aa0fc7bf0bac206a756e294c8e6a0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Feb 2018 13:10:02 +0100 Subject: [PATCH 4/6] decoder/flac: move code to FlacSubmitToClient() --- src/decoder/plugins/FlacDecoderPlugin.cxx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/decoder/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx index 688382d4a..2dc24568d 100644 --- a/src/decoder/plugins/FlacDecoderPlugin.cxx +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -139,19 +139,25 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) return data->initialized; } +static DecoderCommand +FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept +{ + if (!d.tag.IsEmpty()) { + auto cmd = client.SubmitTag(d.GetInputStream(), + std::move(d.tag)); + d.tag.Clear(); + return cmd; + } else + return client.GetCommand(); +} + 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(); From b53a23b51b8d02a709af0bc8681a25197f12481b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Feb 2018 13:10:03 +0100 Subject: [PATCH 5/6] decoder/flac: call FlacSubmitToClient() again after seeking See code comment. --- src/decoder/plugins/FlacDecoderPlugin.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/decoder/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx index 2dc24568d..0b1c2a12a 100644 --- a/src/decoder/plugins/FlacDecoderPlugin.cxx +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -166,6 +166,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; From 026aef7465506429ee94e7b5b68e36ab8ed5a849 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Feb 2018 13:10:04 +0100 Subject: [PATCH 6/6] decoder/flac: move the SubmitData() call out of the callback This addresses two problems: 1. the libFLAC write callback had to send an error status to its caller when SubmitData() returned a command; this disrupted libFLAC and the resulting command could not be used for anything; 2. the libFLAC function FLAC__stream_decoder_seek_absolute() also calls the write callback, but its result cannot be used, because seeking is still in progress, so we lose all data from one FLAC frame. By moving the SubmitData() call until after CommandFinished(), we avoid losing this data. This fixes another part of #113 --- NEWS | 2 ++ src/decoder/plugins/FlacCommon.cxx | 20 ++------------------ src/decoder/plugins/FlacCommon.hxx | 14 ++++++++++++++ src/decoder/plugins/FlacDecoderPlugin.cxx | 21 ++++++++++++++++++--- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 0fac9e169..d27757eb2 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.20.18 (not yet released) +* decoder + - flac: improve seeking precision * fix gapless CUE song transitions ver 0.20.17 (2018/02/11) diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx index 1abd5682e..5dfd8555f 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 0b1c2a12a..80647d07a 100644 --- a/src/decoder/plugins/FlacDecoderPlugin.cxx +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -142,13 +142,28 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) static DecoderCommand FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept { + if (d.tag.IsEmpty() && d.chunk.IsEmpty()) + return client.GetCommand(); + if (!d.tag.IsEmpty()) { auto cmd = client.SubmitTag(d.GetInputStream(), std::move(d.tag)); d.tag.Clear(); - return cmd; - } else - return client.GetCommand(); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (!d.chunk.IsEmpty()) { + 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