From 3836768bb8b95188e6657ab69027d1d9e4b13a77 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 3 Apr 2023 14:06:30 +1000 Subject: [PATCH] new pipeline working(?), but the dac eludes me --- src/audio/audio_decoder.cpp | 10 ++++ src/audio/audio_playback.cpp | 5 +- src/audio/audio_task.cpp | 20 +++++++- src/audio/fatfs_audio_input.cpp | 3 ++ src/audio/i2s_audio_output.cpp | 6 +++ src/audio/include/audio_sink.hpp | 1 + src/audio/include/i2s_audio_output.hpp | 1 + src/audio/include/stream_info.hpp | 13 +++-- src/audio/pipeline.cpp | 5 +- src/drivers/dac.cpp | 66 +++++++++++++++++--------- src/drivers/include/dac.hpp | 5 ++ src/main/main.cpp | 2 +- src/memory/include/himem.hpp | 13 +++-- 13 files changed, 112 insertions(+), 38 deletions(-) diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index ada1f8f7..03c7e998 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "cbor/tinycbor/src/cborinternal_p.h" #include "freertos/FreeRTOS.h" @@ -37,6 +38,7 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { if (!std::holds_alternative(info.format)) { return false; } + ESP_LOGI(kTag, "got new stream"); const auto& encoded = std::get(info.format); // Reuse the existing codec if we can. This will help with gapless playback, @@ -45,12 +47,14 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { if (current_codec_ != nullptr && current_codec_->CanHandleType(encoded.type)) { current_codec_->ResetForNewStream(); + ESP_LOGI(kTag, "reusing existing decoder"); return true; } // TODO: use audio type from stream auto result = codecs::CreateCodecForType(encoded.type); if (result.has_value()) { + ESP_LOGI(kTag, "creating new decoder"); current_codec_ = std::move(result.value()); } else { ESP_LOGE(kTag, "no codec for this file"); @@ -73,6 +77,9 @@ auto AudioDecoder::Process(const std::vector& inputs, } const StreamInfo& info = input->info(); + if (std::holds_alternative(info.format)) { + return; + } if (!current_input_format_ || *current_input_format_ != info.format) { // The input stream has changed! Immediately throw everything away and // start from scratch. @@ -100,6 +107,9 @@ auto AudioDecoder::Process(const std::vector& inputs, } auto write_res = current_codec_->WriteOutputSamples(output->data()); + if (write_res.first > 0) { + ESP_LOGI(kTag, "wrote %u bytes of samples", write_res.first); + } output->add(write_res.first); has_samples_to_send_ = !write_res.second; diff --git a/src/audio/audio_playback.cpp b/src/audio/audio_playback.cpp index 89139ec4..9a978535 100644 --- a/src/audio/audio_playback.cpp +++ b/src/audio/audio_playback.cpp @@ -32,7 +32,8 @@ auto AudioPlayback::create(drivers::GpioExpander* expander) } AudioPlayback::AudioPlayback(std::unique_ptr output) - : file_source_(), i2s_output_(std::move(output)) { + : file_source_(std::make_unique()), + i2s_output_(std::move(output)) { AudioDecoder* codec = new AudioDecoder(); elements_.emplace_back(codec); @@ -51,7 +52,7 @@ auto AudioPlayback::Play(const std::string& filename) -> void { } auto AudioPlayback::LogStatus() -> void { - // TODO. + i2s_output_->Log(); } } // namespace audio diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index e6c7778c..eb33611b 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "audio_sink.hpp" #include "cbor.h" @@ -114,16 +115,29 @@ void AudioTaskMain(void* args) { OutputStream out_stream(&raw_out_stream); elements.at(i)->OutputElement()->Process(in_streams, &out_stream); + + std::for_each(in_regions.begin(), in_regions.end(), + [](auto&& r) { r.Unmap(); }); + out_region.Unmap(); } - RawStream raw_sink_stream = elements.back()->OutStream(&out_region); + RawStream raw_sink_stream = elements.front()->OutStream(&out_region); InputStream sink_stream(&raw_sink_stream); - if (!output_format || output_format != sink_stream.info().format) { + if (sink_stream.data().size_bytes() == 0) { + out_region.Unmap(); + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + + if ((!output_format || output_format != sink_stream.info().format) && + !std::holds_alternative( + sink_stream.info().format)) { // The format of the stream within the sink stream has changed. We // need to reconfigure the sink, but shouldn't do so until we've fully // drained the current buffer. if (xStreamBufferIsEmpty(sink->buffer())) { + ESP_LOGI(kTag, "reconfiguring dac"); output_format = sink_stream.info().format; sink->Configure(*output_format); } @@ -140,6 +154,8 @@ void AudioTaskMain(void* args) { sink_stream.data().size_bytes(), pdMS_TO_TICKS(10)); sink_stream.consume(sent); } + + out_region.Unmap(); } } } diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index b9882711..240f7084 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -65,6 +65,9 @@ auto FatfsAudioInput::Process(const std::vector& inputs, return; } + if (size > 0) { + ESP_LOGI(kTag, "read %u bytes", size); + } output->add(size); if (size < max_size || f_eof(¤t_file_)) { diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp index 55d45001..2d336152 100644 --- a/src/audio/i2s_audio_output.cpp +++ b/src/audio/i2s_audio_output.cpp @@ -44,12 +44,14 @@ I2SAudioOutput::~I2SAudioOutput() {} auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool { if (!std::holds_alternative(format)) { + ESP_LOGI(kTag, "ignoring non-pcm stream (%d)", format.index()); return false; } StreamInfo::Pcm pcm = std::get(format); if (current_config_ && pcm == *current_config_) { + ESP_LOGI(kTag, "ignoring unchanged format"); return true; } @@ -97,6 +99,10 @@ auto I2SAudioOutput::Send(const cpp::span& data) -> void { dac_->WriteData(data); } +auto I2SAudioOutput::Log() -> void { + dac_->LogStatus(); +} + auto I2SAudioOutput::SetVolume(uint8_t volume) -> void { dac_->WriteVolume(volume); } diff --git a/src/audio/include/audio_sink.hpp b/src/audio/include/audio_sink.hpp index ed7eb02b..ad63ec2e 100644 --- a/src/audio/include/audio_sink.hpp +++ b/src/audio/include/audio_sink.hpp @@ -15,6 +15,7 @@ class IAudioSink { virtual auto Configure(const StreamInfo::Format& format) -> bool = 0; virtual auto Send(const cpp::span& data) -> void = 0; + virtual auto Log() -> void {} auto buffer() const -> StreamBufferHandle_t { return buffer_; } }; diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp index 77019228..31510a91 100644 --- a/src/audio/include/i2s_audio_output.hpp +++ b/src/audio/include/i2s_audio_output.hpp @@ -27,6 +27,7 @@ class I2SAudioOutput : public IAudioSink { auto Configure(const StreamInfo::Format& format) -> bool override; auto Send(const cpp::span& data) -> void override; + auto Log() -> void override; I2SAudioOutput(const I2SAudioOutput&) = delete; I2SAudioOutput& operator=(const I2SAudioOutput&) = delete; diff --git a/src/audio/include/stream_info.hpp b/src/audio/include/stream_info.hpp index 5622517f..5a36384c 100644 --- a/src/audio/include/stream_info.hpp +++ b/src/audio/include/stream_info.hpp @@ -17,12 +17,12 @@ namespace audio { struct StreamInfo { // The number of bytes that are available for consumption within this // stream's buffer. - std::size_t bytes_in_stream; + std::size_t bytes_in_stream{0}; // The total length of this stream, in case its source is finite (e.g. a // file on disk). May be absent for endless streams (internet streams, // generated audio, etc.) - std::optional length_bytes; + std::optional length_bytes{}; struct Encoded { // The codec that this stream is associated with. @@ -42,8 +42,8 @@ struct StreamInfo { bool operator==(const Pcm&) const = default; }; - typedef std::variant Format; - Format format; + typedef std::variant Format; + Format format{}; bool operator==(const StreamInfo&) const = default; }; @@ -91,8 +91,11 @@ class OutputStream { void add(std::size_t bytes) const { raw_->info->bytes_in_stream += bytes; } bool prepare(const StreamInfo::Format& new_format) { - if (new_format == raw_->info->format) { + if (std::holds_alternative(raw_->info->format)) { raw_->info->format = new_format; + raw_->info->bytes_in_stream = 0; + } + if (new_format == raw_->info->format) { return true; } if (raw_->is_incomplete) { diff --git a/src/audio/pipeline.cpp b/src/audio/pipeline.cpp index 8af8f215..bab2f3ff 100644 --- a/src/audio/pipeline.cpp +++ b/src/audio/pipeline.cpp @@ -4,7 +4,10 @@ namespace audio { -Pipeline::Pipeline(IAudioElement* output) : root_(output), subtrees_() {} +Pipeline::Pipeline(IAudioElement* output) : root_(output), subtrees_() { + assert(output != nullptr); +} + Pipeline::~Pipeline() {} auto Pipeline::AddInput(IAudioElement* input) -> Pipeline* { diff --git a/src/drivers/dac.cpp b/src/drivers/dac.cpp index 1f3ba557..60679678 100644 --- a/src/drivers/dac.cpp +++ b/src/drivers/dac.cpp @@ -13,6 +13,7 @@ #include "esp_log.h" #include "freertos/portmacro.h" #include "freertos/projdefs.h" +#include "hal/gpio_types.h" #include "hal/i2c_types.h" #include "gpio_expander.hpp" @@ -50,21 +51,23 @@ auto AudioDac::create(GpioExpander* expander) i2s_std_config_t i2s_config = { .clk_cfg = dac->clock_config_, .slot_cfg = dac->slot_config_, - .gpio_cfg = - {// TODO: investigate running in three wire mode for less noise - .mclk = GPIO_NUM_0, - .bclk = GPIO_NUM_26, - .ws = GPIO_NUM_27, - .dout = GPIO_NUM_5, - .din = I2S_GPIO_UNUSED, - .invert_flags = - { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }}, + .gpio_cfg = {.mclk = GPIO_NUM_0, + //.mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_26, + .ws = GPIO_NUM_27, + .dout = GPIO_NUM_5, + .din = I2S_GPIO_UNUSED, + .invert_flags = + { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }}, }; + // gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); + // gpio_set_level(GPIO_NUM_0, 0); + if (esp_err_t err = i2s_channel_init_std_mode(i2s_handle, &i2s_config) != ESP_OK) { ESP_LOGE(kTag, "failed to initialise i2s channel %x", err); @@ -81,20 +84,29 @@ auto AudioDac::create(GpioExpander* expander) // The DAC should be booted but in power down mode, but it might not be if we // didn't shut down cleanly. Reset it to ensure it is in a consistent state. - dac->WriteRegister(Register::POWER_MODE, 0b10001); dac->WriteRegister(Register::POWER_MODE, 1 << 4); dac->WriteRegister(Register::RESET, 0b10001); + // Use BCK for the internal PLL. + // dac->WriteRegister(Register::PLL_CLOCK_SOURCE, 1 << 4); + + // dac->WriteRegister(Register::PLL_ENABLE, 0); + dac->WriteRegister(Register::INTERPOLATION, 1 << 4); + + dac->Reconfigure(BPS_16, SAMPLE_RATE_44_1); + dac->WriteRegister(Register::POWER_MODE, 0); + // Now configure the DAC for standard auto-clock SCK mode. - dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5); + // dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5); // Enable auto clocking, and do your best to carry on despite errors. // dac->WriteRegister(Register::CLOCK_ERRORS, 0b1111101); - i2s_channel_enable(dac->i2s_handle_); + // i2s_channel_enable(dac->i2s_handle_); - dac->WaitForPowerState( - [](bool booted, PowerState state) { return state == STANDBY; }); + dac->WaitForPowerState([](bool booted, PowerState state) { + return state == RUN || state == STANDBY; + }); return dac; } @@ -102,6 +114,7 @@ auto AudioDac::create(GpioExpander* expander) AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle) : gpio_(gpio), i2s_handle_(i2s_handle), + i2s_active_(false), 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)) { @@ -163,9 +176,10 @@ bool AudioDac::WaitForPowerState( } auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void { - // Disable the current output, if it isn't already stopped. - WriteRegister(Register::POWER_MODE, 1 << 4); - i2s_channel_disable(i2s_handle_); + WriteRegister(Register::RESYNC_REQUEST, 1); + if (i2s_active_) { + i2s_channel_disable(i2s_handle_); + } // I2S reconfiguration. @@ -181,15 +195,21 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void { ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_)); // DAC reconfiguration. + if (rate == SAMPLE_RATE_44_1) { + WriteRegister(Register::DE_EMPHASIS, 1 << 4); + } else { + WriteRegister(Register::DE_EMPHASIS, 0); + } // TODO: base on BPS - WriteRegister(Register::I2S_FORMAT, 0); + WriteRegister(Register::I2S_FORMAT, 0b00); // Configuration is all done, so we can now bring the DAC and I2S stream back // up. I2S first, since otherwise the DAC will see that there's no clocks and // shut itself down. + WriteRegister(Register::RESYNC_REQUEST, 0); ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_)); - WriteRegister(Register::POWER_MODE, 0); + i2s_active_ = true; } auto AudioDac::WriteData(const cpp::span& data) -> void { diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp index 4a1b2a5b..6849d92c 100644 --- a/src/drivers/include/dac.hpp +++ b/src/drivers/include/dac.hpp @@ -83,6 +83,7 @@ class AudioDac { private: GpioExpander* gpio_; i2s_chan_handle_t i2s_handle_; + bool i2s_active_; i2s_std_clk_config_t clock_config_; i2s_std_slot_config_t slot_config_; @@ -97,9 +98,13 @@ class AudioDac { PAGE_SELECT = 0, RESET = 1, POWER_MODE = 2, + PLL_ENABLE = 4, DE_EMPHASIS = 7, + PLL_CLOCK_SOURCE = 13, DAC_CLOCK_SOURCE = 14, + RESYNC_REQUEST = 19, CLOCK_ERRORS = 37, + INTERPOLATION = 34, I2S_FORMAT = 40, DIGITAL_VOLUME_L = 61, DIGITAL_VOLUME_R = 62, diff --git a/src/main/main.cpp b/src/main/main.cpp index 98a571b6..77a1e14b 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -41,7 +41,7 @@ static const char* TAG = "MAIN"; void IRAM_ATTR tick_hook(void) { - lv_tick_inc(1); + // lv_tick_inc(1); } static const size_t kLvglStackSize = 8 * 1024; diff --git a/src/memory/include/himem.hpp b/src/memory/include/himem.hpp index f71e912f..517ebfdf 100644 --- a/src/memory/include/himem.hpp +++ b/src/memory/include/himem.hpp @@ -57,21 +57,26 @@ class MappableRegion { } auto Get() -> cpp::span { - if (bytes_ != nullptr) { + if (bytes_ == nullptr) { return {}; } return {bytes_, size}; } auto Map(const HimemAlloc& alloc) -> cpp::span { - if (bytes_ != nullptr) { - ESP_ERROR_CHECK(esp_himem_unmap(range_handle, bytes_, size)); - } + assert(bytes_ == nullptr); ESP_ERROR_CHECK(esp_himem_map(alloc.handle, range_handle, 0, 0, size, 0, reinterpret_cast(&bytes_))); return Get(); } + auto Unmap() -> void { + if (bytes_ != nullptr) { + ESP_ERROR_CHECK(esp_himem_unmap(range_handle, bytes_, size)); + bytes_ = nullptr; + } + } + // Not copyable or movable. MappableRegion(const MappableRegion&) = delete; MappableRegion& operator=(const MappableRegion&) = delete;