From 175bfc4e3e9f7aa39e084d3f1625347f1d5711ec Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 25 Mar 2024 17:34:41 +1100 Subject: WIP rewrie audio pipeline+fsm guts for more reliability --- src/audio/include/audio_converter.hpp | 5 ++ src/audio/include/audio_decoder.hpp | 20 ------- src/audio/include/audio_events.hpp | 108 ++++++++++++++++++++++++++-------- src/audio/include/audio_fsm.hpp | 53 +++++++---------- 4 files changed, 110 insertions(+), 76 deletions(-) (limited to 'src/audio/include') diff --git a/src/audio/include/audio_converter.hpp b/src/audio/include/audio_converter.hpp index c2ebde60..dcd068b5 100644 --- a/src/audio/include/audio_converter.hpp +++ b/src/audio/include/audio_converter.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -40,6 +41,8 @@ class SampleConverter { auto SetTargetFormat(const IAudioOutput::Format& format) -> void; auto HandleSamples(cpp::span, bool) -> size_t; + auto SendToSink(cpp::span) -> void; + struct Args { IAudioOutput::Format format; size_t samples_available; @@ -59,6 +62,8 @@ class SampleConverter { IAudioOutput::Format source_format_; IAudioOutput::Format target_format_; size_t leftover_bytes_; + + uint32_t samples_sunk_; }; } // namespace audio diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp index b8aac710..89f0f43c 100644 --- a/src/audio/include/audio_decoder.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -19,25 +19,6 @@ namespace audio { -/* - * Sample-based timer for the current elapsed playback time. - */ -class Timer { - public: - Timer(std::shared_ptr, const codecs::ICodec::OutputFormat& format, uint32_t current_seconds = 0); - - auto AddSamples(std::size_t) -> void; - - private: - std::shared_ptr track_; - - uint32_t current_seconds_; - uint32_t current_sample_in_second_; - uint32_t samples_per_second_; - - uint32_t total_duration_seconds_; -}; - /* * Handle to a persistent task that takes bytes from the given source, decodes * them into sample::Sample (normalised to 16 bit signed PCM), and then @@ -65,7 +46,6 @@ class Decoder { std::shared_ptr stream_; std::unique_ptr codec_; - std::unique_ptr timer_; std::optional current_format_; std::optional current_sink_format_; diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index a8533646..9af30467 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -9,8 +9,10 @@ #include #include #include +#include #include +#include "audio_sink.hpp" #include "tinyfsm.hpp" #include "track.hpp" @@ -18,24 +20,80 @@ namespace audio { -struct Track { +/* + * Struct encapsulating information about the decoder's current track. + */ +struct TrackInfo { + /* + * Audio tags extracted from the file. May be absent for files without any + * parseable tags. + */ std::shared_ptr tags; - std::shared_ptr db_info; - uint32_t duration; - uint32_t bitrate_kbps; + /* + * URI that the current track was retrieved from. This is currently always a + * file path on the SD card. + */ + std::string uri; + + /* + * The length of this track in seconds. This is either retrieved from the + * track's tags, or sometimes computed. It may therefore sometimes be + * inaccurate or missing. + */ + std::optional duration; + + /* The offset in seconds that this file's decoding started from. */ + std::optional start_offset; + + /* The approximate bitrate of this track in its original encoded form. */ + std::optional bitrate_kbps; + + /* The encoded format of the this track. */ codecs::StreamType encoding; - std::string filepath; }; -struct PlaybackStarted : tinyfsm::Event {}; - +/* + * Event emitted by the audio FSM when the state of the audio pipeline has + * changed. This is usually once per second while a track is playing, plus one + * event each when a track starts or finishes. + */ struct PlaybackUpdate : tinyfsm::Event { - uint32_t seconds_elapsed; - std::shared_ptr track; + /* + * The track that is currently being decoded by the audio pipeline. May be + * absent if there is no current track. + */ + std::shared_ptr current_track; + + /* + * How long the current track has been playing for, in seconds. Will always + * be present if current_track is present. + */ + std::optional track_position; + + /* Whether or not the current track is currently being output to a sink. */ + bool paused; +}; + +/* + * Sets a new track to be decoded by the audio pipeline, replacing any + * currently playing track. + */ +struct SetTrack : tinyfsm::Event { + std::variant new_track; + std::optional seek_to_second; + + enum Transition { + kHardCut, + kGapless, + // TODO: kCrossFade + }; + Transition transition; }; -struct PlaybackStopped : tinyfsm::Event {}; +struct TogglePlayPause : tinyfsm::Event { + std::optional set_to; +}; struct QueueUpdate : tinyfsm::Event { bool current_changed; @@ -49,15 +107,6 @@ struct QueueUpdate : tinyfsm::Event { Reason reason; }; -struct PlayFile : tinyfsm::Event { - std::string filename; -}; - -struct SeekFile : tinyfsm::Event { - uint32_t offset; - std::string filename; -}; - struct StepUpVolume : tinyfsm::Event {}; struct StepDownVolume : tinyfsm::Event {}; struct SetVolume : tinyfsm::Event { @@ -83,17 +132,26 @@ struct SetVolumeLimit : tinyfsm::Event { int limit_db; }; -struct TogglePlayPause : tinyfsm::Event {}; - struct OutputModeChanged : tinyfsm::Event {}; namespace internal { -struct InputFileOpened : tinyfsm::Event {}; -struct InputFileClosed : tinyfsm::Event {}; -struct InputFileFinished : tinyfsm::Event {}; +struct DecoderOpened : tinyfsm::Event { + std::shared_ptr track; +}; + +struct DecoderClosed : tinyfsm::Event {}; + +struct DecoderError : tinyfsm::Event {}; -struct AudioPipelineIdle : tinyfsm::Event {}; +struct ConverterConfigurationChanged : tinyfsm::Event { + IAudioOutput::Format src_format; + IAudioOutput::Format dst_format; +}; + +struct ConverterProgress : tinyfsm::Event { + uint32_t samples_sunk; +}; } // namespace internal diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 13e241be..62bb4786 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -41,6 +42,17 @@ class AudioState : public tinyfsm::Fsm { /* Fallback event handler. Does nothing. */ void react(const tinyfsm::Event& ev) {} + void react(const QueueUpdate&); + void react(const SetTrack&); + void react(const TogglePlayPause&); + + void react(const internal::DecoderOpened&); + void react(const internal::DecoderClosed&); + void react(const internal::DecoderError&); + + void react(const internal::ConverterConfigurationChanged&); + void react(const internal::ConverterProgress&); + void react(const StepUpVolume&); void react(const StepDownVolume&); virtual void react(const system_fsm::HasPhonesChanged&); @@ -56,17 +68,6 @@ class AudioState : public tinyfsm::Fsm { virtual void react(const system_fsm::StorageMounted&) {} virtual void react(const system_fsm::BluetoothEvent&); - virtual void react(const PlayFile&) {} - virtual void react(const SeekFile&) {} - virtual void react(const QueueUpdate&) {} - virtual void react(const PlaybackUpdate&) {} - void react(const TogglePlayPause&); - - virtual void react(const internal::InputFileOpened&) {} - virtual void react(const internal::InputFileClosed&) {} - virtual void react(const internal::InputFileFinished&) {} - virtual void react(const internal::AudioPipelineIdle&) {} - protected: auto clearDrainBuffer() -> void; auto playTrack(database::TrackId id) -> void; @@ -83,10 +84,17 @@ class AudioState : public tinyfsm::Fsm { static StreamBufferHandle_t sDrainBuffer; - static std::optional sCurrentTrack; + static std::shared_ptr sCurrentTrack; + static uint64_t sCurrentSamples; + static std::optional sCurrentFormat; - auto readyToPlay() -> bool; - static bool sIsPlaybackAllowed; + static std::shared_ptr sNextTrack; + static uint64_t sNextTrackCueSamples; + + static bool sIsResampling; + static bool sIsPaused; + + auto currentPositionSeconds() -> std::optional; }; namespace states { @@ -94,7 +102,6 @@ namespace states { class Uninitialised : public AudioState { public: void react(const system_fsm::BootComplete&) override; - void react(const system_fsm::BluetoothEvent&) override{}; using AudioState::react; @@ -102,10 +109,6 @@ class Uninitialised : public AudioState { class Standby : public AudioState { public: - void react(const PlayFile&) override; - void react(const SeekFile&) override; - void react(const internal::InputFileOpened&) override; - void react(const QueueUpdate&) override; void react(const system_fsm::KeyLockChanged&) override; void react(const system_fsm::StorageMounted&) override; @@ -117,18 +120,6 @@ class Playback : public AudioState { void entry() override; void exit() override; - void react(const system_fsm::HasPhonesChanged&) override; - - void react(const PlayFile&) override; - void react(const SeekFile&) override; - void react(const QueueUpdate&) override; - void react(const PlaybackUpdate&) override; - - void react(const internal::InputFileOpened&) override; - void react(const internal::InputFileClosed&) override; - void react(const internal::InputFileFinished&) override; - void react(const internal::AudioPipelineIdle&) override; - using AudioState::react; }; -- cgit v1.2.3