fix build errors

custom
jacqueline 2 years ago
parent 71a4f5166f
commit 222c810b07
  1. 1
      CMakeLists.txt
  2. 2
      src/audio/CMakeLists.txt
  3. 64
      src/audio/audio_decoder.cpp
  4. 49
      src/audio/audio_task.cpp
  5. 34
      src/audio/chunk.cpp
  6. 62
      src/audio/fatfs_audio_input.cpp
  7. 9
      src/audio/include/audio_decoder.hpp
  8. 27
      src/audio/include/audio_element.hpp
  9. 33
      src/audio/include/chunk.hpp
  10. 11
      src/audio/include/fatfs_audio_input.hpp
  11. 14
      src/audio/include/stream_info.hpp
  12. 53
      src/audio/stream_info.cpp
  13. 131
      src/cbor/cbor_decoder.cpp
  14. 0
      src/cbor_wrapper/CMakeLists.txt
  15. 112
      src/cbor_wrapper/cbor_decoder.cpp
  16. 27
      src/cbor_wrapper/cbor_encoder.cpp
  17. 56
      src/cbor_wrapper/include/cbor_decoder.hpp
  18. 23
      src/cbor_wrapper/include/cbor_encoder.hpp
  19. 6
      src/codecs/CMakeLists.txt
  20. 5
      src/codecs/codec.cpp
  21. 60
      src/codecs/include/codec.hpp
  22. 20
      src/codecs/include/mad.hpp
  23. 80
      src/codecs/mad.cpp
  24. 4
      src/main/app_console.hpp
  25. 4
      src/main/main.cpp
  26. 2
      tools/cmake/common.cmake
  27. 7
      tools/cmake/libmad.cmake

@ -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)

@ -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})

