diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx index cb30144483..9e68c0703a 100644 --- a/src/decoder/Bridge.cxx +++ b/src/decoder/Bridge.cxx @@ -401,6 +401,19 @@ DecoderBridge::OpenUri(std::string_view uri) } } +bool +DecoderBridge::Seek(InputStream &is, offset_type new_offset) noexcept +try { + assert(dc.state == DecoderState::START || + dc.state == DecoderState::DECODE); + + is.LockSeek(new_offset); + return true; +} catch (...) { + error = std::current_exception(); + return false; +} + size_t DecoderBridge::Read(InputStream &is, std::span dest) noexcept try { diff --git a/src/decoder/Bridge.hxx b/src/decoder/Bridge.hxx index 5689cb3033..59b464224c 100644 --- a/src/decoder/Bridge.hxx +++ b/src/decoder/Bridge.hxx @@ -159,6 +159,7 @@ public: uint64_t GetSeekFrame() noexcept override; void SeekError(std::exception_ptr &&_error) noexcept override; InputStreamPtr OpenUri(std::string_view uri) override; + bool Seek(InputStream &is, offset_type new_offset) noexcept override; size_t Read(InputStream &is, std::span dest) noexcept override; void SubmitTimestamp(FloatDuration t) noexcept override; diff --git a/src/decoder/Client.hxx b/src/decoder/Client.hxx index 7dbfea21b6..869d0cef3f 100644 --- a/src/decoder/Client.hxx +++ b/src/decoder/Client.hxx @@ -5,6 +5,7 @@ #include "Command.hxx" #include "Chrono.hxx" +#include "input/Offset.hxx" #include "input/Ptr.hxx" #include @@ -86,6 +87,13 @@ public: */ virtual InputStreamPtr OpenUri(std::string_view uri) = 0; + /** + * Wrapper for InputStream::LockSeek(). + * + * @return true on success, false on error + */ + virtual bool Seek(InputStream &is, offset_type new_offset) noexcept = 0; + /** * Blocking read from the input stream. * diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx index 4de99cd59d..ad7ca25649 100644 --- a/src/decoder/DecoderAPI.cxx +++ b/src/decoder/DecoderAPI.cxx @@ -3,6 +3,7 @@ #include "DecoderAPI.hxx" #include "input/InputStream.hxx" +#include "util/IntOverflow.hxx" #include "Log.hxx" #include @@ -58,18 +59,56 @@ decoder_read_full(DecoderClient *client, InputStream &is, } bool -decoder_skip(DecoderClient *client, InputStream &is, size_t size) noexcept +decoder_skip(DecoderClient *client, InputStream &is, offset_type delta) noexcept { - while (size > 0) { + if (delta > 1024 && is.IsSeekable() && + (delta > 1024 * 1024 || is.CheapSeeking())) { + offset_type new_offset; + if (AddOverflow(is.GetOffset(), delta, new_offset)) + return false; + + return decoder_seek(client, is, new_offset); + } + + if (delta > 4 * 1024 * 1024) + /* skipping that much would be too expensive */ + return false; + + while (delta > 0) { std::byte buffer[1024]; - size_t nbytes = decoder_read(client, is, - std::span{buffer, std::min(sizeof(buffer), size)}); + std::span dest{buffer}; + if (delta < dest.size()) + dest = dest.first(delta); + + size_t nbytes = decoder_read(client, is, dest); if (nbytes == 0) return false; - size -= nbytes; + delta -= nbytes; } return true; } + +bool +decoder_seek(DecoderClient *client, InputStream &is, offset_type new_offset) noexcept +{ + if (is.IsSeekable()) { + if (client != nullptr) + return client->Seek(is, new_offset); + + try { + is.LockSeek(new_offset); + return true; + } catch (...) { + LogError(std::current_exception()); + return false; + } + } + + if (is.GetOffset() > new_offset) + return false; + + return decoder_skip(client, is, new_offset - is.GetOffset()); +} diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx index 9a3c9a5a15..df2bc15b36 100644 --- a/src/decoder/DecoderAPI.hxx +++ b/src/decoder/DecoderAPI.hxx @@ -13,6 +13,7 @@ // IWYU pragma: begin_exports #include "Client.hxx" +#include "input/Offset.hxx" #include "input/Ptr.hxx" #include "Command.hxx" #include "DecoderPlugin.hxx" @@ -46,10 +47,12 @@ class StopDecoder {}; * @return the number of bytes read, or 0 if one of the following * occurs: end of file; error; command (like SEEK or STOP). */ +[[nodiscard]] size_t decoder_read(DecoderClient *decoder, InputStream &is, std::span dest) noexcept; +[[nodiscard]] static inline size_t decoder_read(DecoderClient &decoder, InputStream &is, std::span dest) noexcept @@ -65,6 +68,7 @@ decoder_read(DecoderClient &decoder, InputStream &is, * @return the number of bytes read, or 0 if one of the following * occurs: end of file; error; command (like SEEK or STOP). */ +[[nodiscard]] size_t decoder_read_much(DecoderClient *decoder, InputStream &is, std::span dest) noexcept; @@ -76,6 +80,7 @@ decoder_read_much(DecoderClient *decoder, InputStream &is, * @return true on success, false on error or command or not enough * data */ +[[nodiscard]] bool decoder_read_full(DecoderClient *decoder, InputStream &is, std::span dest) noexcept; @@ -85,5 +90,15 @@ decoder_read_full(DecoderClient *decoder, InputStream &is, * * @return true on success, false on error or command */ +[[nodiscard]] bool -decoder_skip(DecoderClient *decoder, InputStream &is, size_t size) noexcept; +decoder_skip(DecoderClient *decoder, InputStream &is, offset_type delta) noexcept; + +/** + * Wrapper for InputStream::LockSeek(). + * + * @return true on success, false on error or command + */ +[[nodiscard]] +bool +decoder_seek(DecoderClient *decoder, InputStream &is, offset_type new_offset) noexcept; diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx index 5ede67ac62..d98d79711e 100644 --- a/src/decoder/plugins/DsdLib.cxx +++ b/src/decoder/plugins/DsdLib.cxx @@ -9,58 +9,18 @@ #include "config.h" #include "DsdLib.hxx" -#include "../DecoderAPI.hxx" -#include "input/InputStream.hxx" -#include "util/IntOverflow.hxx" #ifdef ENABLE_ID3TAG +#include "../DecoderAPI.hxx" #include "tag/Id3Limits.hxx" #include "tag/Id3Parse.hxx" #include "tag/Id3Scan.hxx" +#include "input/InputStream.hxx" #include "util/AllocatedArray.hxx" #endif #include -bool -dsdlib_skip_to(DecoderClient *client, InputStream &is, - offset_type offset) -{ - if (is.IsSeekable()) { - is.LockSeek(offset); - return true; - } - - if (is.GetOffset() > offset) - return false; - - return dsdlib_skip(client, is, offset - is.GetOffset()); -} - -bool -dsdlib_skip(DecoderClient *client, InputStream &is, - offset_type delta) -{ - if (delta == 0) - return true; - - if (is.IsSeekable()) { - offset_type new_offset; - if (AddOverflow(is.GetOffset(), delta, new_offset)) - return false; - - is.LockSeek(is.GetOffset() + delta); - return true; - } - - if (delta > 1024 * 1024) - /* don't skip more than one megabyte; it would be too - expensive */ - return false; - - return decoder_skip(client, is, delta); -} - bool dsdlib_valid_freq(uint32_t samplefreq) noexcept { @@ -97,7 +57,7 @@ dsdlib_tag_id3(DecoderClient *client, InputStream &is, if (count64 < 10 || count64 > MAX_ID3_TAG_SIZE) return false; - if (!dsdlib_skip_to(client, is, tagoffset)) + if (!decoder_seek(client, is, tagoffset)) return false; const id3_length_t count = count64; diff --git a/src/decoder/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx index 0a46d57f20..b00c7be6a0 100644 --- a/src/decoder/plugins/DsdLib.hxx +++ b/src/decoder/plugins/DsdLib.hxx @@ -21,26 +21,6 @@ struct DsdId { } }; -/** - * Skip the #InputStream to the specified offset. - * - * On error, either throws exception or returns false. - */ -[[nodiscard]] -bool -dsdlib_skip_to(DecoderClient *client, InputStream &is, - offset_type offset); - -/** - * Skip some bytes from the #InputStream. - * - * On error, either throws exception or returns false. - */ -[[nodiscard]] -bool -dsdlib_skip(DecoderClient *client, InputStream &is, - offset_type delta); - /** * Check if the sample frequency is a valid DSD frequency. **/ diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx index 878035f412..90634c96b8 100644 --- a/src/decoder/plugins/DsdiffDecoderPlugin.cxx +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -116,11 +116,11 @@ dsdiff_read_payload(DecoderClient *client, InputStream &is, static bool dsdiff_read_partial_payload(DecoderClient *client, InputStream &is, const DsdiffChunkHeader &header, - std::span dest) + std::span dest) noexcept { return header.GetSize() >= dest.size() && decoder_read_full(client, is, dest) && - dsdlib_skip(client, is, header.GetPaddedSize() - dest.size()); + decoder_skip(client, is, header.GetPaddedSize() - dest.size()); } /** @@ -175,7 +175,7 @@ dsdiff_read_prop_snd(DecoderClient *client, InputStream &is, } else { /* ignore unknown chunk */ - if (!dsdlib_skip_to(client, is, chunk_end_offset)) + if (!decoder_seek(client, is, chunk_end_offset)) return false; } } @@ -201,7 +201,7 @@ dsdiff_read_prop(DecoderClient *client, InputStream &is, return dsdiff_read_prop_snd(client, is, metadata, prop_size, end_offset); else /* ignore unknown PROP chunk */ - return dsdlib_skip_to(client, is, end_offset); + return decoder_seek(client, is, end_offset); } static void @@ -210,7 +210,7 @@ dsdiff_handle_native_tag(DecoderClient *client, InputStream &is, offset_type tagoffset, TagType type) { - if (!dsdlib_skip_to(client, is, tagoffset)) + if (!decoder_seek(client, is, tagoffset)) return; struct dsdiff_native_tag metatag; @@ -252,7 +252,7 @@ dsdiff_read_metadata_extra(DecoderClient *client, InputStream &is, { /* skip from DSD data to next chunk header */ - if (!dsdlib_skip(client, is, metadata.chunk_size)) + if (!decoder_skip(client, is, metadata.chunk_size)) return false; if (!dsdiff_read_chunk_header(client, is, chunk_header)) return false; @@ -295,7 +295,7 @@ dsdiff_read_metadata_extra(DecoderClient *client, InputStream &is, } #endif - if (!dsdlib_skip(client, is, chunk_size)) + if (!decoder_skip(client, is, chunk_size)) break; } while (dsdiff_read_chunk_header(client, is, chunk_header)); @@ -355,7 +355,7 @@ dsdiff_read_metadata(DecoderClient *client, InputStream &is, return true; } else { /* ignore unknown chunk */ - if (!dsdlib_skip_to(client, is, chunk_end_offset)) + if (!decoder_seek(client, is, chunk_end_offset)) return false; } } @@ -404,8 +404,8 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is, } try { - if (dsdlib_skip_to(&client, is, - start_offset + offset)) { + if (decoder_seek(&client, is, + start_offset + offset)) { client.CommandFinished(); remaining_bytes = total_bytes - offset; } else diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx index 4afb7b523d..480ea6fd49 100644 --- a/src/decoder/plugins/DsfDecoderPlugin.cxx +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -261,7 +261,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is, start_offset + block * block_size; try { - if (dsdlib_skip_to(&client, is, offset)) { + if (decoder_seek(&client, is, offset)) { client.CommandFinished(); i = block; } else diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx index 5415269405..53c12d950c 100644 --- a/src/decoder/plugins/MadDecoderPlugin.cxx +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -282,8 +282,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept "ID3 tag is too large: {}", tagsize); - mad_stream_skip(&stream, this_frame.size()); - decoder_skip(client, input_stream, tagsize - this_frame.size()); + if (decoder_skip(client, input_stream, tagsize - this_frame.size())) + mad_stream_skip(&stream, this_frame.size()); return; } @@ -327,8 +327,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept if (tagsize <= this_frame.size()) { mad_stream_skip(&stream, tagsize); } else { - mad_stream_skip(&stream, this_frame.size()); - decoder_skip(client, input_stream, tagsize - this_frame.size()); + if (decoder_skip(client, input_stream, tagsize - this_frame.size())) + mad_stream_skip(&stream, this_frame.size()); } #endif } diff --git a/src/lib/chromaprint/DecoderClient.cxx b/src/lib/chromaprint/DecoderClient.cxx index 8e39e2daa9..ca38192e4e 100644 --- a/src/lib/chromaprint/DecoderClient.cxx +++ b/src/lib/chromaprint/DecoderClient.cxx @@ -66,6 +66,16 @@ ChromaprintDecoderClient::SubmitAudio(InputStream *, return GetCommand(); } +bool +ChromaprintDecoderClient::Seek(InputStream &is, offset_type new_offset) noexcept +try { + is.LockSeek(new_offset); + return true; +} catch (...) { + error = std::current_exception(); + return false; +} + size_t ChromaprintDecoderClient::Read(InputStream &is, std::span dest) noexcept diff --git a/src/lib/chromaprint/DecoderClient.hxx b/src/lib/chromaprint/DecoderClient.hxx index 5dd332800a..a8b77815f2 100644 --- a/src/lib/chromaprint/DecoderClient.hxx +++ b/src/lib/chromaprint/DecoderClient.hxx @@ -73,6 +73,7 @@ public: //InputStreamPtr OpenUri(std::string_view uri) override; + bool Seek(InputStream &is, offset_type new_offset) noexcept override; size_t Read(InputStream &is, std::span dest) noexcept override; diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx index b66da5b629..475692ca87 100644 --- a/src/output/plugins/PipeWireOutputPlugin.cxx +++ b/src/output/plugins/PipeWireOutputPlugin.cxx @@ -154,17 +154,6 @@ class PipeWireOutput final : AudioOutput { return new PipeWireOutput(block); } - static constexpr struct pw_stream_events MakeStreamEvents() noexcept { - struct pw_stream_events events{}; - events.version = PW_VERSION_STREAM_EVENTS; - events.state_changed = StateChanged; - events.process = Process; - events.drained = Drained; - events.control_info = ControlInfo; - events.param_changed = ParamChanged; - return events; - } - void SetVolume(float volume); void SetMixer(PipeWireMixer &_mixer) noexcept; @@ -261,6 +250,15 @@ class PipeWireOutput final : AudioOutput { o.ParamChanged(id, param); } + static constexpr struct pw_stream_events stream_events{ + .version = PW_VERSION_STREAM_EVENTS, + .state_changed = StateChanged, + .control_info = ControlInfo, + .param_changed = ParamChanged, + .process = Process, + .drained = Drained, + }; + /* virtual methods from class AudioOutput */ void Enable() override; void Disable() noexcept override; @@ -287,8 +285,6 @@ class PipeWireOutput final : AudioOutput { void SendTag(const Tag &tag) override; }; -static constexpr auto stream_events = PipeWireOutput::MakeStreamEvents(); - inline PipeWireOutput::PipeWireOutput(const ConfigBlock &block) :AudioOutput(FLAG_ENABLE_DISABLE), diff --git a/test/DumpDecoderClient.cxx b/test/DumpDecoderClient.cxx index 1ac415c942..43779322a5 100644 --- a/test/DumpDecoderClient.cxx +++ b/test/DumpDecoderClient.cxx @@ -63,6 +63,15 @@ DumpDecoderClient::OpenUri(std::string_view uri) return InputStream::OpenReady(uri, mutex); } +bool +DumpDecoderClient::Seek(InputStream &is, offset_type new_offset) noexcept +try { + is.LockSeek(new_offset); + return true; +} catch (...) { + return false; +} + size_t DumpDecoderClient::Read(InputStream &is, std::span dest) noexcept { diff --git a/test/DumpDecoderClient.hxx b/test/DumpDecoderClient.hxx index dc9ea8b80b..1ebfd1bf93 100644 --- a/test/DumpDecoderClient.hxx +++ b/test/DumpDecoderClient.hxx @@ -32,6 +32,7 @@ public: uint64_t GetSeekFrame() noexcept override; void SeekError(std::exception_ptr &&error) noexcept override; InputStreamPtr OpenUri(std::string_view uri) override; + bool Seek(InputStream &is, offset_type new_offset) noexcept override; size_t Read(InputStream &is, std::span dest) noexcept override; void SubmitTimestamp(FloatDuration t) noexcept override;