diff options
Diffstat (limited to 'src/audio/include')
| -rw-r--r-- | src/audio/include/audio_decoder.hpp | 7 | ||||
| -rw-r--r-- | src/audio/include/audio_events.hpp | 4 | ||||
| -rw-r--r-- | src/audio/include/audio_fsm.hpp | 6 | ||||
| -rw-r--r-- | src/audio/include/audio_sink.hpp | 32 | ||||
| -rw-r--r-- | src/audio/include/audio_source.hpp | 33 | ||||
| -rw-r--r-- | src/audio/include/audio_task.hpp | 45 | ||||
| -rw-r--r-- | src/audio/include/fatfs_audio_input.hpp | 125 | ||||
| -rw-r--r-- | src/audio/include/stream_info.hpp | 4 |
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" |
