summaryrefslogtreecommitdiff
path: root/src/audio/include
diff options
context:
space:
mode:
authorailurux <ailuruxx@gmail.com>2024-04-02 11:13:50 +1100
committerailurux <ailuruxx@gmail.com>2024-04-02 11:13:50 +1100
commite20ebe7574db5aedc73f07b7bb3a0a01eae93c84 (patch)
tree34c93ec8a80e282f3ce3e47dd60c41e46de0f8b3 /src/audio/include
parenta750af35aa6afda40aadca8f7cf8db75f41a43b2 (diff)
parent0d0c4b2307cac8436fea7276956f293262b265ed (diff)
downloadtangara-fw-e20ebe7574db5aedc73f07b7bb3a0a01eae93c84.tar.gz
Merge branch 'main' into lua-volume
Diffstat (limited to 'src/audio/include')
-rw-r--r--src/audio/include/audio_converter.hpp21
-rw-r--r--src/audio/include/audio_decoder.hpp20
-rw-r--r--src/audio/include/audio_events.hpp106
-rw-r--r--src/audio/include/audio_fsm.hpp58
-rw-r--r--src/audio/include/audio_sink.hpp18
-rw-r--r--src/audio/include/audio_source.hpp11
-rw-r--r--src/audio/include/bt_audio_output.hpp5
-rw-r--r--src/audio/include/fatfs_audio_input.hpp4
-rw-r--r--src/audio/include/i2s_audio_output.hpp5
-rw-r--r--src/audio/include/track_queue.hpp3
10 files changed, 166 insertions, 85 deletions
diff --git a/src/audio/include/audio_converter.hpp b/src/audio/include/audio_converter.hpp
index c2ebde60..232b5d8e 100644
--- a/src/audio/include/audio_converter.hpp
+++ b/src/audio/include/audio_converter.hpp
@@ -6,9 +6,11 @@
#pragma once
+#include <stdint.h>
#include <cstdint>
#include <memory>
+#include "audio_events.hpp"
#include "audio_sink.hpp"
#include "audio_source.hpp"
#include "codec.hpp"
@@ -30,18 +32,23 @@ class SampleConverter {
auto SetOutput(std::shared_ptr<IAudioOutput>) -> void;
- auto ConvertSamples(cpp::span<sample::Sample>,
- const IAudioOutput::Format& format,
- bool is_eos) -> void;
+ auto beginStream(std::shared_ptr<TrackInfo>) -> void;
+ auto continueStream(cpp::span<sample::Sample>) -> void;
+ auto endStream() -> void;
private:
auto Main() -> void;
- auto SetTargetFormat(const IAudioOutput::Format& format) -> void;
- auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t;
+ auto handleBeginStream(std::shared_ptr<TrackInfo>) -> void;
+ auto handleContinueStream(size_t samples_available) -> void;
+ auto handleEndStream() -> void;
+
+ auto handleSamples(cpp::span<sample::Sample>) -> size_t;
+
+ auto sendToSink(cpp::span<sample::Sample>) -> void;
struct Args {
- IAudioOutput::Format format;
+ std::shared_ptr<TrackInfo>* track;
size_t samples_available;
bool is_end_of_stream;
};
@@ -59,6 +66,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 318e6fd4..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);
-
- 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 03584062..b8a0dba6 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -9,40 +9,104 @@
#include <stdint.h>
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
+#include "audio_sink.hpp"
#include "tinyfsm.hpp"
#include "track.hpp"
-#include "track_queue.hpp"
#include "types.hpp"
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;
-};
-struct PlaybackStarted : tinyfsm::Event {};
+ IAudioOutput::Format format;
+};
+/*
+ * 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;
};
-struct PlaybackStopped : tinyfsm::Event {};
+/*
+ * 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 TogglePlayPause : tinyfsm::Event {
+ std::optional<bool> set_to;
+};
struct QueueUpdate : tinyfsm::Event {
bool current_changed;
-};
-struct PlayFile : tinyfsm::Event {
- std::string filename;
+ enum Reason {
+ kExplicitUpdate,
+ kRepeatingLastTrack,
+ kTrackFinished,
+ kDeserialised,
+ };
+ Reason reason;
};
struct StepUpVolume : tinyfsm::Event {};
@@ -70,17 +134,21 @@ 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 StreamStarted : tinyfsm::Event {
+ std::shared_ptr<TrackInfo> track;
+ IAudioOutput::Format src_format;
+ IAudioOutput::Format dst_format;
+};
+
+struct StreamUpdate : tinyfsm::Event {
+ uint32_t samples_sunk;
+};
-struct AudioPipelineIdle : tinyfsm::Event {};
+struct StreamEnded : tinyfsm::Event {};
} // namespace internal
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index 29ec489a..60afb321 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,14 @@ 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::StreamStarted&);
+ void react(const internal::StreamUpdate&);
+ void react(const internal::StreamEnded&);
+
void react(const StepUpVolume&);
void react(const StepDownVolume&);
virtual void react(const system_fsm::HasPhonesChanged&);
@@ -52,21 +61,14 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const OutputModeChanged&);
virtual void react(const system_fsm::BootComplete&) {}
- virtual void react(const system_fsm::KeyLockChanged&) {};
+ virtual void react(const system_fsm::KeyLockChanged&){};
virtual void react(const system_fsm::StorageMounted&) {}
virtual void react(const system_fsm::BluetoothEvent&);
- virtual void react(const PlayFile&) {}
- 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 awaitEmptyDrainBuffer() -> void;
+
auto playTrack(database::TrackId id) -> void;
auto commitVolume() -> void;
@@ -79,10 +81,21 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::shared_ptr<BluetoothAudioOutput> sBtOutput;
static std::shared_ptr<IAudioOutput> sOutput;
- static std::optional<database::TrackId> sCurrentTrack;
+ static StreamBufferHandle_t sDrainBuffer;
- auto readyToPlay() -> bool;
- static bool sIsPlaybackAllowed;
+ static std::shared_ptr<TrackInfo> sCurrentTrack;
+ static uint64_t sCurrentSamples;
+ static std::optional<IAudioOutput::Format> sDrainFormat;
+ static bool sCurrentTrackIsFromQueue;
+
+ static std::shared_ptr<TrackInfo> sNextTrack;
+ static uint64_t sNextTrackCueSamples;
+ static bool sNextTrackIsFromQueue;
+
+ static bool sIsResampling;
+ static bool sIsPaused;
+
+ auto currentPositionSeconds() -> std::optional<uint32_t>;
};
namespace states {
@@ -90,17 +103,13 @@ namespace states {
class Uninitialised : public AudioState {
public:
void react(const system_fsm::BootComplete&) override;
-
- void react(const system_fsm::BluetoothEvent&) override {};
+ void react(const system_fsm::BluetoothEvent&) override{};
using AudioState::react;
};
class Standby : public AudioState {
public:
- void react(const PlayFile&) 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;
@@ -112,17 +121,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 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;
};
diff --git a/src/audio/include/audio_sink.hpp b/src/audio/include/audio_sink.hpp
index 116410f6..f31d0d75 100644
--- a/src/audio/include/audio_sink.hpp
+++ b/src/audio/include/audio_sink.hpp
@@ -11,7 +11,6 @@
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
-#include "idf_additions.h"
namespace audio {
@@ -27,7 +26,8 @@ class IAudioOutput {
StreamBufferHandle_t stream_;
public:
- IAudioOutput(StreamBufferHandle_t stream) : stream_(stream) {}
+ IAudioOutput(StreamBufferHandle_t stream)
+ : stream_(stream), mode_(Modes::kOff) {}
virtual ~IAudioOutput() {}
@@ -41,7 +41,14 @@ class IAudioOutput {
* Indicates whether this output is currently being sent samples. If this is
* false, the output should place itself into a low power state.
*/
- virtual auto SetMode(Modes) -> void = 0;
+ auto mode(Modes m) -> void {
+ if (mode_ == m) {
+ return;
+ }
+ changeMode(m);
+ mode_ = m;
+ }
+ auto mode() -> Modes { return mode_; }
virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0;
@@ -70,6 +77,11 @@ class IAudioOutput {
virtual auto Configure(const Format& format) -> void = 0;
auto stream() -> StreamBufferHandle_t { return stream_; }
+
+ protected:
+ Modes mode_;
+
+ virtual auto changeMode(Modes new_mode) -> void = 0;
};
} // namespace audio
diff --git a/src/audio/include/audio_source.hpp b/src/audio/include/audio_source.hpp
index 68145f5b..b38acd7a 100644
--- a/src/audio/include/audio_source.hpp
+++ b/src/audio/include/audio_source.hpp
@@ -16,7 +16,10 @@ namespace audio {
class TaggedStream : public codecs::IStream {
public:
TaggedStream(std::shared_ptr<database::TrackTags>,
- std::unique_ptr<codecs::IStream> wrapped);
+ std::unique_ptr<codecs::IStream> wrapped,
+ std::string path,
+ uint32_t offset = 0
+ );
auto tags() -> std::shared_ptr<database::TrackTags>;
@@ -30,11 +33,17 @@ class TaggedStream : public codecs::IStream {
auto Size() -> std::optional<int64_t> override;
+ auto Offset() -> uint32_t;
+
+ auto Filepath() -> std::string;
+
auto SetPreambleFinished() -> void override;
private:
std::shared_ptr<database::TrackTags> tags_;
std::unique_ptr<codecs::IStream> wrapped_;
+ std::string filepath_;
+ int32_t offset_;
};
class IAudioSource {
diff --git a/src/audio/include/bt_audio_output.hpp b/src/audio/include/bt_audio_output.hpp
index dff25131..74b0301a 100644
--- a/src/audio/include/bt_audio_output.hpp
+++ b/src/audio/include/bt_audio_output.hpp
@@ -28,8 +28,6 @@ class BluetoothAudioOutput : public IAudioOutput {
tasks::WorkerPool&);
~BluetoothAudioOutput();
- auto SetMode(Modes) -> void override;
-
auto SetVolumeImbalance(int_fast8_t balance) -> void override;
auto SetVolume(uint16_t) -> void override;
@@ -50,6 +48,9 @@ class BluetoothAudioOutput : public IAudioOutput {
BluetoothAudioOutput(const BluetoothAudioOutput&) = delete;
BluetoothAudioOutput& operator=(const BluetoothAudioOutput&) = delete;
+ protected:
+ auto changeMode(Modes) -> void override;
+
private:
drivers::Bluetooth& bluetooth_;
tasks::WorkerPool& bg_worker_;
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index 4cccbb46..10b7433e 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -39,7 +39,7 @@ class FatfsAudioInput : public IAudioSource {
* given file path.
*/
auto SetPath(std::optional<std::string>) -> void;
- auto SetPath(const std::string&) -> void;
+ auto SetPath(const std::string&,uint32_t offset = 0) -> void;
auto SetPath() -> void;
auto HasNewStream() -> bool override;
@@ -49,7 +49,7 @@ class FatfsAudioInput : public IAudioSource {
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
private:
- auto OpenFile(const std::string& path) -> bool;
+ auto OpenFile(const std::string& path,uint32_t offset) -> bool;
auto ContainerToStreamType(database::Container)
-> std::optional<codecs::StreamType>;
diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp
index 538eafb6..7954257a 100644
--- a/src/audio/include/i2s_audio_output.hpp
+++ b/src/audio/include/i2s_audio_output.hpp
@@ -23,8 +23,6 @@ class I2SAudioOutput : public IAudioOutput {
I2SAudioOutput(StreamBufferHandle_t, drivers::IGpios& expander);
~I2SAudioOutput();
- auto SetMode(Modes) -> void override;
-
auto SetMaxVolume(uint16_t) -> void;
auto SetVolumeDb(uint16_t) -> void;
@@ -48,6 +46,9 @@ class I2SAudioOutput : public IAudioOutput {
I2SAudioOutput(const I2SAudioOutput&) = delete;
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;
+ protected:
+ auto changeMode(Modes) -> void override;
+
private:
drivers::IGpios& expander_;
std::unique_ptr<drivers::I2SDac> dac_;
diff --git a/src/audio/include/track_queue.hpp b/src/audio/include/track_queue.hpp
index e4fd7881..5b7c9448 100644
--- a/src/audio/include/track_queue.hpp
+++ b/src/audio/include/track_queue.hpp
@@ -12,6 +12,7 @@
#include <shared_mutex>
#include <vector>
+#include "audio_events.hpp"
#include "cppbor_parse.h"
#include "database.hpp"
#include "tasks.hpp"
@@ -120,6 +121,8 @@ class TrackQueue {
TrackQueue& operator=(const TrackQueue&) = delete;
private:
+ auto next(QueueUpdate::Reason r) -> void;
+
mutable std::shared_mutex mutex_;
tasks::WorkerPool& bg_worker_;