From f253d2ee7568b61ce2fab962f7328a50e2da6adf Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 27 Aug 2024 21:17:53 +1000 Subject: Timeout when writing output samples throughout the audio pipeline This allows the audio pipeline to remain responsive even when the drain buffer has completely filled. This in turn means that you now see the track info in the 'now playing' screen change if the current track changes whilst you are paused. Since I was fucking around a lot in the audio processor anyway, I also added mono->stereo expansion so that playing mono tracks on Bluetooth no longer destroys your ears. --- src/tangara/audio/processor.hpp | 70 +++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) (limited to 'src/tangara/audio/processor.hpp') diff --git a/src/tangara/audio/processor.hpp b/src/tangara/audio/processor.hpp index 5c4ad0fa..f1b1d921 100644 --- a/src/tangara/audio/processor.hpp +++ b/src/tangara/audio/processor.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include "audio/audio_events.hpp" @@ -33,18 +35,43 @@ class SampleProcessor { auto SetOutput(std::shared_ptr) -> void; + /* + * Signals to the sample processor that a new discrete stream of audio is now + * being sent. This will typically represent a new track being played. + */ auto beginStream(std::shared_ptr) -> void; - auto continueStream(std::span) -> void; + + /* + * Sends a span of PCM samples to the processor. Returns a subspan of the + * given span containing samples that were not able to be sent during this + * call, e.g. because of congestion downstream from the processor. + */ + auto continueStream(std::span) -> std::span; + + /* + * Signals to the sample processor that the current stream is ending. This + * can either be because the stream has naturally finished, or because it is + * being interrupted. + * If `cancelled` is false, the sample processor will ensure all previous + * samples are processed and sent before communicating the end of the stream + * onwards. If `cancelled` is true, any samples from the current stream that + * have not yet been played will be discarded. + */ auto endStream(bool cancelled) -> void; + SampleProcessor(const SampleProcessor&) = delete; + SampleProcessor& operator=(const SampleProcessor&) = delete; + private: auto Main() -> void; auto handleBeginStream(std::shared_ptr) -> void; - auto handleContinueStream(size_t samples_available) -> void; auto handleEndStream(bool cancel) -> void; - auto handleSamples(std::span) -> size_t; + auto processSamples(bool finalise) -> bool; + + auto hasPendingWork() -> bool; + auto flushOutputBuffer() -> bool; struct Args { std::shared_ptr* track; @@ -53,21 +80,44 @@ class SampleProcessor { bool clear_buffers; }; QueueHandle_t commands_; + std::list pending_commands_; - std::unique_ptr resampler_; + auto discardCommand(Args& command) -> void; StreamBufferHandle_t source_; drivers::PcmBuffer& sink_; - std::span input_buffer_; - std::span input_buffer_as_bytes_; + class Buffer { + public: + Buffer(); + ~Buffer(); + + auto writeAcquire() -> std::span; + auto writeCommit(size_t) -> void; + + auto readAcquire() -> std::span; + auto readCommit(size_t) -> void; - std::span resampled_buffer_; + auto isEmpty() -> bool; + auto clear() -> void; + + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + private: + std::span buffer_; + std::span samples_in_buffer_; + }; + + Buffer input_buffer_; + Buffer resampled_buffer_; + Buffer output_buffer_; + + std::unique_ptr resampler_; + bool double_samples_; std::shared_ptr output_; - IAudioOutput::Format source_format_; - IAudioOutput::Format target_format_; - size_t leftover_bytes_; + size_t unprocessed_samples_; }; } // namespace audio -- cgit v1.2.3