summaryrefslogtreecommitdiff
path: root/src/audio/include
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-03-25 17:34:41 +1100
committerjacqueline <me@jacqueline.id.au>2024-03-25 17:34:41 +1100
commit175bfc4e3e9f7aa39e084d3f1625347f1d5711ec (patch)
treef71b458f19acca855815ab876944d48a3c5acbcb /src/audio/include
parent5c985afd258a96b68d6bd5a4fade17ed998d2c07 (diff)
downloadtangara-fw-175bfc4e3e9f7aa39e084d3f1625347f1d5711ec.tar.gz
WIP rewrie audio pipeline+fsm guts for more reliability
Diffstat (limited to 'src/audio/include')
-rw-r--r--src/audio/include/audio_converter.hpp5
-rw-r--r--src/audio/include/audio_decoder.hpp20
-rw-r--r--src/audio/include/audio_events.hpp108
-rw-r--r--src/audio/include/audio_fsm.hpp53
4 files changed, 110 insertions, 76 deletions
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 <stdint.h>
#include <cstdint>
#include <memory>
@@ -40,6 +41,8 @@ class SampleConverter {
auto SetTargetFormat(const IAudioOutput::Format& format) -> void;
auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t;
+ auto SendToSink(cpp::span<sample::Sample>) -> 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
@@ -20,25 +20,6 @@
namespace audio {
/*
- * Sample-based timer for the current elapsed playback time.
- */
-class Timer {
- public:
- Timer(std::shared_ptr<Track>, const codecs::ICodec::OutputFormat& format, uint32_t current_seconds = 0);
-
- auto AddSamples(std::size_t) -> void;
-
- private:
- std::shared_ptr<Track> 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
* forwards the resulting stream to the given converter.
@@ -65,7 +46,6 @@ class Decoder {
std::shared_ptr<codecs::IStream> stream_;
std::unique_ptr<codecs::ICodec> codec_;
- std::unique_ptr<Timer> timer_;
std::optional<codecs::ICodec::OutputFormat> current_format_;
std::optional<IAudioOutput::Format> 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 <stdint.h>
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
+#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<database::TrackTags> tags;
- std::shared_ptr<database::TrackData> 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<uint32_t> duration;
+
+ /* The offset in seconds that this file's decoding started from. */
+ std::optional<uint32_t> start_offset;
+
+ /* The approximate bitrate of this track in its original encoded form. */
+ std::optional<uint32_t> 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> track;
+ /*
+ * The track that is currently being decoded by the audio pipeline. May be
+ * absent if there is no current track.
+ */
+ std::shared_ptr<TrackInfo> current_track;
+
+ /*
+ * How long the current track has been playing for, in seconds. Will always
+ * be present if current_track is present.
+ */
+ std::optional<uint32_t> 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<std::string, database::TrackId, std::monostate> new_track;
+ std::optional<uint32_t> seek_to_second;
+
+ enum Transition {
+ kHardCut,
+ kGapless,
+ // TODO: kCrossFade
+ };
+ Transition transition;
};
-struct PlaybackStopped : tinyfsm::Event {};
+struct TogglePlayPause : tinyfsm::Event {
+ std::optional<bool> 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<TrackInfo> 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 <stdint.h>
#include <deque>
#include <memory>
#include <vector>
@@ -41,6 +42,17 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
/* 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<AudioState> {
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<AudioState> {
static StreamBufferHandle_t sDrainBuffer;
- static std::optional<database::TrackId> sCurrentTrack;
+ static std::shared_ptr<TrackInfo> sCurrentTrack;
+ static uint64_t sCurrentSamples;
+ static std::optional<IAudioOutput::Format> sCurrentFormat;
- auto readyToPlay() -> bool;
- static bool sIsPlaybackAllowed;
+ static std::shared_ptr<TrackInfo> sNextTrack;
+ static uint64_t sNextTrackCueSamples;
+
+ static bool sIsResampling;
+ static bool sIsPaused;
+
+ auto currentPositionSeconds() -> std::optional<uint32_t>;
};
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;
};