summaryrefslogtreecommitdiff
path: root/src/audio/include
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/include')
-rw-r--r--src/audio/include/audio_decoder.hpp7
-rw-r--r--src/audio/include/audio_events.hpp4
-rw-r--r--src/audio/include/audio_fsm.hpp6
-rw-r--r--src/audio/include/audio_sink.hpp32
-rw-r--r--src/audio/include/audio_source.hpp33
-rw-r--r--src/audio/include/audio_task.hpp45
-rw-r--r--src/audio/include/fatfs_audio_input.hpp125
-rw-r--r--src/audio/include/stream_info.hpp4
8 files changed, 199 insertions, 57 deletions
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
index a6b4754a..e8da415e 100644
--- a/src/audio/include/audio_decoder.hpp
+++ b/src/audio/include/audio_decoder.hpp
@@ -25,15 +25,12 @@ namespace audio {
* An audio element that accepts various kinds of encoded audio streams as
* input, and converts them to uncompressed PCM output.
*/
-class AudioDecoder : public IAudioElement {
+class AudioDecoder {
public:
AudioDecoder();
~AudioDecoder();
- auto NeedsToProcess() const -> bool override;
-
- auto Process(const std::vector<InputStream>& inputs, OutputStream* output)
- -> void override;
+ auto Process(const InputStream& input, OutputStream* output) -> void;
AudioDecoder(const AudioDecoder&) = delete;
AudioDecoder& operator=(const AudioDecoder&) = delete;
diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp
index 8af3703a..933eb7a2 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -26,7 +26,9 @@ struct PlaybackUpdate : tinyfsm::Event {
uint32_t seconds_total;
};
-struct QueueUpdate : tinyfsm::Event {};
+struct QueueUpdate : tinyfsm::Event {
+ bool current_changed;
+};
struct VolumeChanged : tinyfsm::Event {};
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index 7910f4e2..3a598902 100644
--- a/src/audio/include/audio_fsm.hpp
+++ b/src/audio/include/audio_fsm.hpp
@@ -11,6 +11,7 @@
#include <vector>
#include "audio_events.hpp"
+#include "audio_task.hpp"
#include "database.hpp"
#include "display.hpp"
#include "fatfs_audio_input.hpp"
@@ -18,6 +19,7 @@
#include "i2s_audio_output.hpp"
#include "i2s_dac.hpp"
#include "storage.hpp"
+#include "tag_parser.hpp"
#include "tinyfsm.hpp"
#include "track.hpp"
@@ -30,6 +32,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
public:
static auto Init(drivers::IGpios* gpio_expander,
std::weak_ptr<database::Database>,
+ std::shared_ptr<database::ITagParser>,
TrackQueue* queue) -> bool;
virtual ~AudioState() {}
@@ -61,11 +64,12 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::shared_ptr<drivers::I2SDac> sDac;
static std::weak_ptr<database::Database> sDatabase;
+ static std::unique_ptr<AudioTask> sTask;
static std::unique_ptr<FatfsAudioInput> sFileSource;
static std::unique_ptr<I2SAudioOutput> sI2SOutput;
- static std::vector<std::unique_ptr<IAudioElement>> sPipeline;
static TrackQueue* sTrackQueue;
+ static std::optional<database::TrackId> sCurrentTrack;
};
namespace states {
diff --git a/src/audio/include/audio_sink.hpp b/src/audio/include/audio_sink.hpp
index ac007bf8..c9124688 100644
--- a/src/audio/include/audio_sink.hpp
+++ b/src/audio/include/audio_sink.hpp
@@ -10,35 +10,25 @@
#include "audio_element.hpp"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
+#include "idf_additions.h"
#include "stream_info.hpp"
+
namespace audio {
class IAudioSink {
private:
// TODO: tune. at least about 12KiB seems right for mp3
- static const std::size_t kDrainBufferSize = 48 * 1024;
- uint8_t* buffer_;
- StaticStreamBuffer_t* metadata_;
- StreamBufferHandle_t handle_;
+ static const std::size_t kDrainBufferSize = 24 * 1024;
+ StreamBufferHandle_t stream_;
public:
IAudioSink()
- : buffer_(reinterpret_cast<uint8_t*>(
- heap_caps_malloc(kDrainBufferSize,
- MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))),
- metadata_(reinterpret_cast<StaticStreamBuffer_t*>(
- heap_caps_malloc(sizeof(StaticStreamBuffer_t),
- MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))),
- handle_(xStreamBufferCreateStatic(kDrainBufferSize,
- 1,
- buffer_,
- metadata_)) {}
-
- virtual ~IAudioSink() {
- vStreamBufferDelete(handle_);
- free(buffer_);
- free(metadata_);
- }
+ : stream_(xStreamBufferCreateWithCaps(
+ kDrainBufferSize,
+ 1,
+ MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)) {}
+
+ virtual ~IAudioSink() { vStreamBufferDeleteWithCaps(stream_); }
virtual auto SetInUse(bool) -> void {}
@@ -51,7 +41,7 @@ class IAudioSink {
virtual auto Configure(const StreamInfo::Format& format) -> bool = 0;
virtual auto Send(const cpp::span<std::byte>& data) -> void = 0;
- auto buffer() -> StreamBufferHandle_t { return handle_; }
+ auto stream() -> StreamBufferHandle_t { return stream_; }
};
} // namespace audio
diff --git a/src/audio/include/audio_source.hpp b/src/audio/include/audio_source.hpp
new file mode 100644
index 00000000..e062fd1a
--- /dev/null
+++ b/src/audio/include/audio_source.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/portmacro.h"
+#include "freertos/semphr.h"
+
+#include "stream_info.hpp"
+
+namespace audio {
+
+class IAudioSource {
+ public:
+ virtual ~IAudioSource() {}
+
+ /*
+ * Synchronously fetches data from this source.
+ */
+ virtual auto Read(std::function<bool(StreamInfo::Format)>,
+ std::function<size_t(cpp::span<const std::byte>)>,
+ TickType_t) -> void = 0;
+};
+
+} // namespace audio
diff --git a/src/audio/include/audio_task.hpp b/src/audio/include/audio_task.hpp
index f997caee..e316f17a 100644
--- a/src/audio/include/audio_task.hpp
+++ b/src/audio/include/audio_task.hpp
@@ -6,15 +6,54 @@
#pragma once
+#include <sys/_stdint.h>
+#include <cstdint>
+#include <memory>
+#include "audio_decoder.hpp"
#include "audio_sink.hpp"
+#include "audio_source.hpp"
+#include "codec.hpp"
#include "pipeline.hpp"
namespace audio {
-namespace task {
+class Timer {
+ public:
+ explicit Timer(StreamInfo::Pcm);
-auto StartPipeline(Pipeline* pipeline, IAudioSink* sink) -> void;
+ auto SetLengthSeconds(uint32_t) -> void;
+ auto SetLengthBytes(uint32_t) -> void;
-} // namespace task
+ auto AddBytes(std::size_t) -> void;
+
+ private:
+ StreamInfo::Pcm format_;
+
+ uint32_t last_seconds_;
+ uint32_t total_duration_seconds_;
+ float current_seconds_;
+};
+
+class AudioTask {
+ public:
+ static auto Start(IAudioSource* source, IAudioSink* sink) -> AudioTask*;
+
+ auto Main() -> void;
+
+ private:
+ AudioTask(IAudioSource* source, IAudioSink* sink);
+
+ IAudioSource* source_;
+ IAudioSink* sink_;
+ std::unique_ptr<codecs::ICodec> codec_;
+ std::unique_ptr<Timer> timer_;
+
+ bool is_new_stream_;
+ std::optional<StreamInfo::Format> current_input_format_;
+ std::optional<StreamInfo::Format> current_output_format_;
+
+ std::byte* sample_buffer_;
+ std::size_t sample_buffer_len_;
+};
} // namespace audio
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index 56f92fcf..a1b9689b 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -6,57 +6,130 @@
#pragma once
+#include <cstddef>
#include <cstdint>
#include <future>
#include <memory>
#include <string>
-#include <vector>
-
-#include "arena.hpp"
-#include "chunk.hpp"
-#include "freertos/FreeRTOS.h"
#include "ff.h"
-#include "freertos/message_buffer.h"
-#include "freertos/queue.h"
-#include "span.hpp"
-#include "track.hpp"
-#include "audio_element.hpp"
-#include "stream_buffer.hpp"
+#include "audio_source.hpp"
+#include "freertos/portmacro.h"
+#include "future_fetcher.hpp"
#include "stream_info.hpp"
+#include "tag_parser.hpp"
#include "types.hpp"
namespace audio {
-class FatfsAudioInput : public IAudioElement {
+/*
+ * Handles coordination with a persistent background task to asynchronously
+ * read files from disk into a StreamBuffer.
+ */
+class FileStreamer {
public:
- FatfsAudioInput();
- ~FatfsAudioInput();
+ FileStreamer(StreamBufferHandle_t dest, SemaphoreHandle_t first_read);
+ ~FileStreamer();
+
+ /*
+ * Continues reading data into the destination buffer until the destination
+ * is full.
+ */
+ auto Fetch() -> void;
+
+ /* Returns true if the streamer has run out of data from the current file. */
+ auto HasFinished() -> bool;
+
+ /*
+ * Clears any remaining buffered data, and begins reading again from the
+ * given file. This function respects any seeking/reading that has already
+ * been done on the new source file.
+ */
+ auto Restart(std::unique_ptr<FIL>) -> void;
+
+ FileStreamer(const FileStreamer&) = delete;
+ FileStreamer& operator=(const FileStreamer&) = delete;
+
+ private:
+ // Note: private methods here should only be called from the streamer's task.
+
+ auto Main() -> void;
+ auto CloseFile() -> void;
+
+ enum Command {
+ kRestart,
+ kRefillBuffer,
+ kQuit,
+ };
+ QueueHandle_t control_;
+ StreamBufferHandle_t destination_;
+ SemaphoreHandle_t data_was_read_;
+
+ std::atomic<bool> has_data_;
+ std::unique_ptr<FIL> file_;
+ std::unique_ptr<FIL> next_file_;
+};
- auto CurrentFile() -> std::optional<std::string> { return current_path_; }
- auto OpenFile(std::future<std::optional<std::string>>&& path) -> void;
- auto OpenFile(const std::string& path) -> bool;
+/*
+ * Audio source that fetches data from a FatFs (or exfat i guess) filesystem.
+ *
+ * All public methods are safe to call from any task.
+ */
+class FatfsAudioInput : public IAudioSource {
+ public:
+ explicit FatfsAudioInput(std::shared_ptr<database::ITagParser> tag_parser);
+ ~FatfsAudioInput();
- auto NeedsToProcess() const -> bool override;
+ /*
+ * Immediately cease reading any current source, and begin reading from the
+ * given file path.
+ */
+ auto SetPath(std::future<std::optional<std::string>>) -> void;
+ auto SetPath(const std::string&) -> void;
+ auto SetPath() -> void;
- auto Process(const std::vector<InputStream>& inputs, OutputStream* output)
- -> void override;
+ auto Read(std::function<bool(StreamInfo::Format)>,
+ std::function<size_t(cpp::span<const std::byte>)>,
+ TickType_t) -> void override;
FatfsAudioInput(const FatfsAudioInput&) = delete;
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
private:
+ // Note: private methods assume that the appropriate locks have already been
+ // acquired.
+
+ auto OpenFile(const std::string& path) -> void;
+ auto CloseCurrentFile() -> void;
+ auto HasDataRemaining() -> bool;
+
auto ContainerToStreamType(database::Encoding)
-> std::optional<codecs::StreamType>;
+ auto IsCurrentFormatMp3() -> bool;
+
+ std::shared_ptr<database::ITagParser> tag_parser_;
+
+ // Semaphore used to block when this source is out of data. This should be
+ // acquired before attempting to read data, and returned after each incomplete
+ // read.
+ SemaphoreHandle_t has_data_;
+
+ StreamBufferHandle_t streamer_buffer_;
+ std::unique_ptr<FileStreamer> streamer_;
+
+ StreamInfo file_buffer_info_;
+ std::size_t file_buffer_len_;
+ std::byte* file_buffer_;
+
+ RawStream file_buffer_stream_;
- std::optional<std::future<std::optional<std::string>>> pending_path_;
- std::optional<std::string> current_path_;
- FIL current_file_;
- bool is_file_open_;
- bool has_prepared_output_;
+ // Mutex guarding the current file/stream associated with this source. Must be
+ // held during readings, and before altering the current file.
+ std::mutex source_mutex_;
- std::optional<database::Encoding> current_container_;
+ std::unique_ptr<database::FutureFetcher<std::optional<std::string>>>
+ pending_path_;
std::optional<StreamInfo::Format> current_format_;
};
diff --git a/src/audio/include/stream_info.hpp b/src/audio/include/stream_info.hpp
index 69bf3c4b..00aa1110 100644
--- a/src/audio/include/stream_info.hpp
+++ b/src/audio/include/stream_info.hpp
@@ -15,6 +15,10 @@
#include <utility>
#include <variant>
+#include "freertos/FreeRTOS.h"
+#include "freertos/ringbuf.h"
+#include "freertos/stream_buffer.h"
+
#include "result.hpp"
#include "span.hpp"
#include "types.hpp"