diff --git a/CMakeLists.txt b/CMakeLists.txt index 751ee3ec..7f56397f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,3 @@ idf_build_set_property(COMPILE_OPTIONS "-DRESULT_DISABLE_EXCEPTIONS" APPEND) list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/src") project(gay-ipod-fw) -include($ENV{PROJ_PATH}/tools/cmake/extra-libs.cmake) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index e3f7dd33..32ca1a56 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -2,6 +2,6 @@ idf_component_register( SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" "stream_info.cpp" INCLUDE_DIRS "include" - REQUIRES "codecs" "drivers" "cbor" "result" "tasks") + REQUIRES "codecs" "drivers" "cbor_wrapper" "result" "tasks") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index 02217187..a19fd5bf 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -1,29 +1,41 @@ #include "audio_decoder.hpp" + #include + #include #include -#include "chunk.hpp" + +#include "freertos/FreeRTOS.h" + #include "esp_heap_caps.h" +#include "freertos/message_buffer.h" #include "freertos/portmacro.h" -#include "include/audio_element.hpp" -#include "include/fatfs_audio_input.hpp" + +#include "audio_element.hpp" +#include "chunk.hpp" +#include "fatfs_audio_input.hpp" + +static const char* kTag = "DEC"; namespace audio { AudioDecoder::AudioDecoder() : IAudioElement(), - chunk_buffer_(heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM)), - stream_info_({}) {} + stream_info_({}), + chunk_buffer_(static_cast( + heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM))) + +{} AudioDecoder::~AudioDecoder() { free(chunk_buffer_); } -auto AudioDecoder::SetInputBuffer(StreamBufferHandle_t* buffer) -> void { +auto AudioDecoder::SetInputBuffer(MessageBufferHandle_t* buffer) -> void { input_buffer_ = buffer; } -auto AudioDecoder::SetOutputBuffer(StreamBufferHandle_t* buffer) -> void { +auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void { output_buffer_ = buffer; } @@ -34,12 +46,12 @@ auto AudioDecoder::ProcessStreamInfo(StreamInfo&& info) // Reuse the existing codec if we can. This will help with gapless playback, // since we can potentially just continue to decode as we were before, // without any setup overhead. - if (current_codec_->CanHandleFile(info.path)) { + if (current_codec_->CanHandleFile(info.Path().value_or(""))) { current_codec_->ResetForNewStream(); return {}; } - auto result = codecs::CreateCodecForFile(info.path); + auto result = codecs::CreateCodecForFile(info.Path().value()); if (result.has_value()) { current_codec_ = std::move(result.value()); } else { @@ -57,26 +69,46 @@ auto AudioDecoder::ProcessChunk(uint8_t* data, std::size_t length) } current_codec_->SetInput(data, length); - cpp::result result; + + bool has_samples_to_send = false; + bool needs_more_input = false; + std::optional error = std::nullopt; WriteChunksToStream( - output_buffer_, working_buffer_, kWorkingBufferSize, - [&](uint8_t* buf, size_t len) { - result = current_codec_->Process(data, length, buf, len); - if (result.has_error()) { - // End our output stream immediately if the codec barfed. - return 0; + output_buffer_, chunk_buffer_, kMaxChunkSize, + [&](uint8_t* buf, size_t len) -> std::size_t { + std::size_t bytes_written = 0; + // Continue filling up the output buffer so long as we have samples + // leftover, or are able to synthesize more samples from the input. + while (has_samples_to_send || !needs_more_input) { + if (!has_samples_to_send) { + auto result = current_codec_->ProcessNextFrame(); + has_samples_to_send = true; + if (result.has_error()) { + error = result.error(); + // End our output stream immediately if the codec barfed. + return 0; + } else { + needs_more_input = result.value(); + } + } else { + auto result = current_codec_->WriteOutputSamples( + buf + bytes_written, len - bytes_written); + bytes_written += result.first; + has_samples_to_send = !result.second; + } } - return result.value(); + return bytes_written; }, // This element doesn't support any kind of out of band commands, so we // can just suspend the whole task if the output buffer fills up. portMAX_DELAY); - if (result.has_error()) { + if (error) { + ESP_LOGE(kTag, "Codec encountered error %d", error.value()); return cpp::fail(IO_ERROR); } - return current_codec_->GetOutputProcessed(); + return current_codec_->GetInputPosition(); } auto AudioDecoder::ProcessIdle() -> cpp::result { diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index a125548a..f3362897 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -13,7 +13,7 @@ #include "freertos/stream_buffer.h" #include "audio_element.hpp" -#include "include/audio_element.hpp" +#include "chunk.hpp" #include "stream_message.hpp" #include "tasks.hpp" @@ -25,72 +25,83 @@ static const size_t kChunkBufferSize = kMaxChunkSize * 1.5; auto StartAudioTask(const std::string& name, std::shared_ptr& element) -> void { - AudioTaskArgs* args = new AudioTaskArgs(element); + AudioTaskArgs* args = new AudioTaskArgs{.element = element}; xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args, kTaskPriorityAudio, NULL); } void AudioTaskMain(void* args) { - AudioTaskArgs* real_args = reinterpret_cast(args); - std::shared_ptr element = std::move(real_args->element); - delete real_args; - - ChunkReader chunk_reader = ChunkReader(element->InputBuffer()); - - while (1) { - cpp::result process_res; - - // If this element has an input stream, then our top priority is processing - // any chunks from it. Try doing this first, then fall back to the other - // cases. - bool has_received_message = false; - if (stream != nullptr) { - EncodeReadResult chunk_res = chunk_reader.ReadChunkFromStream( - [&](uint8_t* data, std::size_t length) -> std::optional { - process_res = element->ProcessChunk(data, length); - if (process_res.has_value()) { - return process_res.value(); - } else { - return {}; - } - }, - element->IdleTimeout()); - - if (chunk_res == CHUNK_PROCESSING_ERROR || - chunk_res == CHUNK_DECODING_ERROR) { - break; // TODO. - } else if (chunk_res == CHUNK_STREAM_ENDED) { - has_received_message = true; + { + AudioTaskArgs* real_args = reinterpret_cast(args); + std::shared_ptr element = std::move(real_args->element); + delete real_args; + + ChunkReader chunk_reader = ChunkReader(element->InputBuffer()); + + while (1) { + cpp::result process_res; + + // If this element has an input stream, then our top priority is + // processing any chunks from it. Try doing this first, then fall back to + // the other cases. + bool has_received_message = false; + if (element->InputBuffer() != nullptr) { + ChunkReadResult chunk_res = chunk_reader.ReadChunkFromStream( + [&](uint8_t* data, std::size_t length) -> std::optional { + process_res = element->ProcessChunk(data, length); + if (process_res.has_value()) { + return process_res.value(); + } else { + return {}; + } + }, + element->IdleTimeout()); + + if (chunk_res == CHUNK_PROCESSING_ERROR || + chunk_res == CHUNK_DECODING_ERROR) { + break; // TODO. + } else if (chunk_res == CHUNK_STREAM_ENDED) { + has_received_message = true; + } } - } - if (has_received_message) { - auto& [buffer, length] = chunk_reader.GetLastMessage(); - auto decoder_res = cbor::ArrayDecoder::Create(buffer, length); - if (decoder_res.has_error()) { - // TODO. - break; - } - auto decoder = decoder_res.value(); - MessageType message_type = decoder->NextValue(); - if (message_type == TYPE_STREAM_INFO) { - element->ProcessStreamInfo(StreamInfo(decoder->Iterator());); + if (has_received_message) { + auto [buffer, length] = chunk_reader.GetLastMessage(); + auto decoder_res = cbor::ArrayDecoder::Create(buffer, length); + if (decoder_res.has_error()) { + // TODO. + break; + } + auto decoder = std::move(decoder_res.value()); + // TODO: this can be more elegant i think + cpp::result message_type = + decoder->NextValue(); + if (message_type.has_error()) { + break; // TODO. + } else if (message_type.value() == TYPE_STREAM_INFO) { + auto info_decoder = cbor::MapDecoder::Create(decoder->Iterator()); + if (info_decoder.has_value()) { + auto process_error = element->ProcessStreamInfo( + StreamInfo(info_decoder.value().get())); + if (process_error.has_error()) { + break; // TODO. + } + } else { + break; // TODO. + } + } } - } - // TODO: Do any out of band reading, such a a pause command, here. + // TODO: Do any out of band reading, such a a pause command, here. - // Chunk reading must have timed out, or we don't have an input stream. - // Signal the element to do any of its idle tasks. - process_res = element->ProcessIdle(); - if (process_res.has_error()) { - break; // TODO. + // Chunk reading must have timed out, or we don't have an input stream. + // Signal the element to do any of its idle tasks. + auto process_error = element->ProcessIdle(); + if (process_error.has_error()) { + break; // TODO. + } } } - - element.clear(); - free(chunk_buffer_); - vTaskDelete(NULL); } diff --git a/src/audio/chunk.cpp b/src/audio/chunk.cpp index a157b946..718b2649 100644 --- a/src/audio/chunk.cpp +++ b/src/audio/chunk.cpp @@ -1,17 +1,20 @@ #include "chunk.hpp" #include + #include #include + +#include "cbor.h" + #include "cbor_decoder.hpp" #include "cbor_encoder.hpp" -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" #include "stream_message.hpp" namespace audio { // TODO: tune. -static const std::size_t kMaxChunkSize = 512; +const std::size_t kMaxChunkSize = 512; // TODO: tune static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5; @@ -28,7 +31,7 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream, uint8_t* working_buffer, size_t working_buffer_length, std::function callback, - TickType_t max_wait) -> EncodeWriteResult { + TickType_t max_wait) -> ChunkWriteResult { size_t header_size = kInitialHeaderSize; while (1) { // First, ask the callback for some data to write. @@ -43,9 +46,9 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream, // Put together a header. cbor::Encoder encoder(cbor::CONTAINER_ARRAY, 3, working_buffer, working_buffer_length); - encoder.WriteUnsigned(TYPE_CHUNK_HEADER); - encoder.WriteUnsigned(header_size); - encoder.WriteUnsigned(chunk_size); + encoder.WriteValue(TYPE_CHUNK_HEADER); + encoder.WriteValue(header_size); + encoder.WriteValue(chunk_size); size_t new_header_size = header_size; cpp::result encoder_res = encoder.Finish(); @@ -76,7 +79,8 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream, } ChunkReader::ChunkReader(MessageBufferHandle_t* stream) : stream_(stream) { - working_buffer_ = heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM); + working_buffer_ = static_cast( + heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM)); }; ChunkReader::~ChunkReader() { @@ -94,7 +98,7 @@ auto ChunkReader::GetLastMessage() -> std::pair { auto ChunkReader::ReadChunkFromStream( std::function(uint8_t*, size_t)> callback, - TickType_t max_wait) -> EncodeReadResult { + TickType_t max_wait) -> ChunkReadResult { // First, wait for a message to arrive over the buffer. last_message_size_ = xMessageBufferReceive(*stream_, working_buffer_ + leftover_bytes_, @@ -104,14 +108,15 @@ auto ChunkReader::ReadChunkFromStream( return CHUNK_READ_TIMEOUT; } - auto decoder = cbor::MapDecoder::Create(working_buffer_ + leftover_bytes_, - last_message_size_); + auto decoder = cbor::ArrayDecoder::Create(working_buffer_ + leftover_bytes_, + last_message_size_); if (decoder.has_error()) { // Weird; this implies someone is shoving invalid data into the buffer. return CHUNK_DECODING_ERROR; } - MessageType type = decoder.value().ParseUnsigned().value_or(TYPE_UNKNOWN); + MessageType type = static_cast( + decoder.value()->NextValue().value_or(TYPE_UNKNOWN)); if (type != TYPE_CHUNK_HEADER) { // This message wasn't for us, so let the caller handle it. Reset(); @@ -119,9 +124,9 @@ auto ChunkReader::ReadChunkFromStream( } // Work the size and position of the chunk. - header_length = decoder.ParseUnsigned().value_or(0); - chunk_length = decoder.ParseUnsigned().value_or(0); - if (decoder.Failed()) { + size_t header_length = decoder.value()->NextValue().value_or(0); + size_t chunk_length = decoder.value()->NextValue().value_or(0); + if (decoder.value()->Failed()) { return CHUNK_DECODING_ERROR; } @@ -146,7 +151,10 @@ auto ChunkReader::ReadChunkFromStream( if (leftover_bytes_ > 0) { memmove(working_buffer_, combined_buffer + amount_processed.value(), leftover_bytes_); + return CHUNK_LEFTOVER_DATA; } + + return CHUNK_READ_OKAY; } } // namespace audio diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 351fd017..dee090d0 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -1,14 +1,15 @@ -#include "fatfs_audio_input.hppccc +#include "fatfs_audio_input.hpp" + #include #include -#include "chunk.hpp" -#include "fatfs_audio_input.hpp" +#include +#include "audio_element.hpp" #include "esp_heap_caps.h" +#include "freertos/portmacro.h" #include "audio_element.hpp" -#include "freertos/portmacro.h" -#include "include/audio_element.hpp" +#include "chunk.hpp" namespace audio { @@ -20,14 +21,17 @@ static const std::size_t kOutputBufferSize = 1024 * 4; FatfsAudioInput::FatfsAudioInput(std::shared_ptr storage) : IAudioElement(), storage_(storage) { - file_buffer_ = heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_SPIRAM); + file_buffer_ = static_cast( + heap_caps_malloc(kFileBufferSize, MALLOC_CAP_SPIRAM)); file_buffer_read_pos_ = file_buffer_; file_buffer_write_pos_ = file_buffer_; - chunk_buffer_ = heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM); + chunk_buffer_ = + static_cast(heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM)); - output_buffer_memory_ = - heap_caps_malloc(kOutputBufferSize, MALLOC_CAP_SPIRAM); - output_buffer_ = xMessageBufferCreateStatic( + output_buffer_memory_ = static_cast( + heap_caps_malloc(kOutputBufferSize, MALLOC_CAP_SPIRAM)); + output_buffer_ = new MessageBufferHandle_t; + *output_buffer_ = xMessageBufferCreateStatic( kOutputBufferSize, output_buffer_memory_, &output_buffer_metadata_); } @@ -36,24 +40,18 @@ FatfsAudioInput::~FatfsAudioInput() { free(chunk_buffer_); vMessageBufferDelete(output_buffer_); free(output_buffer_memory_); + free(output_buffer_); } -auto FatfsAudioInput::InputBuffer() -> MessageBufferHandle_t { - return input_buffer_; -} - -auto FatfsAudioInput::OutputBuffer() -> MessageBufferHandle_t { - return output_buffer_; -} - -auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info) +auto FatfsAudioInput::ProcessStreamInfo(StreamInfo&& info) -> cpp::result { if (is_file_open_) { f_close(¤t_file_); is_file_open_ = false; } - FRESULT res = f_open(¤t_file_, info.path.c_str(), FA_READ); + std::string path = info.Path().value_or(""); + FRESULT res = f_open(¤t_file_, path.c_str(), FA_READ); if (res != FR_OK) { return cpp::fail(IO_ERROR); } @@ -66,12 +64,12 @@ auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info) } auto FatfsAudioInput::ProcessChunk(uint8_t* data, std::size_t length) - -> cpp::result { + -> cpp::result { // TODO. - return {}; + return 0; } -static auto GetRingBufferDistance() -> size_t { +auto FatfsAudioInput::GetRingBufferDistance() -> size_t { if (file_buffer_read_pos_ == file_buffer_write_pos_) { return 0; } @@ -82,10 +80,10 @@ static auto GetRingBufferDistance() -> size_t { // Read position to end of buffer. (file_buffer_ + kFileBufferSize - file_buffer_read_pos_) // Start of buffer to write position. - + (file_buffer_write_pos_ - file_buffer_) + + (file_buffer_write_pos_ - file_buffer_); } -virtual auto FatfsAudioInput::ProcessIdle() -> cpp::result { +auto FatfsAudioInput::ProcessIdle() -> cpp::result { // First, see if we're able to fill up the input buffer with any more of the // file's contents. if (is_file_open_) { @@ -103,8 +101,8 @@ virtual auto FatfsAudioInput::ProcessIdle() -> cpp::result { UINT bytes_read = 0; FRESULT result = f_read(¤t_file_, file_buffer_write_pos_, read_size, &bytes_read); - if (!FR_OK) { - return ERROR; // TODO; + if (result != FR_OK) { + return cpp::fail(IO_ERROR); // TODO; } if (f_eof(¤t_file_)) { @@ -123,16 +121,16 @@ virtual auto FatfsAudioInput::ProcessIdle() -> cpp::result { // Now stream data into the output buffer until it's full. pending_read_pos_ = nullptr; - EncodeWriteResult result = - WriteChunksToStream(&output_buffer_, chunk_buffer_, kMaxChunkSize, - &SendChunk, kServiceInterval); + ChunkWriteResult result = WriteChunksToStream( + output_buffer_, chunk_buffer_, kMaxChunkSize, + [&](uint8_t* b, size_t s) { return SendChunk(b, s); }, kServiceInterval); switch (result) { case CHUNK_WRITE_TIMEOUT: case CHUNK_OUT_OF_DATA: - return; // TODO. + return {}; // TODO. default: - return; // TODO. + return {}; // TODO. } } diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp index 98ebdc71..a4508c3e 100644 --- a/src/audio/include/audio_decoder.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -18,8 +18,13 @@ class AudioDecoder : public IAudioElement { AudioDecoder(); ~AudioDecoder(); - auto SetInputBuffer(StreamBufferHandle_t*) -> void; - auto SetOutputBuffer(StreamBufferHandle_t*) -> void; + auto SetInputBuffer(MessageBufferHandle_t*) -> void; + auto SetOutputBuffer(MessageBufferHandle_t*) -> void; + + auto ProcessStreamInfo(StreamInfo&& info) -> cpp::result; + auto ProcessChunk(uint8_t* data, std::size_t length) + -> cpp::result; + auto ProcessIdle() -> cpp::result; AudioDecoder(const AudioDecoder&) = delete; AudioDecoder& operator=(const AudioDecoder&) = delete; diff --git a/src/audio/include/audio_element.hpp b/src/audio/include/audio_element.hpp index 2a2f0727..13a48590 100644 --- a/src/audio/include/audio_element.hpp +++ b/src/audio/include/audio_element.hpp @@ -1,13 +1,28 @@ #pragma once #include + #include + +#include "freertos/FreeRTOS.h" + +#include "freertos/message_buffer.h" #include "freertos/portmacro.h" #include "result.hpp" + +#include "stream_info.hpp" #include "types.hpp" namespace audio { +/* Errors that may be returned by any of the Process* methods. */ +enum StreamError { + // Indicates that this element is unable to handle the upcoming chunks. + UNSUPPORTED_STREAM, + // Indicates an error with reading or writing stream data. + IO_ERROR, +}; + /* * One indepedentent part of an audio pipeline. Each element has an input and * output message stream, and is responsible for taking data from the input @@ -46,14 +61,6 @@ class IAudioElement { /* Returns this element's output buffer. */ auto OutputBuffer() -> MessageBufferHandle_t* { return output_buffer_; } - /* Errors that may be returned by any of the Process* methods. */ - enum StreamError { - // Indicates that this element is unable to handle the upcoming chunks. - UNSUPPORTED_STREAM, - // Indicates an error with reading or writing stream data. - IO_ERROR, - }; - /* * Called when a StreamInfo message is received. Used to configure this * element in preperation for incoming chunks. @@ -78,8 +85,8 @@ class IAudioElement { virtual auto ProcessIdle() -> cpp::result = 0; protected: - StreamBufferHandle_t* input_buffer_; - StreamBufferHandle_t* output_buffer_; + MessageBufferHandle_t* input_buffer_; + MessageBufferHandle_t* output_buffer_; }; } // namespace audio diff --git a/src/audio/include/chunk.hpp b/src/audio/include/chunk.hpp index a3f943ea..365c83d0 100644 --- a/src/audio/include/chunk.hpp +++ b/src/audio/include/chunk.hpp @@ -4,8 +4,13 @@ #include #include #include -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" + +#include "freertos/FreeRTOS.h" + +#include "cbor.h" +#include "freertos/message_buffer.h" #include "freertos/portmacro.h" +#include "freertos/queue.h" #include "result.hpp" namespace audio { @@ -35,7 +40,22 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream, uint8_t* working_buffer, size_t working_buffer_length, std::function callback, - TickType_t max_wait) -> EncodeWriteResult; + TickType_t max_wait) -> ChunkWriteResult; + +enum ChunkReadResult { + CHUNK_READ_OKAY, + // Returned when the chunk was read successfully, but the consumer did not + // use all of the data. + CHUNK_LEFTOVER_DATA, + // Returned an error in parsing the cbor-encoded header. + CHUNK_DECODING_ERROR, + // Returned when max_wait expired before any data was read. + CHUNK_READ_TIMEOUT, + // Returned when a non-chunk message is received. + CHUNK_STREAM_ENDED, + // Returned when the processing callback does not return a value. + CHUNK_PROCESSING_ERROR, +}; class ChunkReader { public: @@ -46,17 +66,6 @@ class ChunkReader { auto GetLastMessage() -> std::pair; - enum ChunkReadResult { - // Returned an error in parsing the cbor-encoded header. - CHUNK_DECODING_ERROR, - // Returned when max_wait expired before any data was read. - CHUNK_READ_TIMEOUT, - // Returned when a non-chunk message is received. - CHUNK_STREAM_ENDED, - // Returned when the processing callback does not return a value. - CHUNK_PROCESSING_ERROR, - }; - /* * Reads chunks of data from the given input stream, and invokes the given * callback to process each of them in turn. @@ -71,7 +80,7 @@ class ChunkReader { */ auto ReadChunkFromStream( std::function(uint8_t*, size_t)> callback, - TickType_t max_wait) -> EncodeReadResult; + TickType_t max_wait) -> ChunkReadResult; private: MessageBufferHandle_t* stream_; diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index 0fc2729f..6fe178ab 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -5,8 +5,9 @@ #include #include "freertos/FreeRTOS.h" + +#include "freertos/message_buffer.h" #include "freertos/queue.h" -#include "freertos/stream_buffer.h" #include "audio_element.hpp" #include "storage.hpp" @@ -18,11 +19,16 @@ class FatfsAudioInput : public IAudioElement { FatfsAudioInput(std::shared_ptr storage); ~FatfsAudioInput(); - auto OutputBuffer() -> MessageBufferHandle_t; + auto ProcessStreamInfo(StreamInfo&& info) -> cpp::result; + auto ProcessChunk(uint8_t* data, std::size_t length) + -> cpp::result; + auto ProcessIdle() -> cpp::result; auto SendChunk(uint8_t* buffer, size_t size) -> size_t; private: + auto GetRingBufferDistance() -> size_t; + std::shared_ptr storage_; uint8_t* file_buffer_; @@ -39,7 +45,6 @@ class FatfsAudioInput : public IAudioElement { uint8_t* output_buffer_memory_; StaticMessageBuffer_t output_buffer_metadata_; - MessageBufferHandle_t output_buffer_; }; } // namespace audio diff --git a/src/audio/include/stream_info.hpp b/src/audio/include/stream_info.hpp index bf5d4c60..ca28dd4e 100644 --- a/src/audio/include/stream_info.hpp +++ b/src/audio/include/stream_info.hpp @@ -3,9 +3,13 @@ #include #include #include -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" + +#include "cbor.h" #include "result.hpp" +#include "cbor_decoder.hpp" +#include "cbor_encoder.hpp" + namespace audio { class StreamInfo { @@ -18,7 +22,7 @@ class StreamInfo { static auto Create(const uint8_t* buffer, size_t length) -> cpp::result; - StreamInfo(CborValue& map); + StreamInfo(cbor::MapDecoder*); StreamInfo() = default; StreamInfo(const StreamInfo&) = default; @@ -34,11 +38,7 @@ class StreamInfo { return sample_rate_; } - enum EncodeError { - OUT_OF_MEMORY, - }; - - auto WriteToMap(CborEncoder encoder) -> cpp::result; + auto WriteToMap(cbor::Encoder& encoder) -> cpp::result; private: std::optional path_; diff --git a/src/audio/stream_info.cpp b/src/audio/stream_info.cpp index 6011571d..a5f7bebf 100644 --- a/src/audio/stream_info.cpp +++ b/src/audio/stream_info.cpp @@ -1,26 +1,31 @@ #include "stream_info.hpp" + #include +#include + +#include "cbor.h" + #include "cbor_decoder.hpp" -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" +#include "cbor_encoder.hpp" #include "stream_message.hpp" namespace audio { -static const char* kKeyPath = "p"; -static const char* kKeyChannels = "c"; -static const char* kKeyBitsPerSample = "b"; -static const char* kKeySampleRate = "r"; +static const std::string kKeyPath = "p"; +static const std::string kKeyChannels = "c"; +static const std::string kKeyBitsPerSample = "b"; +static const std::string kKeySampleRate = "r"; -static auto StreamInfo::Create(const uint8_t* buffer, size_t length) +auto StreamInfo::Create(const uint8_t* buffer, size_t length) -> cpp::result { CborParser parser; CborValue value; - cbor_parser_init(buffer, len, 0, &parser, &value); + cbor_parser_init(buffer, length, 0, &parser, &value); - uint8_t type = 0; - if (!cbor_value_is_integer(&value) || - !cbor_value_get_integer(&value, &type) || type != STREAM_INFO) { + int type = 0; + if (!cbor_value_is_integer(&value) || !cbor_value_get_int(&value, &type) || + type != TYPE_STREAM_INFO) { return cpp::fail(WRONG_TYPE); } @@ -30,26 +35,28 @@ static auto StreamInfo::Create(const uint8_t* buffer, size_t length) return cpp::fail(MISSING_MAP); } - return StreamInfo(value); + auto map_decoder = cbor::MapDecoder::Create(value); + if (map_decoder.has_value()) { + return StreamInfo(map_decoder.value().get()); + } + return cpp::fail(CBOR_ERROR); } -StreamInfo::StreamInfo(CborValue& map) { +StreamInfo::StreamInfo(cbor::MapDecoder* decoder) { // TODO: this method is n^2, which seems less than ideal. But you don't do it // that frequently, so maybe it's okay? Needs investigation. - cbor::MapDecoder decoder(map); - channels_ = decoder.FindValue(kKeyChannels); - bits_per_sample_ = decoder.FindValue(kKeyBitsPerSample); - sample_rate_ = decoder.FindValue(kKeySampleRate); - path_ = decoder.FindValue(kKeyPath); + channels_ = decoder->FindValue(kKeyChannels); + bits_per_sample_ = decoder->FindValue(kKeyBitsPerSample); + sample_rate_ = decoder->FindValue(kKeySampleRate); + path_ = decoder->FindValue(kKeyPath); } auto StreamInfo::WriteToMap(cbor::Encoder& map_encoder) - -> cpp::result { - CborEncoder map; - map_encoder.WriteKeyValue(kKeyChannels, channels_); - map_encoder.WriteKeyValue(kKeyBitsPerSample, bits_per_sample_); - map_encoder.WriteKeyValue(kKeySampleRate, sample_rate_); - map_encoder.WriteKeyValue(kKeyPath, path_); + -> cpp::result { + map_encoder.WriteKeyValue(kKeyChannels, channels_); + map_encoder.WriteKeyValue(kKeyBitsPerSample, bits_per_sample_); + map_encoder.WriteKeyValue(kKeySampleRate, sample_rate_); + map_encoder.WriteKeyValue(kKeyPath, path_); return map_encoder.Finish(); } diff --git a/src/cbor/cbor_decoder.cpp b/src/cbor/cbor_decoder.cpp deleted file mode 100644 index 20696afb..00000000 --- a/src/cbor/cbor_decoder.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "cbor_decoder.hpp" -#include -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" -#include "include/cbor_decoder.hpp" - -namespace cbor { - -static auto ArrayDecoder::Create(uint8_t* buffer, size_t buffer_len) - -> cpp::result, CborError> { - auto decoder = std::make_unique(); - cbor_parser_init(buffer, buffer_len, &decoder->parser_, &decoder->root_); - if (!cbor_value_is_array(&decoder->root_)) { - return cpp::fail(CborErrorIllegalType); - } - CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); - if (err != CborNoError) { - return cpp::fail(err); - } - return std::move(decoder); -} - -static auto ArrayDecoder::Create(CborValue& root) - -> cpp::result, CborError> { - auto decoder = std::make_unique(); - decoder->root_ = root; - if (!cbor_value_is_array(&decoder->root_)) { - return cpp::fail(CborErrorIllegalType); - } - - CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); - if (err != CborNoError) { - return cpp::fail(err); - } - return std::move(decoder); -} - -static auto MapDecoder::Create(uint8_t* buffer, size_t buffer_len) - -> cpp::result, CborError> { - auto decoder = std::make_unique(); - cbor_parser_init(buffer, buffer_len, &decoder->parser_, &decoder->root_); - if (!cbor_value_is_map(&decoder->root_)) { - return cpp::fail(CborErrorIllegalType); - } - CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); - if (err != CborNoError) { - return cpp::fail(err); - } - return std::move(decoder); -} - -static auto MapDecoder::Create(CborValue& root) - -> cpp::result, CborError> { - auto decoder = std::make_unique(); - decoder->root_ = root; - if (!cbor_value_is_map(&decoder->root_)) { - return cpp::fail(CborErrorIllegalType); - } - CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); - if (err != CborNoError) { - return cpp::fail(err); - } - return std::move(decoder); -} - -auto MapDecoder::FindString(const std::string& key) - -> std::optional { - CborValue val; - if (error_ != CborNoError) { - return {}; - } - if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { - return {}; - } - if (!cbor_value_is_byte_string(&val)) { - error_ = CborErrorIllegalType; - return {}; - } - uint8_t* buf; - size_t len; - error_ = cbor_value_dup_byte_string(&val, &buf, &len, NULL); - if (error_ != CborNoError) { - return cpp::fail(error_); - } - std::string ret(buf, len); - free(buf); - return ret; -} - -auto MapDecoder::FindUnsigned(const std::string& key) - -> std::optional { - CborValue val; - if (error_ != CborNoError) { - return {}; - } - if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { - return {}; - } - if (!cbor_value_is_unsigned_integer(&val)) { - error_ = CborErrorIllegalType; - return {}; - } - uint64_t ret; - error_ = cbor_value_get_uint64(&val, &ret); - if (error_ != CborNoError) { - return cpp::fail(error_); - } - return ret; -} - -auto MapDecoder::FindSigned(const std::string& key) -> std::optional { - CborValue val; - if (error_ != CborNoError) { - return {}; - } - if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { - // Don't store this as an error; missing keys are fine. - return {}; - } - if (!cbor_value_is_integer(&val)) { - error_ = CborErrorIllegalType; - return {}; - } - int32_t ret; - error_ = cbor_value_get_int(&val, &ret); - if (error_ != CborNoError) { - return cpp::fail(error_); - } - return ret; -} - -} // namespace cbor diff --git a/src/cbor/CMakeLists.txt b/src/cbor_wrapper/CMakeLists.txt similarity index 100% rename from src/cbor/CMakeLists.txt rename to src/cbor_wrapper/CMakeLists.txt diff --git a/src/cbor_wrapper/cbor_decoder.cpp b/src/cbor_wrapper/cbor_decoder.cpp new file mode 100644 index 00000000..a99cca02 --- /dev/null +++ b/src/cbor_wrapper/cbor_decoder.cpp @@ -0,0 +1,112 @@ +#include "cbor_decoder.hpp" + +#include +#include + +#include "cbor.h" +#include "result.hpp" + +static const int kDecoderFlags = 0; + +namespace cbor { + +auto parse_stdstring(const CborValue* val, std::string* out) -> CborError { + char* buf; + size_t len; + CborError err = cbor_value_dup_text_string(val, &buf, &len, NULL); + if (err != CborNoError) { + return err; + } + *out = std::string(buf, len); + free(buf); + return err; +} + +auto ArrayDecoder::Create(uint8_t* buffer, size_t buffer_len) + -> cpp::result, CborError> { + auto decoder = std::make_unique(); + cbor_parser_init(buffer, buffer_len, kDecoderFlags, &decoder->parser_, + &decoder->root_); + if (!cbor_value_is_array(&decoder->root_)) { + return cpp::fail(CborErrorIllegalType); + } + CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); + if (err != CborNoError) { + return cpp::fail(err); + } + return std::move(decoder); +} + +auto ArrayDecoder::Create(CborValue& root) + -> cpp::result, CborError> { + auto decoder = std::make_unique(); + decoder->root_ = root; + if (!cbor_value_is_array(&decoder->root_)) { + return cpp::fail(CborErrorIllegalType); + } + + CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); + if (err != CborNoError) { + return cpp::fail(err); + } + return std::move(decoder); +} + +template <> +auto ArrayDecoder::NextValue() -> cpp::result { + return NextValue(&cbor_value_is_integer, &cbor_value_get_int); +} +template <> +auto ArrayDecoder::NextValue() -> cpp::result { + return NextValue(&cbor_value_is_unsigned_integer, &cbor_value_get_uint64); +} +template <> +auto ArrayDecoder::NextValue() -> cpp::result { + return NextValue(&cbor_value_is_byte_string, &parse_stdstring); +} + +auto MapDecoder::Create(uint8_t* buffer, size_t buffer_len) + -> cpp::result, CborError> { + auto decoder = std::make_unique(); + cbor_parser_init(buffer, buffer_len, kDecoderFlags, &decoder->parser_, + &decoder->root_); + if (!cbor_value_is_map(&decoder->root_)) { + return cpp::fail(CborErrorIllegalType); + } + CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); + if (err != CborNoError) { + return cpp::fail(err); + } + return std::move(decoder); +} + +auto MapDecoder::Create(CborValue& root) + -> cpp::result, CborError> { + auto decoder = std::make_unique(); + decoder->root_ = root; + if (!cbor_value_is_map(&decoder->root_)) { + return cpp::fail(CborErrorIllegalType); + } + CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_); + if (err != CborNoError) { + return cpp::fail(err); + } + return std::move(decoder); +} + +template <> +auto MapDecoder::FindValue(const std::string& key) -> std::optional { + return FindValue(key, &cbor_value_is_integer, &cbor_value_get_int); +} +template <> +auto MapDecoder::FindValue(const std::string& key) -> std::optional { + return FindValue(key, &cbor_value_is_unsigned_integer, + &cbor_value_get_uint64); +} +template <> +auto MapDecoder::FindValue(const std::string& key) + -> std::optional { + return FindValue(key, &cbor_value_is_byte_string, &parse_stdstring); +} + +} // namespace cbor diff --git a/src/cbor/cbor_encoder.cpp b/src/cbor_wrapper/cbor_encoder.cpp similarity index 62% rename from src/cbor/cbor_encoder.cpp rename to src/cbor_wrapper/cbor_encoder.cpp index 6940917e..e4e8ee84 100644 --- a/src/cbor/cbor_encoder.cpp +++ b/src/cbor_wrapper/cbor_encoder.cpp @@ -1,6 +1,11 @@ #include "cbor_encoder.hpp" + #include -#include "esp-idf/components/cbor/tinycbor/src/cbor.h" +#include + +#include "cbor.h" +#include "cbor_decoder.hpp" +#include "result.hpp" namespace cbor { @@ -10,15 +15,15 @@ Encoder::Encoder(ContainerType type, uint32_t container_len, uint8_t* buffer, size_t buffer_len) { - cbor_encoder_init(&root_encoder, buffer, buffer_len, kEncoderFlags); + cbor_encoder_init(&root_encoder_, buffer, buffer_len, kEncoderFlags); switch (type) { case CONTAINER_ARRAY: - error_ = cbor_encoder_create_array(&encoder, &container_encoder_, + error_ = cbor_encoder_create_array(&root_encoder_, &container_encoder_, container_len); break; case CONTAINER_MAP: - error_ = - cbor_encoder_create_map(&encoder, &container_encoder_, container_len); + error_ = cbor_encoder_create_map(&root_encoder_, &container_encoder_, + container_len); break; } } @@ -28,7 +33,7 @@ auto Encoder::WriteValue(const std::string& val) -> void { return; } error_ = - cbor_encode_byte_string(&container_encoder_, val.c_str(), val.size()); + cbor_encode_text_string(&container_encoder_, val.c_str(), val.size()); } auto Encoder::WriteValue(uint32_t val) -> void { @@ -46,15 +51,13 @@ auto Encoder::WriteValue(int32_t val) -> void { } auto Encoder::Finish() -> cpp::result { + if (error_ == CborNoError) { + error_ = cbor_encoder_close_container(&root_encoder_, &container_encoder_); + } if (error_ != CborNoError) { return cpp::fail(error_); } - if (CborError final_error = - cbor_encoder_close_container(&root_encoder, &container_encoder_) != - CborNoError) { - return cpp::fail(final_error); - } - return cbor_encoder_get_buffer_size(&root_encoder); + return cbor_encoder_get_buffer_size(&root_encoder_, buffer_); } } // namespace cbor diff --git a/src/cbor/include/cbor_decoder.hpp b/src/cbor_wrapper/include/cbor_decoder.hpp similarity index 59% rename from src/cbor/include/cbor_decoder.hpp rename to src/cbor_wrapper/include/cbor_decoder.hpp index 879a1efa..193e7843 100644 --- a/src/cbor/include/cbor_decoder.hpp +++ b/src/cbor_wrapper/include/cbor_decoder.hpp @@ -3,23 +3,13 @@ #include #include +#include #include "cbor.h" +#include "result.hpp" namespace cbor { -static auto parse_stdstring(CborValue* val, std::string* out) -> CborError { - uint8_t* buf; - size_t len; - CborError err = cbor_value_dup_byte_string(val, &buf, &len, NULL); - if (err != CborNoError) { - return err; - } - *out = std::move(std::string(buf, len)); - free(buf); - return err -} - class ArrayDecoder { public: static auto Create(uint8_t* buffer, size_t buffer_len) @@ -28,25 +18,14 @@ class ArrayDecoder { static auto Create(CborValue& root) -> cpp::result, CborError>; + ArrayDecoder() {} + template auto NextValue() -> cpp::result; - template <> - auto NextValue() -> cpp::result { - return NextValue(&cbor_value_is_integer, &cbor_value_get_int); - } - template <> - auto NextValue() -> cpp::result { - return NextValue(&cbor_value_is_unsigned_integer, &cbor_value_get_uint64); - } - template <> - auto NextValue() -> cpp::result { - return NextValue(&cbor_value_is_byte_string, &parse_stdstring); - } - template - auto NextValue(bool (*is_valid)(CborValue*), - CborError (*parse)(CborValue*, T*)) + auto NextValue(bool (*is_valid)(const CborValue*), + CborError (*parse)(const CborValue*, T*)) -> cpp::result { if (error_ != CborNoError) { return cpp::fail(error_); @@ -90,30 +69,19 @@ class MapDecoder { static auto Create(CborValue& root) -> cpp::result, CborError>; + MapDecoder() {} + template auto FindValue(const std::string& key) -> std::optional; - template <> - auto FindValue(const std::string& key) -> std::optional { - return FindValue(key, &cbor_value_is_integer, &cbor_value_get_int); - } - template <> - auto FindValue(const std::string& key) -> std::optional { - return FindValue(key, &cbor_value_is_unsigned_integer, - &cbor_value_get_uint64); - } - template <> - auto FindValue(const std::string& key) -> std::optional { - return FindValue(key, &cbor_value_is_byte_string, &parse_stdstring); - } - template auto FindValue(const std::string& key, - bool (*is_valid)(CborValue*), - CborError (*parse)(CborValue*, T*)) -> std::optional { + bool (*is_valid)(const CborValue*), + CborError (*parse)(const CborValue*, T*)) -> std::optional { if (error_ != CborNoError) { return {}; } + CborValue val; if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { return {}; } @@ -124,7 +92,7 @@ class MapDecoder { T ret; error_ = parse(&val, &ret); if (error_ != CborNoError) { - return cpp::fail(error_); + return {}; } return ret; } diff --git a/src/cbor/include/cbor_encoder.hpp b/src/cbor_wrapper/include/cbor_encoder.hpp similarity index 64% rename from src/cbor/include/cbor_encoder.hpp rename to src/cbor_wrapper/include/cbor_encoder.hpp index 8f5214f6..cc57e8a4 100644 --- a/src/cbor/include/cbor_encoder.hpp +++ b/src/cbor_wrapper/include/cbor_encoder.hpp @@ -1,28 +1,40 @@ #pragma once #include +#include +#include #include "cbor.h" +#include "result.hpp" namespace cbor { +enum ContainerType { CONTAINER_ARRAY, CONTAINER_MAP }; + class Encoder { public: - enum ContainerType { CONTAINER_ARRAY, CONTAINER_MAP }; Encoder(ContainerType type, uint32_t container_len, uint8_t* buffer, size_t buffer_len); + auto WriteValue(const std::string& val) -> void; + auto WriteValue(uint32_t val) -> void; + auto WriteValue(int32_t val) -> void; + template - auto WriteKeyValue(const std::string& key, const T& val) -> void { + auto WriteKeyValue(const std::string& key, const T&& val) -> void { WriteValue(key); WriteValue(val); } - auto WriteValue(const std::string& val) -> void; - auto WriteValue(uint32_t val) -> void; - auto WriteValue(int32_t val) -> void; + template + auto WriteKeyValue(const std::string& key, const std::optional& val) + -> void { + if (val) { + WriteKeyValue(key, val.value()); + } + } auto Finish() -> cpp::result; @@ -30,6 +42,7 @@ class Encoder { Encoder& operator=(const Encoder&) = delete; private: + uint8_t* buffer_; CborEncoder root_encoder_; CborEncoder container_encoder_; diff --git a/src/codecs/CMakeLists.txt b/src/codecs/CMakeLists.txt index 4a8918ae..4f89f370 100644 --- a/src/codecs/CMakeLists.txt +++ b/src/codecs/CMakeLists.txt @@ -1,6 +1,8 @@ idf_component_register( SRCS "codec.cpp" "mad.cpp" - INCLUDE_DIRS "include") + INCLUDE_DIRS "include" + REQUIRES "result") -target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) add_dependencies("${COMPONENT_LIB}" libmad) +target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS}) +target_include_directories("${COMPONENT_LIB}" PRIVATE "${LIBMAD_INCLUDE}") diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp index db8d69b9..2a66b5f1 100644 --- a/src/codecs/codec.cpp +++ b/src/codecs/codec.cpp @@ -1,10 +1,13 @@ #include "codec.hpp" +#include +#include "mad.hpp" + namespace codecs { auto CreateCodecForExtension(std::string extension) -> cpp::result, CreateCodecError> { - return cpp::fail(UNKNOWN_EXTENSION); + return std::make_unique(); // TODO. } } // namespace codecs diff --git a/src/codecs/include/codec.hpp b/src/codecs/include/codec.hpp index 8e82bd71..764b63fc 100644 --- a/src/codecs/include/codec.hpp +++ b/src/codecs/include/codec.hpp @@ -1,18 +1,17 @@ #pragma once #include + #include #include +#include +#include +#include #include "result.hpp" namespace codecs { -enum CreateCodecError { UNKNOWN_EXTENSION }; - -auto CreateCodecForFile(const std::string& extension) - -> cpp::result, CreateCodecError>; - class ICodec { public: virtual ~ICodec() {} @@ -22,39 +21,48 @@ class ICodec { struct OutputFormat { uint8_t num_channels; uint8_t bits_per_sample; - int sample_rate_hz; + uint32_t sample_rate_hz; }; virtual auto GetOutputFormat() -> OutputFormat = 0; - enum ProcessingError {}; - - struct Result { - bool need_more_input; - /* - * For need_more_input, this is how far we got in the input buffer - * before we were unable to process more data. Any remaining data in the - * buffer should be moved to the start before the next call. - */ - std::size_t input_processed; - - bool flush_output; - /* - * For flush_output, this is how far we got in the output buffer before - * we ran out of space for samples. The caller should flush this many - * bytes downstream. - */ - std::size_t output_written; - }; + enum ProcessingError { MALFORMED_DATA }; virtual auto ResetForNewStream() -> void = 0; - virtual auto SetInput(uint8_t* buffer, std::size_t length) = 0; - virtual auto Process(uint8_t* output, std::size_t output_length) - -> cpp::result = 0; - - virtual auto GetOutputProcessed() -> std::size_t; - = 0; + virtual auto SetInput(uint8_t* buffer, std::size_t length) -> void = 0; + + /* + * Returns the codec's next read position within the input buffer. If the + * codec is out of usable data, but there is still some data left in the + * stream, that data should be prepended to the next input buffer. + */ + virtual auto GetInputPosition() -> std::size_t = 0; + + /* + * Read one frame (or equivalent discrete chunk) from the input, and + * synthesize output samples for it. + * + * Returns true if we are out of usable data from the input stream, or false + * otherwise. + */ + virtual auto ProcessNextFrame() -> cpp::result = 0; + + /* + * Writes PCM samples to the given output buffer. + * + * Returns the number of bytes that were written, and true if all of the + * samples synthesized from the last call to `ProcessNextFrame` have been + * written. If this returns false, then this method should be called again + * after flushing the output buffer. + */ + virtual auto WriteOutputSamples(uint8_t* output, std::size_t output_length) + -> std::pair = 0; }; +enum CreateCodecError { UNKNOWN_EXTENSION }; + +auto CreateCodecForFile(const std::string& extension) + -> cpp::result, CreateCodecError>; + } // namespace codecs diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp index 241ea6c3..aa24f3c9 100644 --- a/src/codecs/include/mad.hpp +++ b/src/codecs/include/mad.hpp @@ -1,5 +1,11 @@ #pragma once +#include +#include +#include + +#include "mad.h" + #include "codec.hpp" namespace codecs { @@ -9,10 +15,14 @@ class MadMp3Decoder : public ICodec { MadMp3Decoder(); ~MadMp3Decoder(); - auto ProcessInput(Result* res, uint8_t* input, std::size_t input_len) -> void; - auto WriteOutputSamples(Result* res, - uint8_t* output, - std::size_t output_length) -> void; + auto CanHandleFile(const std::string& path) -> bool override; + auto GetOutputFormat() -> OutputFormat override; + auto ResetForNewStream() -> void override; + auto SetInput(uint8_t* buffer, std::size_t length) -> void override; + auto GetInputPosition() -> std::size_t override; + auto ProcessNextFrame() -> cpp::result override; + auto WriteOutputSamples(uint8_t* output, std::size_t output_length) + -> std::pair override; private: mad_stream stream_; @@ -22,7 +32,7 @@ class MadMp3Decoder : public ICodec { mad_header header_; bool has_decoded_header_; - int current_sample_ = -1; + int current_sample_; }; } // namespace codecs diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp index c918e849..4afc9a77 100644 --- a/src/codecs/mad.cpp +++ b/src/codecs/mad.cpp @@ -1,8 +1,11 @@ #include "mad.hpp" + #include #include "mad.h" +#include "codec.hpp" + namespace codecs { static int32_t scaleTo24Bits(mad_fixed_t sample) { @@ -32,118 +35,99 @@ MadMp3Decoder::~MadMp3Decoder() { mad_header_finish(&header_); } -auto MadMp3Decoder::CanHandleExtension(std::string extension) -> bool { - return extension == "mp3"; +auto MadMp3Decoder::CanHandleFile(const std::string& path) -> bool { + return true; // TODO. } -auto GetOutputFormat() -> OutputFormat { +auto MadMp3Decoder::GetOutputFormat() -> OutputFormat { return OutputFormat{ - .num_channels = synth_.pcm.channels, + .num_channels = static_cast(synth_.pcm.channels), .bits_per_sample = 24, .sample_rate_hz = synth_.pcm.samplerate, }; } -auto MadMp3Decoder::Process(uint8_t* input, - std::size_t input_len, - uint8_t* output, - std::size_t output_length) - -> cpp::result { - Result res { - .need_more_input = false, .input_processed = 0, .flush_output = false, - .output_written = 0, - } - while (true) { - // Only process more of the input if we're done sending off the - // samples for the previous frame. - if (current_sample_ == -1) { - ProcessInput(&res, input, input_len); - } +auto MadMp3Decoder::ResetForNewStream() -> void { + has_decoded_header_ = false; +} - // Write PCM samples to the output buffer. This always needs to be - // done, even if we ran out of input, so that we don't keep the last - // few samples buffered if the input stream has actually finished. - WriteOutputSamples(&res, output, output_length); +auto MadMp3Decoder::SetInput(uint8_t* buffer, std::size_t length) -> void { + mad_stream_buffer(&stream_, buffer, length); +} - if (res.need_more_input || res.flush_output) { - return res; - } - } +auto MadMp3Decoder::GetInputPosition() -> std::size_t { + return stream_.next_frame - stream_.buffer; +} - auto MadMp3Decoder::ProcessInput(Result * res, uint8_t * input, - std::size_t input_len) - ->void { - if (input != stream_.buffer) { - mad_stream_buffer(&stream_, input, input_len); - } +auto MadMp3Decoder::ProcessNextFrame() -> cpp::result { + if (!has_decoded_header_) { + // The header of any given frame should be representative of the + // entire stream, so only need to read it once. + mad_header_decode(&header_, &stream_); + has_decoded_header_ = true; - if (!has_decoded_header_) { - // The header of any given frame should be representative of the - // entire stream, so only need to read it once. - mad_header_decode(&header_, &stream); - has_decoded_header_ = true; + // TODO: Use the info in the header for something. I think the + // duration will help with seeking? + } - // TODO: Use the info in the header for something. I think the - // duration will help with seeking? + // Whatever was last synthesized is now invalid, so ensure we don't try to + // send it. + current_sample_ = -1; + + // Decode the next frame. To signal errors, this returns -1 and + // stashes an error code in the stream structure. + if (mad_frame_decode(&frame_, &stream_) < 0) { + if (MAD_RECOVERABLE(stream_.error)) { + // Recoverable errors are usually malformed parts of the stream. + // We can recover from them by just retrying the decode. + return false; } - // Decode the next frame. To signal errors, this returns -1 and - // stashes an error code in the stream structure. - if (mad_frame_decode(&frame_, &stream_) < 0) { - if (MAD_RECOVERABLE(stream_.error)) { - // Recoverable errors are usually malformed parts of the stream. - // We can recover from them by just retrying the decode. - continue; - } - - if (stream_.error = MAD_ERROR_BUFLEN) { - // The decoder ran out of bytes before it completed a frame. We - // need to return back to the caller to give us more data. Note - // that there might still be some unused data in the input, so we - // should calculate that amount and return it. - size_t remaining_bytes = stream.bufend - stream_.next_frame; - return remaining_bytes; - } - - // The error is unrecoverable. Give up. - return cpp::fail(MALFORMED_DATA); + if (stream_.error == MAD_ERROR_BUFLEN) { + // The decoder ran out of bytes before it completed a frame. We + // need to return back to the caller to give us more data. + return true; } - // We've successfully decoded a frame! - // Now we need to synthesize PCM samples based on the frame, and send - // them downstream. - mad_synth_frame(&synth_, &frame_); - up_to_sample = 0; + // The error is unrecoverable. Give up. + return cpp::fail(MALFORMED_DATA); } - auto MadMp3Decoder::WriteOutputSamples(Result * res, uint8_t * output, - std::size_t output_length) - ->void { - size_t output_byte = 0; - // First ensure that we actually have some samples to send off. - if (current_sample_ < 0) { - return; - } - res->flush_output = true; - - while (current_sample_ < synth_.pcm.length) { - if (output_byte + (3 * synth_.pcm.channels) >= output_length) { - res->output_written = output_byte; - return; - } - - for (int channel = 0; channel < synth_.pcm.channels; channel++) { - uint32_t sample_24 = scaleTo24Bits(synth_.pcm.samples[channel][sample]); - output[output_byte++] = (sample_24 >> 0) & 0xff; - output[output_byte++] = (sample_24 >> 8) & 0xff; - output[output_byte++] = (sample_24 >> 16) & 0xff; - } - current_sample_++; + // We've successfully decoded a frame! + // Now we need to synthesize PCM samples based on the frame, and send + // them downstream. + mad_synth_frame(&synth_, &frame_); + current_sample_ = 0; + return false; +} + +auto MadMp3Decoder::WriteOutputSamples(uint8_t* output, + std::size_t output_length) + -> std::pair { + size_t output_byte = 0; + // First ensure that we actually have some samples to send off. + if (current_sample_ < 0) { + return std::make_pair(output_byte, true); + } + + while (current_sample_ < synth_.pcm.length) { + if (output_byte + (3 * synth_.pcm.channels) >= output_length) { + return std::make_pair(output_byte, false); } - // We wrote everything! Reset, ready for the next frame. - current_sample_ = -1; - res->output_written = output_byte; + for (int channel = 0; channel < synth_.pcm.channels; channel++) { + uint32_t sample_24 = + scaleTo24Bits(synth_.pcm.samples[channel][current_sample_]); + output[output_byte++] = (sample_24 >> 0) & 0xff; + output[output_byte++] = (sample_24 >> 8) & 0xff; + output[output_byte++] = (sample_24 >> 16) & 0xff; + } + current_sample_++; } + // We wrote everything! Reset, ready for the next frame. + current_sample_ = -1; + return std::make_pair(output_byte, true); +} + } // namespace codecs diff --git a/src/main/app_console.hpp b/src/main/app_console.hpp index d41810b0..155d8127 100644 --- a/src/main/app_console.hpp +++ b/src/main/app_console.hpp @@ -9,8 +9,8 @@ namespace console { class AppConsole : public Console { public: - AppConsole(){}; - virtual ~AppConsole(){}; + AppConsole() {} + virtual ~AppConsole() {} protected: virtual auto RegisterExtraComponents() -> void; diff --git a/src/main/main.cpp b/src/main/main.cpp index 79a2f42b..3e073401 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -110,6 +110,7 @@ extern "C" void app_main(void) { (void*)lvglArgs, 1, sLvglStack, &sLvglTaskBuffer, 1); + /* ESP_LOGI(TAG, "Init audio output (I2S)"); auto sink_res = drivers::I2SAudioOutput::create(expander); if (sink_res.has_error()) { @@ -118,7 +119,6 @@ extern "C" void app_main(void) { } std::unique_ptr sink = std::move(sink_res.value()); - /* ESP_LOGI(TAG, "Init audio pipeline"); auto playback_res = drivers::AudioPlayback::create(std::move(sink)); if (playback_res.has_error()) { @@ -131,7 +131,7 @@ extern "C" void app_main(void) { */ ESP_LOGI(TAG, "Launch console"); - console::AppConsole console(playback.get()); + console::AppConsole console; console.Launch(); while (1) { diff --git a/tools/cmake/common.cmake b/tools/cmake/common.cmake index 53a6a243..577a77d5 100644 --- a/tools/cmake/common.cmake +++ b/tools/cmake/common.cmake @@ -25,3 +25,5 @@ set(EXTRA_WARNINGS "-Wshadow" "-Wnon-virtual-dtor" "-Wunused" # just be used to setting flags that our external dependencies requires. # Otherwise, prefer adding per-component build flags to keep things neat. idf_build_set_property(COMPILE_OPTIONS "-DLV_CONF_INCLUDE_SIMPLE" APPEND) + +include($ENV{PROJ_PATH}/tools/cmake/extra-libs.cmake) diff --git a/tools/cmake/libmad.cmake b/tools/cmake/libmad.cmake index 5beb96cb..7be23494 100644 --- a/tools/cmake/libmad.cmake +++ b/tools/cmake/libmad.cmake @@ -1,5 +1,6 @@ set(LIBMAD_SRC "$ENV{PROJ_PATH}/lib/libmad") set(LIBMAD_BIN "${CMAKE_CURRENT_BINARY_DIR}/libmad") +set(LIBMAD_INCLUDE "${LIBMAD_BIN}/include") externalproject_add(libmad_build SOURCE_DIR "${LIBMAD_SRC}" @@ -7,13 +8,13 @@ externalproject_add(libmad_build CONFIGURE_COMMAND ${LIBMAD_SRC}/configure CC=${CMAKE_C_COMPILER} --srcdir=${LIBMAD_SRC} --prefix=${LIBMAD_BIN} --host=xtensa-elf --disable-debugging --disable-shared BUILD_COMMAND make INSTALL_COMMAND make install - BUILD_BYPRODUCTS "${LIBMAD_BIN}/libmad.a" + BUILD_BYPRODUCTS "${LIBMAD_BIN}/lib/libmad.a" "${LIBMAD_INCLUDE}/mad.h" ) add_library(libmad STATIC IMPORTED GLOBAL) add_dependencies(libmad libmad_build) set_target_properties(libmad PROPERTIES IMPORTED_LOCATION - "${LIBMAD_BIN}/libmad.a") + "${LIBMAD_BIN}/lib/libmad.a") set_target_properties(libmad PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${LIBMAD_BIN}") + "${LIBMAD_INCLUDE}")