diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp index 021be901..0ef1005a 100644 --- a/src/drivers/i2s_dac.cpp +++ b/src/drivers/i2s_dac.cpp @@ -6,9 +6,6 @@ #include "i2s_dac.hpp" -#include -#include - #include #include #include @@ -50,7 +47,7 @@ auto I2SDac::create(IGpios& expander) -> std::optional { }; ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL)); - // + // First, instantiate the instance so it can do all of its power on // configuration. std::unique_ptr dac = std::make_unique(expander, i2s_handle); @@ -130,6 +127,8 @@ auto I2SDac::SetPaused(bool paused) -> void { } } +static volatile bool sSwapWords = false; + auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void { std::lock_guard lock(configure_mutex_); @@ -146,9 +145,11 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) switch (ch) { case CHANNELS_MONO: + sSwapWords = true; slot_config_.slot_mode = I2S_SLOT_MODE_MONO; break; case CHANNELS_STEREO: + sSwapWords = false; slot_config_.slot_mode = I2S_SLOT_MODE_STEREO; break; } @@ -211,17 +212,32 @@ extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle, if (event->data == nullptr || event->size == 0) { return false; } - uint8_t** buf = reinterpret_cast(event->data); + assert(event->size % 4 == 0); + + uint8_t* buf = *reinterpret_cast(event->data); auto src = reinterpret_cast(user_ctx); BaseType_t ret = false; size_t bytes_written = - xStreamBufferReceiveFromISR(src, *buf, event->size, &ret); + xStreamBufferReceiveFromISR(src, buf, event->size, &ret); + + // The ESP32's I2S peripheral has a different endianness to its processors. + // ESP-IDF handles this difference for stereo channels, but not for mono + // channels. We therefore sometimes need to swap each pair of words as they're + // written to the DMA buffer. + if (sSwapWords) { + uint16_t* buf_as_words = reinterpret_cast(buf); + for (size_t i = 0; i + 1 < bytes_written / 2; i += 2) { + uint16_t temp = buf_as_words[i]; + buf_as_words[i] = buf_as_words[i + 1]; + buf_as_words[i + 1] = temp; + } + } // If we ran out of data, then make sure we clear out the DMA buffers rather // than continuing to repreat the last few samples. if (bytes_written < event->size) { - std::memset((*buf) + bytes_written, 0, event->size - bytes_written); + std::memset(buf + bytes_written, 0, event->size - bytes_written); } return ret; diff --git a/src/drivers/include/i2s_dac.hpp b/src/drivers/include/i2s_dac.hpp index d66d9762..bd837ca0 100644 --- a/src/drivers/include/i2s_dac.hpp +++ b/src/drivers/include/i2s_dac.hpp @@ -32,7 +32,7 @@ namespace drivers { // means that at 48kHz, we have about 21ms of budget to fill each buffer. // We base this off of the maximum DMA size in order to minimise the amount of // work the CPU has to do to service the DMA callbacks. -constexpr size_t kI2SBufferLengthFrames = 1023; +constexpr size_t kI2SBufferLengthFrames = 1024; /** * Interface for a DAC that receives PCM samples over I2S.