[i2s_audio] Fix mono sample swap and block 8-bit mono on ESP32 (#14516)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Jonathan Swoboda
2026-03-08 12:10:43 -10:00
committed by GitHub
co-authored by Claude Opus 4.6 J. Nick Koston
parent 50b3f9d25c
commit d5dc4a39cb
5 changed files with 33 additions and 21 deletions
@@ -46,6 +46,12 @@ def _validate_esp32_variant(config):
if config[CONF_ADC_TYPE] == "external":
if config[CONF_PDM] and variant not in PDM_VARIANTS:
raise cv.Invalid(f"{variant} does not support PDM")
if (
variant == esp32.VARIANT_ESP32
and config.get(CONF_BITS_PER_SAMPLE) == 8
and config.get(CONF_CHANNEL) in (CONF_LEFT, CONF_RIGHT)
):
raise cv.Invalid("8-bit mono mode is not supported on ESP32")
return config
if config[CONF_ADC_TYPE] == "internal":
if variant not in INTERNAL_ADC_VARIANTS:
@@ -281,7 +281,7 @@ bool I2SAudioMicrophone::start_driver_() {
}
/* Before reading data, start the RX channel first */
i2s_channel_enable(this->rx_handle_);
err = i2s_channel_enable(this->rx_handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Enabling failed: %s", esp_err_to_name(err));
return false;
@@ -454,13 +454,14 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
}
this->status_clear_warning();
#if defined(USE_ESP32_VARIANT_ESP32) and not defined(USE_I2S_LEGACY)
// For ESP32 8/16 bit standard mono mode samples need to be switched.
if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ <= 16 && !this->pdm_) {
size_t samples_read = bytes_read / sizeof(int16_t);
for (int i = 0; i < samples_read; i += 2) {
int16_t tmp = buf[i];
buf[i] = buf[i + 1];
buf[i + 1] = tmp;
// For ESP32 16-bit standard mono mode, adjacent samples need to be swapped.
if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_16BIT && !this->pdm_) {
int16_t *samples = reinterpret_cast<int16_t *>(buf);
size_t sample_count = bytes_read / sizeof(int16_t);
for (size_t i = 0; i + 1 < sample_count; i += 2) {
int16_t tmp = samples[i];
samples[i] = samples[i + 1];
samples[i + 1] = tmp;
}
}
#endif
@@ -100,11 +100,16 @@ def _set_stream_limits(config):
def _validate_esp32_variant(config):
if config[CONF_DAC_TYPE] != "internal":
return config
variant = esp32.get_esp32_variant()
if variant not in INTERNAL_DAC_VARIANTS:
raise cv.Invalid(f"{variant} does not have an internal DAC")
if config[CONF_DAC_TYPE] == "internal":
if variant not in INTERNAL_DAC_VARIANTS:
raise cv.Invalid(f"{variant} does not have an internal DAC")
elif (
variant == esp32.VARIANT_ESP32
and config.get(CONF_BITS_PER_SAMPLE) == 8
and config.get(CONF_CHANNEL) in (CONF_MONO, CONF_LEFT, CONF_RIGHT)
):
raise cv.Invalid("8-bit mono mode is not supported on ESP32")
return config
@@ -372,15 +372,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
}
#ifdef USE_ESP32_VARIANT_ESP32
// For ESP32 8/16 bit mono mode samples need to be switched.
// For ESP32 16-bit mono mode, adjacent samples need to be swapped.
if (this_speaker->current_stream_info_.get_channels() == 1 &&
this_speaker->current_stream_info_.get_bits_per_sample() <= 16) {
size_t len = bytes_read / sizeof(int16_t);
int16_t *tmp_buf = (int16_t *) new_data;
for (size_t i = 0; i < len; i += 2) {
int16_t tmp = tmp_buf[i];
tmp_buf[i] = tmp_buf[i + 1];
tmp_buf[i + 1] = tmp;
this_speaker->current_stream_info_.get_bits_per_sample() == 16) {
int16_t *samples = reinterpret_cast<int16_t *>(new_data);
size_t sample_count = bytes_read / sizeof(int16_t);
for (size_t i = 0; i + 1 < sample_count; i += 2) {
int16_t tmp = samples[i];
samples[i] = samples[i + 1];
samples[i + 1] = tmp;
}
}
#endif
@@ -504,7 +504,7 @@ void AudioPipeline::decode_task(void *params) {
if (!started_playback && has_stream_info) {
// Verify enough data is available before starting playback
std::shared_ptr<RingBuffer> temp_ring_buffer = this_pipeline->raw_file_ring_buffer_.lock();
if (temp_ring_buffer->available() >= initial_bytes_to_buffer) {
if (temp_ring_buffer != nullptr && temp_ring_buffer->available() >= initial_bytes_to_buffer) {
started_playback = true;
}
}