diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index d90ca496..97f45534 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -21,7 +21,7 @@ namespace audio { static const char* kTag = "DEC"; -static const std::size_t kSamplesPerChunk = 256; +static const std::size_t kSamplesPerChunk = 1024; AudioDecoder::AudioDecoder() : IAudioElement(), @@ -89,14 +89,12 @@ auto AudioDecoder::ProcessEndOfStream() -> void { needs_more_input_ = true; current_codec_.reset(); - SendOrBufferEvent( - std::unique_ptr( - StreamEvent::CreateEndOfStream(input_events_))); + SendOrBufferEvent(std::unique_ptr( + StreamEvent::CreateEndOfStream(input_events_))); } auto AudioDecoder::Process() -> cpp::result { if (has_samples_to_send_) { - ESP_LOGI(kTag, "sending samples"); // Writing samples is relatively quick (it's just a bunch of memcopy's), so // do them all at once. while (has_samples_to_send_ && !IsOverBuffered()) { @@ -132,7 +130,6 @@ auto AudioDecoder::Process() -> cpp::result { } if (!needs_more_input_) { - ESP_LOGI(kTag, "decoding frame"); auto res = current_codec_->ProcessNextFrame(); if (res.has_error()) { // todo diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index 078aa461..9d0c4bd0 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -38,8 +38,9 @@ auto StartAudioTask(const std::string& name, ESP_LOGI(kTag, "starting audio task %s", name.c_str()); if (core_id) { - xTaskCreatePinnedToCore(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args, - kTaskPriorityAudio, task_handle.get(), *core_id); + xTaskCreatePinnedToCore(&AudioTaskMain, name.c_str(), + element->StackSizeBytes(), args, kTaskPriorityAudio, + task_handle.get(), *core_id); } else { xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args, kTaskPriorityAudio, task_handle.get()); diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 8990bf4f..fd1c1f3a 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -71,11 +71,10 @@ auto FatfsAudioInput::ProcessChunk(const cpp::span& chunk) auto FatfsAudioInput::ProcessEndOfStream() -> void { if (is_file_open_) { - f_close(¤t_file_); - is_file_open_ = false; - SendOrBufferEvent( - std::unique_ptr( - StreamEvent::CreateEndOfStream(input_events_))); + f_close(¤t_file_); + is_file_open_ = false; + SendOrBufferEvent(std::unique_ptr( + StreamEvent::CreateEndOfStream(input_events_))); } } @@ -85,7 +84,6 @@ auto FatfsAudioInput::Process() -> cpp::result { StreamEvent::CreateChunkData(input_events_, kChunkSize)); UINT bytes_read = 0; - ESP_LOGI(kTag, "reading from file"); FRESULT result = f_read(¤t_file_, dest_event->chunk_data.raw_bytes, kChunkSize, &bytes_read); if (result != FR_OK) { @@ -93,13 +91,11 @@ auto FatfsAudioInput::Process() -> cpp::result { return cpp::fail(IO_ERROR); } - ESP_LOGI(kTag, "sending file data (%u bytes)", bytes_read); dest_event->chunk_data.bytes = dest_event->chunk_data.bytes.first(bytes_read); SendOrBufferEvent(std::move(dest_event)); if (bytes_read < kChunkSize || f_eof(¤t_file_)) { - ESP_LOGI(kTag, "closing file"); f_close(¤t_file_); is_file_open_ = false; } diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp index 3bed15b5..7ecadc03 100644 --- a/src/audio/i2s_audio_output.cpp +++ b/src/audio/i2s_audio_output.cpp @@ -43,22 +43,12 @@ I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander, volume_(255), is_soft_muted_(false), chunk_reader_(), - latest_chunk_(), - dma_size_(), - dma_queue_(nullptr) {} + latest_chunk_() {} -I2SAudioOutput::~I2SAudioOutput() { - if (dma_queue_ != nullptr) { - ClearDmaQueue(); - } - // TODO: power down the DAC. -} +I2SAudioOutput::~I2SAudioOutput() {} auto I2SAudioOutput::HasUnprocessedInput() -> bool { - if (dma_queue_ == nullptr || !dma_size_) { - return false; - } - return latest_chunk_.size() >= *dma_size_; + return latest_chunk_.size() > 0; } auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info) @@ -108,77 +98,30 @@ auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info) return cpp::fail(UNSUPPORTED_STREAM); } - QueueHandle_t new_dma_queue = - xQueueCreate(kDmaQueueLength, sizeof(std::byte*)); - - dma_size_ = dac_->Reconfigure(bps, sample_rate, new_dma_queue); - - if (dma_queue_ != nullptr) { - ClearDmaQueue(); - } - dma_queue_ = new_dma_queue; + dac_->Reconfigure(bps, sample_rate); return {}; } auto I2SAudioOutput::ProcessChunk(const cpp::span& chunk) -> cpp::result { - ESP_LOGI(kTag, "received new samples"); latest_chunk_ = chunk_reader_->HandleNewData(chunk); return 0; } auto I2SAudioOutput::ProcessEndOfStream() -> void { - if (chunk_reader_ && dma_size_) { - auto leftovers = chunk_reader_->GetLeftovers(); - if (leftovers.size() > 0 && leftovers.size() < *dma_size_) { - std::byte* dest = static_cast(malloc(*dma_size_)); - cpp::span dest_span(dest, *dma_size_); - - std::copy(leftovers.begin(), leftovers.end(), dest_span.begin()); - std::fill(dest_span.begin() + leftovers.size(), dest_span.end(), static_cast(0)); - - xQueueSend(dma_queue_, &dest, portMAX_DELAY); - } - } - - SendOrBufferEvent( - std::unique_ptr( - StreamEvent::CreateEndOfStream(input_events_))); - - chunk_reader_.reset(); - dma_size_.reset(); + SendOrBufferEvent(std::unique_ptr( + StreamEvent::CreateEndOfStream(input_events_))); } auto I2SAudioOutput::Process() -> cpp::result { - std::size_t spaces_available = uxQueueSpacesAvailable(dma_queue_); - if (spaces_available == 0) { - // TODO: think about this more. can this just be the output event queue? - vTaskDelay(pdMS_TO_TICKS(100)); - return {}; - } - - // Fill the queue as much as possible, since we need to be able to stream - // FAST. - while (latest_chunk_.size() >= *dma_size_ && spaces_available > 0) { - // TODO: small memory arena for this? - std::byte* dest = static_cast(malloc(*dma_size_)); - cpp::span dest_span(dest, *dma_size_); - cpp::span src_span = latest_chunk_.first(*dma_size_); - std::copy(src_span.begin(), src_span.end(), dest_span.begin()); - if (!xQueueSend(dma_queue_, &dest, 0)) { - // TODO: calculate how often we expect this to happen. - free(dest); - break; - } - latest_chunk_ = latest_chunk_.subspan(*dma_size_); - ESP_LOGI(kTag, "wrote dma buffer of size %u", *dma_size_); - } - if (latest_chunk_.size() < *dma_size_) { - // TODO: if this is the end of the stream, then we should be sending this - // with zero padding. hmm. i guess we need an explicit EOF event? - chunk_reader_->HandleBytesLeftOver(latest_chunk_.size()); - ESP_LOGI(kTag, "not enough samples for dma buffer"); + // Note: no logging here! + std::size_t bytes_written = dac_->WriteData(latest_chunk_); + if (bytes_written == latest_chunk_.size_bytes()) { + latest_chunk_ = cpp::span(); + chunk_reader_->HandleBytesLeftOver(0); + } else { + latest_chunk_ = latest_chunk_.subspan(bytes_written); } return {}; } @@ -202,17 +145,4 @@ auto I2SAudioOutput::SetSoftMute(bool enabled) -> void { } } -auto I2SAudioOutput::ClearDmaQueue() -> void { - // Ensure we don't leak any memory from events leftover in the queue. - while (uxQueueSpacesAvailable(dma_queue_) < kDmaQueueLength) { - std::byte* data = nullptr; - if (xQueueReceive(input_events_, &data, 0)) { - free(data); - } else { - break; - } - } - vQueueDelete(dma_queue_); -} - } // namespace audio diff --git a/src/audio/include/audio_task.hpp b/src/audio/include/audio_task.hpp index 399ad679..0b353735 100644 --- a/src/audio/include/audio_task.hpp +++ b/src/audio/include/audio_task.hpp @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include "audio_element.hpp" #include "audio_element_handle.hpp" diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp index b4fd4c59..fc406665 100644 --- a/src/audio/include/i2s_audio_output.hpp +++ b/src/audio/include/i2s_audio_output.hpp @@ -38,8 +38,6 @@ class I2SAudioOutput : public IAudioElement { auto SetVolume(uint8_t volume) -> void; auto SetSoftMute(bool enabled) -> void; - auto ClearDmaQueue() -> void; - drivers::GpioExpander* expander_; std::unique_ptr dac_; @@ -48,8 +46,6 @@ class I2SAudioOutput : public IAudioElement { std::optional chunk_reader_; cpp::span latest_chunk_; - std::optional dma_size_; - QueueHandle_t dma_queue_; }; } // namespace audio diff --git a/src/drivers/dac.cpp b/src/drivers/dac.cpp index eec1959f..70f344c4 100644 --- a/src/drivers/dac.cpp +++ b/src/drivers/dac.cpp @@ -8,9 +8,11 @@ #include "driver/i2s_common.h" #include "driver/i2s_std.h" #include "driver/i2s_types.h" +#include "esp_attr.h" #include "esp_err.h" #include "esp_log.h" #include "freertos/portmacro.h" +#include "freertos/projdefs.h" #include "hal/i2c_types.h" #include "gpio_expander.hpp" @@ -28,22 +30,13 @@ static const AudioDac::SampleRate kDefaultSampleRate = AudioDac::SAMPLE_RATE_44_1; static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16; -extern "C" { -bool dma_callback(i2s_chan_handle_t handle, - i2s_event_data_t* event, - void* user_ctx) { - AudioDac* dac = static_cast(user_ctx); - return dac->WriteDataFromISR(static_cast(event->data), - event->size); -} -} - auto AudioDac::create(GpioExpander* expander) -> cpp::result, Error> { // TODO: tune. i2s_chan_handle_t i2s_handle; i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG(kI2SPort, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL)); // // First, instantiate the instance so it can do all of its power on @@ -108,8 +101,7 @@ AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle) i2s_handle_(i2s_handle), clock_config_(I2S_STD_CLK_DEFAULT_CONFIG(44100)), slot_config_(I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, - I2S_SLOT_MODE_STEREO)), - dma_queue_(nullptr) { + I2S_SLOT_MODE_STEREO)) { gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true); gpio_->Write(); } @@ -167,9 +159,7 @@ bool AudioDac::WaitForPowerState( return has_matched; } -auto AudioDac::Reconfigure(BitsPerSample bps, - SampleRate rate, - QueueHandle_t dma_queue) -> std::size_t { +auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void { // 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. @@ -183,44 +173,17 @@ auto AudioDac::Reconfigure(BitsPerSample bps, bps == BPS_24 ? I2S_MCLK_MULTIPLE_384 : I2S_MCLK_MULTIPLE_256; ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_)); - dma_queue_ = dma_queue; - - // TODO: less spooky action here plz. - // dma_buffer_size = dma_frame_num (channel config) * slot_num (always 2?) * - // slot_bit_width / 8 - //size_t dma_size = 240 * 2 * slot_config_.slot_bit_width / 8; - size_t dma_size = 960; - ESP_LOGI(kTag, "new dma size: %u bytes", dma_size); - - i2s_event_callbacks_t callbacks = { - .on_recv = NULL, - .on_recv_q_ovf = NULL, - .on_sent = &dma_callback, - .on_send_q_ovf = NULL, - }; - ESP_ERROR_CHECK( - i2s_channel_register_event_callback(i2s_handle_, &callbacks, this)); - ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_)); - - return dma_size; } -auto AudioDac::WriteDataFromISR(std::byte* data, std::size_t size) -> bool { - std::byte* new_data; - BaseType_t high_priority_task_awoken = pdFALSE; - - if (xQueueReceiveFromISR(dma_queue_, &new_data, &high_priority_task_awoken)) { - // Item was received. Copy it into the DMA buffer. - memcpy(data, new_data, size); - free(new_data); - ESP_DRAM_LOGI(kTag, "wrote dma"); - } else { - // No item was received. Write empty data. - memset(data, 0, size); +auto AudioDac::WriteData(cpp::span data) -> std::size_t { + std::size_t bytes_written = 0; + esp_err_t err = i2s_channel_write(i2s_handle_, data.data(), data.size_bytes(), + &bytes_written, 0); + if (err != ESP_ERR_TIMEOUT) { + ESP_ERROR_CHECK(err); } - - return high_priority_task_awoken; + return bytes_written; } void AudioDac::WriteRegister(Register reg, uint8_t val) { diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp index ce8e7169..951a45eb 100644 --- a/src/drivers/display.cpp +++ b/src/drivers/display.cpp @@ -50,8 +50,8 @@ namespace callback { static std::atomic instance = nullptr; extern "C" void flush_cb(lv_disp_drv_t* disp_drv, - const lv_area_t* area, - lv_color_t* color_map) { + const lv_area_t* area, + lv_color_t* color_map) { auto instance_unwrapped = instance.load(); if (instance_unwrapped == nullptr) { ESP_LOGW(kTag, "uncaught flush callback"); diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp index 698019b7..e682d8d7 100644 --- a/src/drivers/include/dac.hpp +++ b/src/drivers/include/dac.hpp @@ -10,6 +10,7 @@ #include "driver/i2s_std.h" #include "driver/i2s_types.h" #include "esp_err.h" +#include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "result.hpp" #include "span.hpp" @@ -67,10 +68,9 @@ class AudioDac { }; // TODO(jacqueline): worth supporting channels here as well? - auto Reconfigure(BitsPerSample bps, SampleRate rate, QueueHandle_t dma_queue) - -> std::size_t; + auto Reconfigure(BitsPerSample bps, SampleRate rate) -> void; - auto WriteDataFromISR(std::byte* data, std::size_t size) -> bool; + auto WriteData(cpp::span data) -> std::size_t; // Not copyable or movable. AudioDac(const AudioDac&) = delete; @@ -83,9 +83,6 @@ class AudioDac { i2s_std_clk_config_t clock_config_; i2s_std_slot_config_t slot_config_; - // TODO: volatile? - volatile QueueHandle_t dma_queue_; - /* * Pools the power state for up to 10ms, waiting for the given predicate to * be true.