diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 28e4e2ef..306def86 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" - "stream_info.cpp" "stream_message.cpp" + "stream_info.cpp" "stream_message.cpp" "i2s_audio_output.cpp" INCLUDE_DIRS "include" REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span") diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index 31cfb50e..c48756ac 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -40,7 +40,7 @@ auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void { output_buffer_ = buffer; } -auto AudioDecoder::ProcessStreamInfo(StreamInfo& info) +auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> cpp::result { stream_info_ = info; @@ -62,7 +62,7 @@ auto AudioDecoder::ProcessStreamInfo(StreamInfo& info) return {}; } -auto AudioDecoder::ProcessChunk(cpp::span& chunk) +auto AudioDecoder::ProcessChunk(const cpp::span& chunk) -> cpp::result { if (current_codec_ == nullptr) { // Should never happen, but fail explicitly anyway. diff --git a/src/audio/chunk.cpp b/src/audio/chunk.cpp index 44dcc410..eb28d5a9 100644 --- a/src/audio/chunk.cpp +++ b/src/audio/chunk.cpp @@ -12,8 +12,15 @@ namespace audio { -// TODO: tune. -const std::size_t kMaxChunkSize = 512; +/* + * The maximum size of a single chunk of stream data. This should be comfortably + * larger than the largest size of a frame of audio we should expect to handle. + * + * 128 kbps MPEG-1 @ 44.1 kHz is approx. 418 bytes according to the internet. + * + * TODO(jacqueline): tune as more codecs are added. + */ +const std::size_t kMaxChunkSize = 2048; // TODO: tune static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5; @@ -60,11 +67,11 @@ ChunkReader::ChunkReader(MessageBufferHandle_t* stream) : stream_(stream), raw_working_buffer_(static_cast( heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM))), - working_buffer_(raw_working_buffer_, kWorkingBufferSize){}; + working_buffer_(raw_working_buffer_, kWorkingBufferSize) {} ChunkReader::~ChunkReader() { free(raw_working_buffer_); -}; +} auto ChunkReader::Reset() -> void { leftover_bytes_ = 0; diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 6777bef2..0f2fbe6d 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -4,7 +4,6 @@ #include #include -#include "audio_element.hpp" #include "esp_heap_caps.h" #include "freertos/portmacro.h" @@ -50,7 +49,7 @@ FatfsAudioInput::~FatfsAudioInput() { free(output_buffer_); } -auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info) +auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info) -> cpp::result { if (is_file_open_) { f_close(¤t_file_); @@ -83,12 +82,12 @@ auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info) return {}; } -auto FatfsAudioInput::ProcessChunk(cpp::span& chunk) +auto FatfsAudioInput::ProcessChunk(const cpp::span& chunk) -> cpp::result { return cpp::fail(UNSUPPORTED_STREAM); } -auto FatfsAudioInput::GetRingBufferDistance() -> size_t { +auto FatfsAudioInput::GetRingBufferDistance() const -> size_t { if (file_buffer_read_pos_ == file_buffer_write_pos_) { return 0; } diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp index 77d01b6a..b6cf27f2 100644 --- a/src/audio/i2s_audio_output.cpp +++ b/src/audio/i2s_audio_output.cpp @@ -2,18 +2,20 @@ #include -#include "audio_element.h" -#include "driver/i2s.h" #include "esp_err.h" #include "freertos/portmacro.h" -#include "i2s_stream.h" -static const i2s_port_t kI2SPort = I2S_NUM_0; +#include "audio_element.hpp" +#include "dac.hpp" +#include "gpio_expander.hpp" +#include "result.hpp" + +static const TickType_t kIdleTimeBeforeMute = pdMS_TO_TICKS(1000); static const char* kTag = "I2SOUT"; -namespace drivers { +namespace audio { -auto I2SAudioOutput::create(GpioExpander* expander) +auto I2SAudioOutput::create(drivers::GpioExpander* expander) -> cpp::result, Error> { // First, we need to perform initial configuration of the DAC chip. auto dac_result = drivers::AudioDac::create(expander); @@ -21,72 +23,82 @@ auto I2SAudioOutput::create(GpioExpander* expander) ESP_LOGE(kTag, "failed to init dac: %d", dac_result.error()); return cpp::fail(DAC_CONFIG); } - std::unique_ptr dac = std::move(dac_result.value()); + std::unique_ptr dac = std::move(dac_result.value()); // Soft mute immediately, in order to minimise any clicks and pops caused by // the initial output element and pipeline configuration. dac->WriteVolume(255); - i2s_stream_cfg_t i2s_stream_config = i2s_stream_cfg_t{ - .type = AUDIO_STREAM_WRITER, - .i2s_config = - { - // static_cast bc esp-adf uses enums incorrectly - .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), - .sample_rate = 44100, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = ESP_INTR_FLAG_LOWMED, - .dma_buf_count = 8, - .dma_buf_len = 64, - .use_apll = false, - .tx_desc_auto_clear = false, - .fixed_mclk = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, - .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, - }, - .i2s_port = kI2SPort, - .use_alc = false, - .volume = 0, // Does nothing; use AudioDac to change this. - .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, - .task_stack = I2S_STREAM_TASK_STACK, - .task_core = I2S_STREAM_TASK_CORE, - .task_prio = I2S_STREAM_TASK_PRIO, - .stack_in_ext = false, - .multi_out_num = 0, - .uninstall_drv = true, - .need_expand = false, - .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT, - }; - audio_element_handle_t i2s_stream_writer = - i2s_stream_init(&i2s_stream_config); - if (i2s_stream_writer == NULL) { - return cpp::fail(Error::STREAM_INIT); + return std::make_unique(expander, std::move(dac)); +} + +I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander, + std::unique_ptr dac) + : expander_(expander), + dac_(std::move(dac)), + volume_(255), + is_soft_muted_(false) {} + +I2SAudioOutput::~I2SAudioOutput() { + // TODO: power down the DAC. +} + +auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info) + -> cpp::result { + // TODO(jacqueline): probs do something with the channel hey + + if (!info.BitsPerSample() && !info.SampleRate()) { + return cpp::fail(UNSUPPORTED_STREAM); + } + + drivers::AudioDac::BitsPerSample bps; + switch (*info.BitsPerSample()) { + case 16: + bps = drivers::AudioDac::BPS_16; + break; + case 24: + bps = drivers::AudioDac::BPS_24; + break; + case 32: + bps = drivers::AudioDac::BPS_32; + break; + default: + return cpp::fail(UNSUPPORTED_STREAM); } - // NOTE: i2s_stream_init does some additional setup that hardcodes MCK as - // GPIO0. This happens to work fine for us, but be careful if changing. - i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0, - .bck_io_num = GPIO_NUM_26, - .ws_io_num = GPIO_NUM_27, - .data_out_num = GPIO_NUM_5, - .data_in_num = I2S_PIN_NO_CHANGE}; - if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) { - ESP_LOGE(kTag, "failed to configure i2s pins %x", err); - return cpp::fail(Error::I2S_CONFIG); + drivers::AudioDac::SampleRate sample_rate; + switch (*info.SampleRate()) { + case 44100: + sample_rate = drivers::AudioDac::SAMPLE_RATE_44_1; + break; + case 48000: + sample_rate = drivers::AudioDac::SAMPLE_RATE_48; + break; + default: + return cpp::fail(UNSUPPORTED_STREAM); } - return std::make_unique(dac, i2s_stream_writer); + dac_->Reconfigure(bps, sample_rate); + + return {}; } -I2SAudioOutput::I2SAudioOutput(std::unique_ptr& dac, - audio_element_handle_t element) - : IAudioOutput(element), dac_(std::move(dac)) { - volume_ = 255; +auto I2SAudioOutput::ProcessChunk(const cpp::span& chunk) + -> cpp::result { + SetSoftMute(false); + // TODO(jacqueline): write smaller parts with a small delay so that we can + // be responsive to pause and seek commands. + return dac_->WriteData(chunk, portMAX_DELAY); } -I2SAudioOutput::~I2SAudioOutput() { - // TODO: power down the DAC. + +auto I2SAudioOutput::IdleTimeout() const -> TickType_t { + return kIdleTimeBeforeMute; +} + +auto I2SAudioOutput::ProcessIdle() -> cpp::result { + // TODO(jacqueline): Consider powering down the dac completely maybe? + SetSoftMute(true); + return {}; } auto I2SAudioOutput::SetVolume(uint8_t volume) -> void { @@ -97,18 +109,15 @@ auto I2SAudioOutput::SetVolume(uint8_t volume) -> void { } auto I2SAudioOutput::SetSoftMute(bool enabled) -> void { - if (enabled) { - is_soft_muted_ = true; + if (enabled == is_soft_muted_) { + return; + } + is_soft_muted_ = enabled; + if (is_soft_muted_) { dac_->WriteVolume(255); } else { - is_soft_muted_ = false; dac_->WriteVolume(volume_); } } -auto I2SAudioOutput::Configure(audio_element_info_t& info) -> void { - audio_element_setinfo(element_, &info); - i2s_stream_set_clk(element_, info.sample_rates, info.bits, info.channels); -} - -} // namespace drivers +} // namespace audio diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp index a32442da..a6b15d9e 100644 --- a/src/audio/include/audio_decoder.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "ff.h" #include "span.hpp" @@ -23,11 +24,13 @@ class AudioDecoder : public IAudioElement { auto SetInputBuffer(MessageBufferHandle_t*) -> void; auto SetOutputBuffer(MessageBufferHandle_t*) -> void; - auto ProcessStreamInfo(StreamInfo& info) - -> cpp::result; - auto ProcessChunk(cpp::span& chunk) - -> cpp::result; - auto ProcessIdle() -> cpp::result; + auto StackSizeBytes() const -> std::size_t override { return 8196; }; + + auto ProcessStreamInfo(const StreamInfo& info) + -> cpp::result override; + auto ProcessChunk(const cpp::span& chunk) + -> cpp::result override; + auto ProcessIdle() -> cpp::result override; 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 06e47b35..1973fccf 100644 --- a/src/audio/include/audio_element.hpp +++ b/src/audio/include/audio_element.hpp @@ -48,26 +48,26 @@ class IAudioElement { * be tuned according to the observed stack size of each element, as different * elements have fairly different stack requirements. */ - virtual auto StackSizeBytes() -> std::size_t { return 2048; }; + virtual auto StackSizeBytes() const -> std::size_t { return 2048; }; /* * How long to wait for new data on the input stream before triggering a call * to ProcessIdle(). If this is portMAX_DELAY (the default), then ProcessIdle * will never be called. */ - virtual auto IdleTimeout() -> TickType_t { return portMAX_DELAY; } + virtual auto IdleTimeout() const -> TickType_t { return portMAX_DELAY; } /* Returns this element's input buffer. */ - auto InputBuffer() -> MessageBufferHandle_t* { return input_buffer_; } + auto InputBuffer() const -> MessageBufferHandle_t* { return input_buffer_; } /* Returns this element's output buffer. */ - auto OutputBuffer() -> MessageBufferHandle_t* { return output_buffer_; } + auto OutputBuffer() const -> MessageBufferHandle_t* { return output_buffer_; } /* * Called when a StreamInfo message is received. Used to configure this * element in preperation for incoming chunks. */ - virtual auto ProcessStreamInfo(StreamInfo& info) + virtual auto ProcessStreamInfo(const StreamInfo& info) -> cpp::result = 0; /* @@ -76,7 +76,7 @@ class IAudioElement { * bytes in this chunk that were actually used; leftover bytes will be * prepended to the next call. */ - virtual auto ProcessChunk(cpp::span& chunk) + virtual auto ProcessChunk(const cpp::span& chunk) -> cpp::result = 0; /* diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index 3ca79457..63555ddc 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -17,19 +17,19 @@ namespace audio { class FatfsAudioInput : public IAudioElement { public: - FatfsAudioInput(std::shared_ptr storage); + explicit FatfsAudioInput(std::shared_ptr storage); ~FatfsAudioInput(); - auto ProcessStreamInfo(StreamInfo& info) - -> cpp::result; - auto ProcessChunk(cpp::span& chunk) - -> cpp::result = 0; - auto ProcessIdle() -> cpp::result; + auto ProcessStreamInfo(const StreamInfo& info) + -> cpp::result override; + auto ProcessChunk(const cpp::span& chunk) + -> cpp::result override; + auto ProcessIdle() -> cpp::result override; auto SendChunk(cpp::span dest) -> size_t; private: - auto GetRingBufferDistance() -> size_t; + auto GetRingBufferDistance() const -> size_t; std::shared_ptr storage_; diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp index cd542f71..4b4a458d 100644 --- a/src/audio/include/i2s_audio_output.hpp +++ b/src/audio/include/i2s_audio_output.hpp @@ -3,30 +3,46 @@ #include #include +#include "audio_element.hpp" #include "result.hpp" #include "dac.hpp" #include "gpio_expander.hpp" +#include "sys/_stdint.h" -namespace drivers { +namespace audio { -class I2SAudioOutput : public IAudioOutput { +class I2SAudioOutput : public IAudioElement { public: enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT }; - static auto create(GpioExpander* expander) + static auto create(drivers::GpioExpander* expander) -> cpp::result, Error>; - I2SAudioOutput(std::unique_ptr& dac, - audio_element_handle_t element); + I2SAudioOutput(drivers::GpioExpander* expander, + std::unique_ptr dac); ~I2SAudioOutput(); - virtual auto SetVolume(uint8_t volume) -> void; - virtual auto Configure(audio_element_info_t& info) -> void; - virtual auto SetSoftMute(bool enabled) -> void; + auto SetInputBuffer(MessageBufferHandle_t* in) -> void { input_buffer_ = in; } + + auto IdleTimeout() const -> TickType_t override; + auto ProcessStreamInfo(const StreamInfo& info) + -> cpp::result override; + auto ProcessChunk(const cpp::span& chunk) + -> cpp::result override; + auto ProcessIdle() -> cpp::result override; + + I2SAudioOutput(const I2SAudioOutput&) = delete; + I2SAudioOutput& operator=(const I2SAudioOutput&) = delete; private: - std::unique_ptr dac_; - bool is_soft_muted_ = false; + auto SetVolume(uint8_t volume) -> void; + auto SetSoftMute(bool enabled) -> void; + + drivers::GpioExpander* expander_; + std::unique_ptr dac_; + + uint8_t volume_; + bool is_soft_muted_; }; -} // namespace drivers +} // namespace audio diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp index 6077314c..1f5791b9 100644 --- a/src/codecs/include/mad.hpp +++ b/src/codecs/include/mad.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "mad.h" #include "span.hpp" diff --git a/src/codecs/test/test.mp3.hpp b/src/codecs/test/test.mp3.hpp index fcf24af0..f6699b85 100644 --- a/src/codecs/test/test.mp3.hpp +++ b/src/codecs/test/test.mp3.hpp @@ -1,101 +1,100 @@ #include std::uint8_t test_mp3[] = { - 0xff, 0xfb, 0x90, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x69, 0x6e, - 0x67, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, - 0x7b, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, - 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, - 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, - 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, - 0xdb, 0xdb, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x50, 0x4c, 0x41, 0x4d, - 0x45, 0x33, 0x2e, 0x31, 0x30, 0x30, 0x04, 0xb9, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x15, 0x20, 0x24, 0x06, 0xb6, 0x41, 0x00, 0x01, - 0xe0, 0x00, 0x00, 0x04, 0x7b, 0xe4, 0x2f, 0x1b, 0xba, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xb0, - 0xc4, 0x00, 0x00, 0x0a, 0x00, 0x03, 0x6b, 0x80, 0x80, 0x00, 0x21, 0xc4, - 0x86, 0x6c, 0x7c, 0xf7, 0xb0, 0x41, 0x05, 0x4f, 0x59, 0x20, 0x0d, 0x53, - 0x58, 0xc1, 0xf0, 0x7c, 0x1f, 0x85, 0xcf, 0x83, 0xe2, 0x70, 0x70, 0x10, - 0x04, 0x01, 0x00, 0x40, 0x1f, 0x07, 0xc1, 0xf0, 0x7c, 0x1c, 0x04, 0x01, - 0x0f, 0xa8, 0x13, 0x07, 0xc1, 0xf0, 0x7c, 0x1f, 0x04, 0x03, 0x00, 0x87, - 0xd0, 0x0f, 0x83, 0xe0, 0xf8, 0x3e, 0x0e, 0x02, 0x01, 0x8f, 0xd2, 0x5c, - 0x1f, 0x07, 0xc3, 0xe0, 0x80, 0x20, 0x08, 0x3b, 0xf1, 0x38, 0x3e, 0x0f, - 0x83, 0x80, 0x80, 0x63, 0xfc, 0x1f, 0x07, 0xc1, 0xf0, 0x40, 0x10, 0x0c, - 0x7f, 0xe8, 0x77, 0x86, 0x56, 0x64, 0x34, 0x52, 0x03, 0x4d, 0x37, 0x14, - 0xc4, 0x18, 0x4d, 0x84, 0xd8, 0x0c, 0xc0, 0x66, 0x00, 0x20, 0x02, 0x04, - 0x81, 0xa8, 0x00, 0x80, 0x08, 0x00, 0x80, 0x50, 0x22, 0x24, 0x98, 0x98, - 0xae, 0x5c, 0xb9, 0x72, 0xe5, 0xcb, 0x82, 0xbe, 0x17, 0x02, 0x82, 0x41, - 0x41, 0x41, 0x41, 0x42, 0x82, 0x82, 0x82, 0x8e, 0x05, 0x05, 0xff, 0xe8, - 0x28, 0x28, 0x28, 0x30, 0x50, 0x50, 0x51, 0x41, 0x5f, 0xff, 0xe0, 0xa0, - 0xa0, 0xa0, 0xa1, 0x41, 0x41, 0x41, 0x47, 0x7f, 0xff, 0xff, 0xf8, 0x28, - 0x30, 0x50, 0x50, 0x50, 0x57, 0xff, 0xff, 0xff, 0xf4, 0x14, 0x28, 0x28, - 0x28, 0x28, 0xaf, 0xff, 0xff, 0xff, 0x82, 0x83, 0x05, 0x05, 0x05, 0x05, - 0x02, 0x8a, 0xff, 0xff, 0xfe, 0x82, 0x85, 0x2a, 0x4c, 0x41, 0x4d, 0x45, - 0x33, 0x2e, 0x31, 0x30, 0x30, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, - 0xfb, 0x10, 0xc4, 0xcd, 0x83, 0xc0, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x04, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa -}; + 0xff, 0xfb, 0x90, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x69, 0x6e, + 0x67, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, + 0x7b, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + 0xdb, 0xdb, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x50, 0x4c, 0x41, 0x4d, + 0x45, 0x33, 0x2e, 0x31, 0x30, 0x30, 0x04, 0xb9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x20, 0x24, 0x06, 0xb6, 0x41, 0x00, 0x01, + 0xe0, 0x00, 0x00, 0x04, 0x7b, 0xe4, 0x2f, 0x1b, 0xba, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xb0, + 0xc4, 0x00, 0x00, 0x0a, 0x00, 0x03, 0x6b, 0x80, 0x80, 0x00, 0x21, 0xc4, + 0x86, 0x6c, 0x7c, 0xf7, 0xb0, 0x41, 0x05, 0x4f, 0x59, 0x20, 0x0d, 0x53, + 0x58, 0xc1, 0xf0, 0x7c, 0x1f, 0x85, 0xcf, 0x83, 0xe2, 0x70, 0x70, 0x10, + 0x04, 0x01, 0x00, 0x40, 0x1f, 0x07, 0xc1, 0xf0, 0x7c, 0x1c, 0x04, 0x01, + 0x0f, 0xa8, 0x13, 0x07, 0xc1, 0xf0, 0x7c, 0x1f, 0x04, 0x03, 0x00, 0x87, + 0xd0, 0x0f, 0x83, 0xe0, 0xf8, 0x3e, 0x0e, 0x02, 0x01, 0x8f, 0xd2, 0x5c, + 0x1f, 0x07, 0xc3, 0xe0, 0x80, 0x20, 0x08, 0x3b, 0xf1, 0x38, 0x3e, 0x0f, + 0x83, 0x80, 0x80, 0x63, 0xfc, 0x1f, 0x07, 0xc1, 0xf0, 0x40, 0x10, 0x0c, + 0x7f, 0xe8, 0x77, 0x86, 0x56, 0x64, 0x34, 0x52, 0x03, 0x4d, 0x37, 0x14, + 0xc4, 0x18, 0x4d, 0x84, 0xd8, 0x0c, 0xc0, 0x66, 0x00, 0x20, 0x02, 0x04, + 0x81, 0xa8, 0x00, 0x80, 0x08, 0x00, 0x80, 0x50, 0x22, 0x24, 0x98, 0x98, + 0xae, 0x5c, 0xb9, 0x72, 0xe5, 0xcb, 0x82, 0xbe, 0x17, 0x02, 0x82, 0x41, + 0x41, 0x41, 0x41, 0x42, 0x82, 0x82, 0x82, 0x8e, 0x05, 0x05, 0xff, 0xe8, + 0x28, 0x28, 0x28, 0x30, 0x50, 0x50, 0x51, 0x41, 0x5f, 0xff, 0xe0, 0xa0, + 0xa0, 0xa0, 0xa1, 0x41, 0x41, 0x41, 0x47, 0x7f, 0xff, 0xff, 0xf8, 0x28, + 0x30, 0x50, 0x50, 0x50, 0x57, 0xff, 0xff, 0xff, 0xf4, 0x14, 0x28, 0x28, + 0x28, 0x28, 0xaf, 0xff, 0xff, 0xff, 0x82, 0x83, 0x05, 0x05, 0x05, 0x05, + 0x02, 0x8a, 0xff, 0xff, 0xfe, 0x82, 0x85, 0x2a, 0x4c, 0x41, 0x4d, 0x45, + 0x33, 0x2e, 0x31, 0x30, 0x30, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, + 0xfb, 0x10, 0xc4, 0xcd, 0x83, 0xc0, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x04, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}; std::size_t test_mp3_len = 1147; diff --git a/src/codecs/test/test_mad.cpp b/src/codecs/test/test_mad.cpp index 388efa17..d5522775 100644 --- a/src/codecs/test/test_mad.cpp +++ b/src/codecs/test/test_mad.cpp @@ -1,15 +1,16 @@ #include "mad.hpp" -#include #include +#include -#include "span.hpp" #include "catch2/catch.hpp" +#include "span.hpp" #include "test.mp3.hpp" void load_mp3(cpp::span dest) { - cpp::span src(reinterpret_cast(test_mp3), test_mp3_len); + cpp::span src(reinterpret_cast(test_mp3), + test_mp3_len); std::copy(src.begin(), src.begin() + dest.size(), dest.begin()); } @@ -24,7 +25,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") { } SECTION("processes streams correctly") { - std::array input; + std::array input; input.fill(std::byte{0}); decoder.ResetForNewStream(); @@ -38,7 +39,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") { } SECTION("invalid stream fails") { - input.fill(std::byte{0xFF}); + input.fill(std::byte{0x69}); auto result = decoder.ProcessNextFrame(); @@ -65,23 +66,26 @@ TEST_CASE("libmad mp3 decoder", "[unit]") { // Just check that some kind of data was written. We don't care // about what. - REQUIRE(std::to_integer(output[0]) != 0); - REQUIRE(std::to_integer(output[1]) != 0); - REQUIRE(std::to_integer(output[2]) != 0); - REQUIRE(std::to_integer(output[3]) != 0); + bool wrote_something = false; + for (int i = 0; i < output.size(); i++) { + if (std::to_integer(output[0]) != 0) { + wrote_something = true; + break; + } + } + REQUIRE(wrote_something == true); } SECTION("output format correct") { auto format = decoder.GetOutputFormat(); // Matches the test data. - REQUIRE(format.num_channels == 2); + REQUIRE(format.num_channels == 1); REQUIRE(format.sample_rate_hz == 44100); // Matches libmad output REQUIRE(format.bits_per_sample == 24); } } - } } diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index fbecf1c8..4c8d4275 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -2,5 +2,5 @@ idf_component_register( SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" "spi.cpp" "display.cpp" "display_init.cpp" INCLUDE_DIRS "include" - REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl") + REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl" "span") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/drivers/dac.cpp b/src/drivers/dac.cpp index f01d701f..23c67e88 100644 --- a/src/drivers/dac.cpp +++ b/src/drivers/dac.cpp @@ -4,11 +4,13 @@ #include "assert.h" #include "driver/i2c.h" +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" #include "hal/i2c_types.h" #include "gpio_expander.hpp" +#include "hal/i2s_types.h" #include "i2c.hpp" namespace drivers { @@ -16,11 +18,55 @@ namespace drivers { static const char* kTag = "AUDIODAC"; static const uint8_t kPcm5122Address = 0x4C; static const uint8_t kPcm5122Timeout = 100 / portTICK_RATE_MS; +static const i2s_port_t kI2SPort = I2S_NUM_0; + +static const AudioDac::SampleRate kDefaultSampleRate = + AudioDac::SAMPLE_RATE_44_1; +static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16; auto AudioDac::create(GpioExpander* expander) -> cpp::result, Error> { + // First, instantiate the instance so it can do all of its power on + // configuration. std::unique_ptr dac = std::make_unique(expander); + // Whilst we wait for the initial boot, we can work on installing the I2S + // driver. + i2s_config_t i2s_config = { + // static_cast bc esp-adf uses enums incorrectly + .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), + .sample_rate = 44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LOWMED, + // TODO(jacqueline): tune dma buffer size. this seems very smol. + .dma_buf_count = 8, + .dma_buf_len = 64, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, + }; + + if (esp_err_t err = + i2s_driver_install(kI2SPort, &i2s_config, 0, NULL) != ESP_OK) { + ESP_LOGE(kTag, "failed to configure i2s pins %x", err); + return cpp::fail(Error::FAILED_TO_INSTALL_I2S); + } + + i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0, + .bck_io_num = GPIO_NUM_26, + .ws_io_num = GPIO_NUM_27, + .data_out_num = GPIO_NUM_5, + .data_in_num = I2S_PIN_NO_CHANGE}; + if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) { + ESP_LOGE(kTag, "failed to configure i2s pins %x", err); + return cpp::fail(Error::FAILED_TO_INSTALL_I2S); + } + + // Now let's double check that the DAC itself came up whilst we we working. bool is_booted = dac->WaitForPowerState( [](bool booted, PowerState state) { return booted; }); if (!is_booted) { @@ -28,8 +74,9 @@ auto AudioDac::create(GpioExpander* expander) return cpp::fail(Error::FAILED_TO_BOOT); } + // Write the initial configuration. dac->WriteRegister(Register::DE_EMPHASIS, 1 << 4); - dac->WriteVolume(100); + dac->WriteVolume(255); bool is_configured = dac->WaitForPowerState([](bool booted, PowerState state) { @@ -43,14 +90,16 @@ auto AudioDac::create(GpioExpander* expander) return dac; } -AudioDac::AudioDac(GpioExpander* gpio) { - this->gpio_ = gpio; -}; +AudioDac::AudioDac(GpioExpander* gpio) : gpio_(gpio) { + gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true); + gpio_->Write(); +} -AudioDac::~AudioDac(){ - // TODO: reset stuff like de-emphasis? Reboot the whole dac? Need to think - // about this. -}; +AudioDac::~AudioDac() { + i2s_driver_uninstall(kI2SPort); + gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, false); + gpio_->Write(); +} void AudioDac::WriteVolume(uint8_t volume) { WriteRegister(Register::DIGITAL_VOLUME_L, volume); @@ -93,6 +142,21 @@ bool AudioDac::WaitForPowerState( return has_matched; } +auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool { + // TODO(jacqueline): investigate how reliable the auto-clocking of the dac + // is. We might need to explicit reconfigure the dac here as well if it's not + // good enough. + i2s_set_clk(kI2SPort, rate, bps, I2S_CHANNEL_STEREO); + return true; +} + +auto AudioDac::WriteData(const cpp::span& data, TickType_t max_wait) + -> std::size_t { + std::size_t res = 0; + i2s_write(kI2SPort, data.data(), data.size(), &res, max_wait); + return res; +} + void AudioDac::WriteRegister(Register reg, uint8_t val) { I2CTransaction transaction; transaction.start() diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp index 6d812b6a..dc03624b 100644 --- a/src/drivers/include/dac.hpp +++ b/src/drivers/include/dac.hpp @@ -5,7 +5,10 @@ #include #include "esp_err.h" +#include "freertos/portmacro.h" +#include "hal/i2s_types.h" #include "result.hpp" +#include "span.hpp" #include "gpio_expander.hpp" @@ -19,7 +22,9 @@ class AudioDac { enum Error { FAILED_TO_BOOT, FAILED_TO_CONFIGURE, + FAILED_TO_INSTALL_I2S, }; + static auto create(GpioExpander* expander) -> cpp::result, Error>; @@ -47,6 +52,22 @@ class AudioDac { /* Returns the current boot-up status and internal state of the DAC */ std::pair ReadPowerState(); + enum BitsPerSample { + BPS_16 = I2S_BITS_PER_SAMPLE_16BIT, + BPS_24 = I2S_BITS_PER_SAMPLE_24BIT, + BPS_32 = I2S_BITS_PER_SAMPLE_32BIT + }; + enum SampleRate { + SAMPLE_RATE_44_1 = 44100, + SAMPLE_RATE_48 = 48000, + }; + + // TODO(jacqueline): worth supporting channels here as well? + auto Reconfigure(BitsPerSample bps, SampleRate rate) -> bool; + + auto WriteData(const cpp::span& data, TickType_t max_wait) + -> std::size_t; + // Not copyable or movable. AudioDac(const AudioDac&) = delete; AudioDac& operator=(const AudioDac&) = delete; diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 9bfefb1d..df0d929b 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "main.cpp" "app_console.cpp" INCLUDE_DIRS "." - REQUIRES "drivers" "dev_console") + REQUIRES "audio" "drivers" "dev_console" "drivers") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/test/main/main.cpp b/test/main/main.cpp index 21471fce..e012218c 100644 --- a/test/main/main.cpp +++ b/test/main/main.cpp @@ -24,8 +24,9 @@ class TestConsole : public Console { protected: virtual auto RegisterExtraComponents() -> void { RegisterCatch2(); } virtual auto GetStackSizeKiB() -> uint16_t { - // Catch2 requires a particularly large stack. - return 24; + // Catch2 requires a particularly large stack to begin with, and some of the + // tests (*cough*libmad*cough*) also use a lot of stack. + return 64; } };