From f35bb64c2b8dbb72fd15f1880e4d01d263660910 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 6 Dec 2022 13:17:56 +1100 Subject: basic i2s output element --- src/audio/CMakeLists.txt | 2 +- src/audio/audio_decoder.cpp | 4 +- src/audio/chunk.cpp | 15 +++- src/audio/fatfs_audio_input.cpp | 7 +- src/audio/i2s_audio_output.cpp | 147 +++++++++++++++++--------------- src/audio/include/audio_decoder.hpp | 13 +-- src/audio/include/audio_element.hpp | 12 +-- src/audio/include/fatfs_audio_input.hpp | 14 +-- src/audio/include/i2s_audio_output.hpp | 38 ++++++--- 9 files changed, 143 insertions(+), 109 deletions(-) (limited to 'src/audio') 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 -- cgit v1.2.3