diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp new file mode 100644 index 00000000..ff9f0d62 --- /dev/null +++ b/src/audio/audio_decoder.cpp @@ -0,0 +1,103 @@ +#include "audio_decoder.hpp" +#include +#include "esp_heap_caps.h" +#include "include/audio_element.hpp" +#include "include/fatfs_audio_input.hpp" + +namespace audio { + +static const TickType_t kMaxWaitTicks = portMAX_DELAY; + // TODO: could this be larger? depends on the codecs i guess + static const std::size_t kWorkingBufferSize = kMaxFrameSize; + + AudioDecoder::AudioDecoder() { + working_buffer_ = heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM); + } + + AudioDecoder::~AudioDecoder() { + free(working_buffer_); + } + + auto AudioDecoder::InputCommandQueue() -> QueueHandle_t { + return input_queue_; + } + + auto AudioDecoder::SetInputCommandQueue(QueueHandle_t queue) -> void { + input_queue_ = queue; + } + + auto AudioDecoder::SetOutputCommandQueue(QueueHandle_t queue) -> void { + output_queue_ = queue; + } + + auto AudioDecoder::InputBuffer() -> StreamBufferHandle_t { + return input_buffer_; + } + + auto AudioDecoder::SetInputBuffer(StreamBufferHandle_t buffer) -> void { + input_buffer_ = buffer; + } + + auto AudioDecoder::SetOutputBuffer(StreamBufferHandle_t buffer) -> void { + output_buffer_ = buffer; + } + + auto AudioDecoder::ProcessElementCommand(void* command) -> ProcessResult { + FatfsAudioInput::OutputCommand *real = std::reinterpret_cast(command); + + if (current_codec_->CanHandleExtension(real->extension)) { + // TODO: Do we need to reset the codec? + delete real; + return OK; + } + + auto result = codecs::CreateCodecForExtension(real->extension); + // TODO: handle error case + if (result.has_value()) { + current_codec_ = result.value(); + } + + delete real; + return OK; + } + + auto AudioDecoder::SkipElementCommand(void* command) -> void { + FatfsAudioInput::OutputCommand *real = std::reinterpret_cast(command); + delete real; + } + + auto AudioDecoder::ProcessData(uint8_t* data, uint16_t length) -> ProcessResult { + if (current_codec_ == nullptr) { + // TODO: signal this + return OK; + } + + auto result = current_codec_->Process(data, length, working_buffer_, kWorkingBufferSize); + if (result.has_value()) { + xStreamBufferSend(&output_buffer_, working_buffer_, result.value(), kMaxWaitTicks); + } else { + // TODO: handle i guess + return ERROR; + } + + return OK; + } + + auto AudioDecoder::ProcessIdle() -> ProcessResult { + // Not used. + return OK; + } + + auto AudioDecoder::Pause() -> void { + // TODO. + } + auto AudioDecoder::IsPaused() -> bool { + // TODO. + } + + auto AudioDecoder::Resume() -> void { + // TODO. + } + + +} // namespace audio diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index 1853431a..86eb4f4a 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -10,10 +10,12 @@ #include "freertos/stream_buffer.h" #include "audio_element.hpp" +#include "include/audio_element.hpp" namespace audio { static const TickType_t kCommandWaitTicks = 1; +static const TickType_t kIdleTaskDelay = 1; void audio_task(void* args) { AudioTaskArgs* real_args = reinterpret_cast(args); @@ -30,8 +32,16 @@ void audio_task(void* args) { while (1) { IAudioElement::Command command; + ProcessResult result; + if (!xQueueReceive(commands, &command, kCommandWaitTicks)) { - element->ProcessIdle(); + result = element->ProcessIdle(); + if (result == IAudioElement::ERROR) { + break; + } + if (result == IAudioElement::OUTPUT_FULL) { + vTaskDelay(kIdleTaskDelay); + } continue; }; @@ -39,7 +49,6 @@ void audio_task(void* args) { if (command.sequence_number > current_sequence_number) { current_sequence_number = command.sequence_number; } - continue; } @@ -49,7 +58,13 @@ void audio_task(void* args) { xStreamBufferReceive(stream, &frame_buffer, command.read_size, 0); if (command.sequence_number == current_sequence_number) { - element->ProcessData(frame_buffer, command.read_size); + result = element->ProcessData(frame_buffer, command.read_size); + if (result == IAudioElement::ERROR) { + break; + } + if (result == IAudioElement::OUTPUT_FULL) { + // TODO: Do we care about this? could just park indefinitely. + } } continue; @@ -58,7 +73,13 @@ void audio_task(void* args) { if (command.type == IAudioElement::ELEMENT) { assert(command.data != NULL); if (command.sequence_number == current_sequence_number) { - element->ProcessElementCommand(command.data); + result = element->ProcessElementCommand(command.data); + if (result == IAudioElement::ERROR) { + break; + } + if (result == IAudioElement::OUTPUT_FULL) { + // TODO: what does this mean lol + } } else { element->SkipElementCommand(command.data); } diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 1e8c35b8..df38f1d6 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -1,28 +1,42 @@ -#include "fatfs_audio_input.hpp" +#include "fatfs_audio_input.hppccc +#include #include -#include "esp-adf/components/input_key_service/include/input_key_service.h" #include "esp_heap_caps.h" #include "audio_element.hpp" +#include "freertos/portmacro.h" +#include "include/audio_element.hpp" namespace audio { -static const size_t kQueueItems = 0; -static constexpr size_t kQueueItemSize = sizeof(IAudioElement::Command); -static constexpr size_t kQueueSize = kQueueItems * kQueueItemSize; +static const TickType_t kMaxWaitTicks = portMAX_DELAY; -static const size_t kOutputBufferSize = 1024; +// Large output buffer size, so that we can keep a get as much of the input file +// into memory as soon as possible. +static constexpr std::size_t kOutputBufferSize = 1024 * 128; +static constexpr std::size_t kQueueItemSize = sizeof(IAudioElement::Command); +// Use a large enough command queue size that we can fit reads for the full +// buffer into the queue. +static constexpr std::size_t kOutputQueueItemNumber = kOutputBufferSize / kMaxFrameSize; +static constexpr std::size_t kOutputQueueSize = kOutputQueueItemNumber * kQueueItemSize; + +// This should be a relatively responsive element, so no need for a particularly +// large queue. +static constexpr std::size_t kInputQueueItemNumber = 4; +static constexpr std::size_t kInputQueueSize = kInputQueueItemNumber * kQueueItemSize; FatfsAudioInput::FatfsAudioInput(std::shared_ptr storage) : IAudioElement(), storage_(storage) { - input_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM); + working_buffer_ = heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_SPIRAM); + + input_queue_memory_ = heap_caps_malloc(kInputQueueSize, MALLOC_CAP_SPIRAM); input_queue_ = xQueueCreateStatic( - kQueueItems, kQueueItemSize, input_queue_memory_, &input_queue_metadata_); + kInputQueueItemNumber, kQueueItemSize, input_queue_memory_, &input_queue_metadata_); - output_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM); + output_queue_memory_ = heap_caps_malloc(kOutputQueueSize, MALLOC_CAP_SPIRAM); output_queue_ = - xQueueCreateStatic(kQueueItems, kQueueItemSize, output_queue_memory_, + xQueueCreateStatic(kOutputQueueItems, kQueueItemSize, output_queue_memory_, &output_queue_metadata_); output_buffer_memory_ = @@ -33,6 +47,7 @@ FatfsAudioInput::FatfsAudioInput(std::shared_ptr storage) } FatfsAudioInput::~FatfsAudioInput() { + free(working_buffer_); vStreamBufferDelete(output_buffer_); free(output_buffer_memory_); vQueueDelete(output_queue_); @@ -57,23 +72,84 @@ auto FatfsAudioInput::OutputBuffer() -> StreamBufferHandle_t { return output_buffer_; } -auto FatfsAudioInput::ProcessElementCommand(void* command) -> void { - InputCommand *real = std::reinterpret_pointer_cast(command); +auto FatfsAudioInput::ProcessElementCommand(void* command) -> ProcessResult { + InputCommand *real = std::reinterpret_cast(command); + + if (uxQueueSpacesAvailable(output_queue_) < 2) { + return OUTPUT_FULL; + } + + if (is_file_open_) { + f_close(¤t_file_); + } + + FRESULT res = f_open(¤t_file_, real->filename.c_str(), FA_READ); + if (res != FR_OK) { + delete real; + return ERROR; + } + + if (real->seek_to && f_lseek(¤t_file_, real->seek_to) { + return ERROR; + } + + is_file_open_ = true; + current_sequence_++; + + Command sequence_update; + sequence_update.type = SEQUENCE_NUMBER; + sequence_update.sequence_number = current_sequence_; - // TODO. + if (real->interrupt) { + xQueueSendToFront(output_queue_, &sequence_update, kMaxWaitTicks); + } else { + xQueueSendToBack(output_queue_, &sequence_update, kMaxWaitTicks); + } + + OutputCommand *data = new OutputCommand; + data->extension = "txt"; + Command file_info; + file_info.type = ELEMENT; + file_info.sequence_number = current_sequence_; + file_info.data = &data; + xQueueSendToBack(output_queue_, &file_info, kMaxWaitTicks); + + delete real; + return OK; } auto FatfsAudioInput::SkipElementCommand(void* command) -> void { - InputCommand *real = std::reinterpret_pointer_cast(command); + InputCommand *real = std::reinterpret_cast(command); delete real; } auto FatfsAudioInput::ProcessData(uint8_t* data, uint16_t length) -> void { - // Not implemented. + // Not used, since we have no input stream. } -auto FatfsAudioInput::ProcessIdle() -> void { - // TODO. +auto FatfsAudioInput::ProcessIdle() -> ProcessResult { + if (!is_file_open_) { + return OK; + } + + if (xStreamBufferSpacesAvailable(output_buffer) < kMaxFrameSize) { + return OUTPUT_FULL; + } + + UINT bytes_read = 0; + FRESULT result = f_read(¤t_file_, working_buffer_, kMaxFrameSize, &bytes_read); + if (!FR_OK) { + return ERROR; + } + + xStreamBufferSend(&output_buffer_, working_buffer_, bytes_read, kMaxWaitTicks); + + if (f_eof(¤t_file_)) { + f_close(¤t_file_); + is_file_open_ = false; + } + + return OK; } } // namespace audio diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp index f460f9e9..083bd564 100644 --- a/src/audio/include/audio_decoder.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -1,34 +1,36 @@ #pragma once #include +#include "audio_element.hpp" #include "ff.h" +#include "codec.hpp" namespace audio { -enum SampleRate {}; -enum BitDepth {}; - -struct PcmStreamHeader { - SampleRate sample_rate; - BitDepth bit_depth; - bool configure_now; -}; - -class AudioDecoder { +class AudioDecoder : public IAudioElement { public: AudioDecoder(); ~AudioDecoder(); - auto SetSource(RingbufHandle_t& source) -> void; + auto Pause() -> void; + auto IsPaused() -> bool; - enum Status {}; - auto ProcessChunk() -> Status; + auto Resume() -> void; - auto GetOutputStream() const -> RingbufHandle_t; + auto SetInputCommandQueue(QueueHandle_t) -> void; + auto SetOutputCommandQueue(QueueHandle_t) -> void; + auto SetInputBuffer(StreamBufferHandle_t) -> void; + auto SetOutputBuffer(StreamBufferHandle_t) -> void; private: - RingbufHandle_t input_; - RingbufHandle_t output_; + std::unique_ptr current_codec_; + + uint8_t *working_buffer_; + + QueueHandle_t input_queue_; + QueueHandle_t output_queue_; + StreamBufferHandle_t input_buffer_; + StreamBufferHandle_t output_buffer_; }; } // namespace audio diff --git a/src/audio/include/audio_element.hpp b/src/audio/include/audio_element.hpp index ea4256ac..03fefd70 100644 --- a/src/audio/include/audio_element.hpp +++ b/src/audio/include/audio_element.hpp @@ -33,6 +33,7 @@ class IAudioElement { struct Command { CommandType type; uint8_t sequence_number; + // TODO: tag data's type union { void* data; std::size_t frame_size; @@ -52,22 +53,28 @@ class IAudioElement { */ virtual auto InputBuffer() -> StreamBufferHandle_t = 0; + enum ProcessResult { + OK, + OUTPUT_FULL, + ERROR, + }; + /* * Called when an element-specific command has been received. */ - virtual auto ProcessElementCommand(void* command) -> void = 0; + virtual auto ProcessElementCommand(void* command) -> ProcessResult = 0; virtual auto SkipElementCommand(void* command) -> void = 0; /* * Called with the result of a read bytes command. */ - virtual auto ProcessData(uint8_t* data, uint16_t length) -> void = 0; + virtual auto ProcessData(uint8_t* data, uint16_t length) -> ProcessResult = 0; /* * Called periodically when there are no pending commands. */ - virtual auto ProcessIdle() -> void = 0; + virtual auto ProcessIdle() -> ProcessResult = 0; }; } // namespace audio diff --git a/src/audio/include/audio_output.hpp b/src/audio/include/audio_output.hpp index 82dca82d..9726c3b5 100644 --- a/src/audio/include/audio_output.hpp +++ b/src/audio/include/audio_output.hpp @@ -5,25 +5,25 @@ #include "audio_common.h" #include "audio_element.h" +#include "audio_element.hpp" -namespace drivers { +namespace audio { -class IAudioOutput { +class AudioOutput : IAudioElement { public: - IAudioOutput(audio_element_handle_t element) : element_(element) {} - virtual ~IAudioOutput() { audio_element_deinit(element_); } + AudioOutput(); + ~AudioOutput(); auto GetAudioElement() -> audio_element_handle_t { return element_; } - virtual auto SetVolume(uint8_t volume) -> void = 0; - virtual auto GetVolume() const -> uint8_t { return volume_; } + auto SetInputBuffer(StreamBufferHandle_t buffer) -> void; virtual auto Configure(audio_element_info_t& info) -> void = 0; virtual auto SetSoftMute(bool enabled) -> void = 0; - protected: + private: audio_element_handle_t element_; uint8_t volume_; }; -} // namespace drivers +} // namespace audio diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index ed4da55e..bf5f150d 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/queue.h" @@ -16,10 +17,12 @@ class FatfsAudioInput : public IAudioElement { public: struct InputCommand { std::string filename; + size_t seek_to; + bool interrupt; }; struct OutputCommand { - // TODO: does this actually need any special output? + std::string extension; }; FatfsAudioInput(std::shared_ptr storage); @@ -31,7 +34,11 @@ class FatfsAudioInput : public IAudioElement { private: std::shared_ptr storage_; - uint8_t current_sequence = 0; + uint8_t *working_buffer_; + + uint8_t current_sequence_ = 0; + FIL current_file_; + bool is_file_open_ = false; uint8_t* input_queue_memory_; StaticQueue_t input_queue_metadata_; diff --git a/src/codecs/codec.hpp b/src/codecs/codec.hpp index 24ba9cfe..5e8763a6 100644 --- a/src/codecs/codec.hpp +++ b/src/codecs/codec.hpp @@ -2,16 +2,28 @@ #include #include + +#include "result.hpp" + namespace codecs { - class IAudioDecoder { + enum CreateCodecError {}; + + auto CreateCodecForExtension(std::string extension) -> cpp::result, CreateCodecError>; + + class ICodec { public: - virtual ~IAudioDecoder() {} + virtual ~ICodec() {} + + virtual auto CanHandleExtension(std::string extension) -> bool = 0; + + enum Error {}; - virtual auto ProcessData( + virtual auto Process( uint8_t *input, - size_t input_len, - uint8_t *output) -> size_t = 0; + std::size_t input_len, + uint8_t *output, + std::size_t output_length) -> cpp::result = 0; }; } // namespace codecs diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 42b774ba..90c1742a 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -2,5 +2,5 @@ idf_component_register( SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" "audio_playback.cpp" "i2s_audio_output.cpp" "display.cpp" "display_init.cpp" "spi.cpp" INCLUDE_DIRS "include" - REQUIRES "esp_adc_cal" "fatfs" "audio_pipeline" "audio_stream" "result" "lvgl") + REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp index e1c576de..aa736793 100644 --- a/src/drivers/include/storage.hpp +++ b/src/drivers/include/storage.hpp @@ -6,6 +6,7 @@ #include "driver/sdspi_host.h" #include "esp_err.h" #include "esp_vfs_fat.h" +#include "ff.h" #include "result.hpp" #include "gpio_expander.hpp" @@ -38,6 +39,8 @@ class SdStorage { auto HandleTransaction(sdspi_dev_handle_t handle, sdmmc_command_t* cmdinfo) -> esp_err_t; + auto GetFs() -> FATFS*; + // Not copyable or movable. // TODO: maybe this could be movable? SdStorage(const SdStorage&) = delete; diff --git a/src/drivers/storage.cpp b/src/drivers/storage.cpp index 9ddd092d..2c2d7a5f 100644 --- a/src/drivers/storage.cpp +++ b/src/drivers/storage.cpp @@ -146,4 +146,8 @@ auto SdStorage::HandleTransaction(sdspi_dev_handle_t handle, return do_transaction_(handle, cmdinfo); } +auto SdStorage::GetFs() -> FATFS* { + return fs_; +} + } // namespace drivers