@ -1,29 +1,41 @@
#include "audio_decoder.hpp"
#include <string.h>
#include <cstddef>
#include <cstdint>
#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<uint8_t*>(
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<size_t, codecs::ICodec::ProcessingError> result;
bool has_samples_to_send = false;
bool needs_more_input = false;
std::optional<codecs::ICodec::ProcessingError> error = std::nullopt;
WriteChunksToStream(
output_buffer_, working_buffer_, kWorkingBufferSize,
[&](uint8_t* buf, size_t len) {
result = current_codec_->Process(data, length, buf, len);
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();
}
return 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 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<void, StreamError> {

@ -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,12 +25,13 @@ static const size_t kChunkBufferSize = kMaxChunkSize * 1.5;
auto StartAudioTask(const std::string& name,
std::shared_ptr<IAudioElement>& 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<AudioTaskArgs*>(args);
std::shared_ptr<IAudioElement> element = std::move(real_args->element);
delete real_args;
@ -38,14 +39,14 @@ void AudioTaskMain(void* args) {
ChunkReader chunk_reader = ChunkReader(element->InputBuffer());
while (1) {
cpp::result<size_t, IAudioElement::StreamError> process_res;
cpp::result<size_t, StreamError> 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.
// 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(
if (element->InputBuffer() != nullptr) {
ChunkReadResult chunk_res = chunk_reader.ReadChunkFromStream(
[&](uint8_t* data, std::size_t length) -> std::optional<size_t> {
process_res = element->ProcessChunk(data, length);
if (process_res.has_value()) {
@ -65,16 +66,29 @@ void AudioTaskMain(void* args) {
}
if (has_received_message) {
auto& [buffer, length] = chunk_reader.GetLastMessage();
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()););
auto decoder = std::move(decoder_res.value());
// TODO: this can be more elegant i think
cpp::result<uint64_t, CborError> message_type =
decoder->NextValue<uint64_t>();
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.
}
}
}
@ -82,15 +96,12 @@ void AudioTaskMain(void* args) {
// 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()) {
auto process_error = element->ProcessIdle();
if (process_error.has_error()) {
break; // TODO.
}
}
element.clear();
free(chunk_buffer_);
}
vTaskDelete(NULL);
}

@ -1,17 +1,20 @@
#include "chunk.hpp"
#include <string.h>
#include <cstddef>
#include <cstdint>
#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<size_t(uint8_t*, size_t)> 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<size_t, CborError> 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<uint8_t*>(
heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM));
};
ChunkReader::~ChunkReader() {
@ -94,7 +98,7 @@ auto ChunkReader::GetLastMessage() -> std::pair<uint8_t*, size_t> {
auto ChunkReader::ReadChunkFromStream(
std::function<std::optional<size_t>(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_,
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<MessageType>(
decoder.value()->NextValue<uint64_t>().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<uint64_t>().value_or(0);
size_t chunk_length = decoder.value()->NextValue<uint64_t>().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

@ -1,14 +1,15 @@
#include "fatfs_audio_input.hpp<D-c>ccc
#include "fatfs_audio_input.hpp"
#include <cstdint>
#include <memory>
#include "chunk.hpp"
#include "fatfs_audio_input.hpp"
#include <string>
#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<drivers::SdStorage> storage)
: IAudioElement(), storage_(storage) {
file_buffer_ = heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_SPIRAM);
file_buffer_ = static_cast<uint8_t*>(
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<uint8_t*>(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<uint8_t*>(
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<void, StreamError> {
if (is_file_open_) {
f_close(&current_file_);
is_file_open_ = false;
}
FRESULT res = f_open(&current_file_, info.path.c_str(), FA_READ);
std::string path = info.Path().value_or("");
FRESULT res = f_open(&current_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<void, StreamError> {
-> cpp::result<size_t, StreamError> {
// 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<void, StreamError> {
auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, StreamError> {
// 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<void, StreamError> {
UINT bytes_read = 0;
FRESULT result = f_read(&current_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(&current_file_)) {
@ -123,16 +121,16 @@ virtual auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, StreamError> {
// 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.
}
}

@ -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<void, StreamError>;
auto ProcessChunk(uint8_t* data, std::size_t length)
-> cpp::result<size_t, StreamError>;
auto ProcessIdle() -> cpp::result<void, StreamError>;
AudioDecoder(const AudioDecoder&) = delete;
AudioDecoder& operator=(const AudioDecoder&) = delete;

@ -1,13 +1,28 @@
#pragma once
#include <stdint.h>
#include <cstdint>
#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<void, StreamError> = 0;
protected:
StreamBufferHandle_t* input_buffer_;
StreamBufferHandle_t* output_buffer_;
MessageBufferHandle_t* input_buffer_;
MessageBufferHandle_t* output_buffer_;
};
} // namespace audio

@ -4,8 +4,13 @@
#include <cstdint>
#include <optional>
#include <string>
#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,18 +40,13 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream,
uint8_t* working_buffer,
size_t working_buffer_length,
std::function<size_t(uint8_t*, size_t)> callback,
TickType_t max_wait) -> EncodeWriteResult;
class ChunkReader {
public:
ChunkReader(MessageBufferHandle_t* stream);
~ChunkReader();
auto Reset() -> void;
auto GetLastMessage() -> std::pair<uint8_t*, size_t>;
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.
@ -57,6 +57,15 @@ class ChunkReader {
CHUNK_PROCESSING_ERROR,
};
class ChunkReader {
public:
ChunkReader(MessageBufferHandle_t* stream);
~ChunkReader();
auto Reset() -> void;
auto GetLastMessage() -> std::pair<uint8_t*, size_t>;
/*
* 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<std::optional<size_t>(uint8_t*, size_t)> callback,
TickType_t max_wait) -> EncodeReadResult;
TickType_t max_wait) -> ChunkReadResult;
private:
MessageBufferHandle_t* stream_;

@ -5,8 +5,9 @@
#include <string>
#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<drivers::SdStorage> storage);
~FatfsAudioInput();
auto OutputBuffer() -> MessageBufferHandle_t;
auto ProcessStreamInfo(StreamInfo&& info) -> cpp::result<void, StreamError>;
auto ProcessChunk(uint8_t* data, std::size_t length)
-> cpp::result<size_t, StreamError>;
auto ProcessIdle() -> cpp::result<void, StreamError>;
auto SendChunk(uint8_t* buffer, size_t size) -> size_t;
private:
auto GetRingBufferDistance() -> size_t;
std::shared_ptr<drivers::SdStorage> 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

@ -3,9 +3,13 @@
#include <cstdint>
#include <optional>
#include <string>
#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, ParseError>;
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<size_t, EncodeError>;
auto WriteToMap(cbor::Encoder& encoder) -> cpp::result<size_t, CborError>;
private:
std::optional<std::string> path_;

@ -1,26 +1,31 @@
#include "stream_info.hpp"
#include <cstdint>
#include <string>
#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<StreamInfo, ParseError> {
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<uint64_t>(kKeyChannels);
bits_per_sample_ = decoder->FindValue<uint64_t>(kKeyBitsPerSample);
sample_rate_ = decoder->FindValue<uint64_t>(kKeySampleRate);
path_ = decoder->FindValue<std::string>(kKeyPath);
}
auto StreamInfo::WriteToMap(cbor::Encoder& map_encoder)
-> cpp::result<size_t, EncodeError> {
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<size_t, CborError> {
map_encoder.WriteKeyValue<uint64_t>(kKeyChannels, channels_);
map_encoder.WriteKeyValue<uint64_t>(kKeyBitsPerSample, bits_per_sample_);
map_encoder.WriteKeyValue<uint64_t>(kKeySampleRate, sample_rate_);
map_encoder.WriteKeyValue<std::string>(kKeyPath, path_);
return map_encoder.Finish();
}

@ -1,131 +0,0 @@
#include "cbor_decoder.hpp"
#include <cstdint>
#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<std::unique_ptr<ArrayDecoder>, CborError> {
auto decoder = std::make_unique<ArrayDecoder>();
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<std::unique_ptr<ArrayDecoder>, CborError> {
auto decoder = std::make_unique<ArrayDecoder>();
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<std::unique_ptr<MapDecoder>, CborError> {
auto decoder = std::make_unique<MapDecoder>();
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<std::unique_ptr<MapDecoder>, CborError> {
auto decoder = std::make_unique<MapDecoder>();
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<std::string> {
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<uint32_t> {
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<int32_t> {
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

@ -0,0 +1,112 @@
#include "cbor_decoder.hpp"
#include <cstdint>
#include <string>
#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<std::unique_ptr<ArrayDecoder>, CborError> {
auto decoder = std::make_unique<ArrayDecoder>();
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<std::unique_ptr<ArrayDecoder>, CborError> {
auto decoder = std::make_unique<ArrayDecoder>();
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<int64_t, CborError> {
return NextValue(&cbor_value_is_integer, &cbor_value_get_int);
}
template <>
auto ArrayDecoder::NextValue() -> cpp::result<uint64_t, CborError> {
return NextValue(&cbor_value_is_unsigned_integer, &cbor_value_get_uint64);
}
template <>
auto ArrayDecoder::NextValue() -> cpp::result<std::string, CborError> {
return NextValue(&cbor_value_is_byte_string, &parse_stdstring);
}
auto MapDecoder::Create(uint8_t* buffer, size_t buffer_len)
-> cpp::result<std::unique_ptr<MapDecoder>, CborError> {
auto decoder = std::make_unique<MapDecoder>();
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<std::unique_ptr<MapDecoder>, CborError> {
auto decoder = std::make_unique<MapDecoder>();
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<int64_t> {
return FindValue(key, &cbor_value_is_integer, &cbor_value_get_int);
}
template <>
auto MapDecoder::FindValue(const std::string& key) -> std::optional<uint64_t> {
return FindValue(key, &cbor_value_is_unsigned_integer,
&cbor_value_get_uint64);
}
template <>
auto MapDecoder::FindValue(const std::string& key)
-> std::optional<std::string> {
return FindValue(key, &cbor_value_is_byte_string, &parse_stdstring);
}
} // namespace cbor

@ -1,6 +1,11 @@
#include "cbor_encoder.hpp"
#include <cstdint>
#include "esp-idf/components/cbor/tinycbor/src/cbor.h"
#include <string>
#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<size_t, CborError> {
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

@ -3,23 +3,13 @@
#include <stdint.h>
#include <cstdint>
#include <string>
#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<std::unique_ptr<ArrayDecoder>, CborError>;
ArrayDecoder() {}
template <typename T>
auto NextValue() -> cpp::result<T, CborError>;
template <>
auto NextValue() -> cpp::result<int64_t, CborError> {
return NextValue(&cbor_value_is_integer, &cbor_value_get_int);
}
template <>
auto NextValue() -> cpp::result<uint64_t, CborError> {
return NextValue(&cbor_value_is_unsigned_integer, &cbor_value_get_uint64);
}
template <>
auto NextValue() -> cpp::result<std::string, CborError> {
return NextValue(&cbor_value_is_byte_string, &parse_stdstring);
}
template <typename T>
auto NextValue(bool (*is_valid)(CborValue*),
CborError (*parse)(CborValue*, T*))
auto NextValue(bool (*is_valid)(const CborValue*),
CborError (*parse)(const CborValue*, T*))
-> cpp::result<T, CborError> {
if (error_ != CborNoError) {
return cpp::fail(error_);
@ -90,30 +69,19 @@ class MapDecoder {
static auto Create(CborValue& root)
-> cpp::result<std::unique_ptr<MapDecoder>, CborError>;
MapDecoder() {}
template <typename T>
auto FindValue(const std::string& key) -> std::optional<T>;
template <>
auto FindValue(const std::string& key) -> std::optional<int64_t> {
return FindValue(key, &cbor_value_is_integer, &cbor_value_get_int);
}
template <>
auto FindValue(const std::string& key) -> std::optional<uint64_t> {
return FindValue(key, &cbor_value_is_unsigned_integer,
&cbor_value_get_uint64);
}
template <>
auto FindValue(const std::string& key) -> std::optional<std::string> {
return FindValue(key, &cbor_value_is_byte_string, &parse_stdstring);
}
template <typename T>
auto FindValue(const std::string& key,
bool (*is_valid)(CborValue*),
CborError (*parse)(CborValue*, T*)) -> std::optional<T> {
bool (*is_valid)(const CborValue*),
CborError (*parse)(const CborValue*, T*)) -> std::optional<T> {
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;
}

@ -1,28 +1,40 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#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 <typename T>
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 <typename T>
auto WriteKeyValue(const std::string& key, const std::optional<T>& val)
-> void {
if (val) {
WriteKeyValue<T>(key, val.value());
}
}
auto Finish() -> cpp::result<size_t, CborError>;
@ -30,6 +42,7 @@ class Encoder {
Encoder& operator=(const Encoder&) = delete;
private:
uint8_t* buffer_;
CborEncoder root_encoder_;
CborEncoder container_encoder_;

@ -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}")

@ -1,10 +1,13 @@
#include "codec.hpp"
#include <memory>
#include "mad.hpp"
namespace codecs {
auto CreateCodecForExtension(std::string extension)
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError> {
return cpp::fail(UNKNOWN_EXTENSION);
return std::make_unique<MadMp3Decoder>(); // TODO.
}
} // namespace codecs

@ -1,18 +1,17 @@
#pragma once
#include <stdint.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "result.hpp"
namespace codecs {
enum CreateCodecError { UNKNOWN_EXTENSION };
auto CreateCodecForFile(const std::string& extension)
-> cpp::result<std::unique_ptr<ICodec>, 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 {};
enum ProcessingError { MALFORMED_DATA };
virtual auto ResetForNewStream() -> void = 0;
virtual auto SetInput(uint8_t* buffer, std::size_t length) -> void = 0;
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.
* 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.
*/
std::size_t input_processed;
virtual auto GetInputPosition() -> std::size_t = 0;
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.
* 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.
*/
std::size_t output_written;
};
virtual auto ProcessNextFrame() -> cpp::result<bool, ProcessingError> = 0;
virtual auto ResetForNewStream() -> void = 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<std::size_t, bool> = 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<size_t, Error> = 0;
enum CreateCodecError { UNKNOWN_EXTENSION };
virtual auto GetOutputProcessed() -> std::size_t;
= 0;
};
auto CreateCodecForFile(const std::string& extension)
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>;
} // namespace codecs

@ -1,5 +1,11 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#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<bool, ProcessingError> override;
auto WriteOutputSamples(uint8_t* output, std::size_t output_length)
-> std::pair<std::size_t, bool> 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

@ -1,8 +1,11 @@
#include "mad.hpp"
#include <cstdint>
#include "mad.h"
#include "codec.hpp"
namespace codecs {
static int32_t scaleTo24Bits(mad_fixed_t sample) {
@ -32,77 +35,58 @@ 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<uint8_t>(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, Error> {
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);
if (res.need_more_input || res.flush_output) {
return res;
}
auto MadMp3Decoder::SetInput(uint8_t* buffer, std::size_t length) -> void {
mad_stream_buffer(&stream_, buffer, length);
}
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::GetInputPosition() -> std::size_t {
return stream_.next_frame - stream_.buffer;
}
auto MadMp3Decoder::ProcessNextFrame() -> cpp::result<bool, ProcessingError> {
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);
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?
}
// 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.
continue;
return false;
}
if (stream_.error = MAD_ERROR_BUFLEN) {
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;
// need to return back to the caller to give us more data.
return true;
}
// The error is unrecoverable. Give up.
@ -113,27 +97,27 @@ auto MadMp3Decoder::Process(uint8_t* input,
// Now we need to synthesize PCM samples based on the frame, and send
// them downstream.
mad_synth_frame(&synth_, &frame_);
up_to_sample = 0;
current_sample_ = 0;
return false;
}
auto MadMp3Decoder::WriteOutputSamples(Result * res, uint8_t * output,
auto MadMp3Decoder::WriteOutputSamples(uint8_t* output,
std::size_t output_length)
->void {
-> std::pair<std::size_t, bool> {
size_t output_byte = 0;
// First ensure that we actually have some samples to send off.
if (current_sample_ < 0) {
return;
return std::make_pair(output_byte, true);
}
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;
return std::make_pair(output_byte, false);
}
for (int channel = 0; channel < synth_.pcm.channels; channel++) {
uint32_t sample_24 = scaleTo24Bits(synth_.pcm.samples[channel][sample]);
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;
@ -143,7 +127,7 @@ auto MadMp3Decoder::Process(uint8_t* input,
// We wrote everything! Reset, ready for the next frame.
current_sample_ = -1;
res->output_written = output_byte;
return std::make_pair(output_byte, true);
}
} // namespace codecs

@ -9,8 +9,8 @@ namespace console {
class AppConsole : public Console {
public:
AppConsole(){};
virtual ~AppConsole(){};
AppConsole() {}
virtual ~AppConsole() {}
protected:
virtual auto RegisterExtraComponents() -> void;

@ -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<drivers::IAudioOutput> 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) {

@ -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)

@ -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}")

Loading…
Cancel
Save