From 07e7c342a4e2a00a732e7be3988f013faa6e1528 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Fri, 18 Nov 2022 17:39:56 +0100 Subject: [PATCH] esp32: tolerate empty i2s write in audio driver --- .../components/btstack/btstack_audio_esp32.c | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/port/esp32/components/btstack/btstack_audio_esp32.c b/port/esp32/components/btstack/btstack_audio_esp32.c index f5cf77c4a..605e11bd3 100644 --- a/port/esp32/components/btstack/btstack_audio_esp32.c +++ b/port/esp32/components/btstack/btstack_audio_esp32.c @@ -54,10 +54,13 @@ #include "driver/gpio.h" #include "driver/i2s.h" +#include "esp_log.h" #include #include +#define LOG_TAG "AUDIO" + #ifdef CONFIG_ESP_LYRAT_V4_3_BOARD #include "driver/i2c.h" #include "es8388.h" @@ -300,34 +303,47 @@ static void btstack_audio_esp32_deinit(void){ } // SINK Implementation - +// - with esp-idf v4.4.3, we occasionally get a TX_DONE but fail to write data without waiting for free buffers +// it's unclera why this happens. this code assumes that the TX_DONE event has been received prematurely and +// just retries the i2s_write the next time without blocking +static uint8_t btstack_audio_esp32_sink_buffer[MAX_DMA_BUFFER_SAMPLES * BYTES_PER_SAMPLE_STEREO]; +static bool btstack_audio_esp32_sink_buffer_ready; static void btstack_audio_esp32_sink_fill_buffer(void){ - size_t bytes_written; - uint8_t buffer[MAX_DMA_BUFFER_SAMPLES * BYTES_PER_SAMPLE_STEREO]; btstack_assert(btstack_audio_esp32_samples_per_dma_buffer <= MAX_DMA_BUFFER_SAMPLES); + // fetch new data + size_t bytes_written; uint16_t data_len = btstack_audio_esp32_samples_per_dma_buffer * BYTES_PER_SAMPLE_STEREO; - if (btstack_audio_esp32_sink_state == BTSTACK_AUDIO_ESP32_STREAMING){ - (*btstack_audio_esp32_sink_playback_callback)((int16_t *) buffer, btstack_audio_esp32_samples_per_dma_buffer); - // duplicate samples for mono - if (btstack_audio_esp32_sink_num_channels == 1){ - int16_t i; - int16_t * buffer16 = (int16_t *) buffer; - for (i=btstack_audio_esp32_samples_per_dma_buffer-1;i >= 0; i--){ - buffer16[2*i ] = buffer16[i]; - buffer16[2*i+1] = buffer16[i]; + if (btstack_audio_esp32_sink_buffer_ready == false){ + if (btstack_audio_esp32_sink_state == BTSTACK_AUDIO_ESP32_STREAMING){ + (*btstack_audio_esp32_sink_playback_callback)((int16_t *) btstack_audio_esp32_sink_buffer, btstack_audio_esp32_samples_per_dma_buffer); + // duplicate samples for mono + if (btstack_audio_esp32_sink_num_channels == 1){ + int16_t i; + int16_t * buffer16 = (int16_t *) btstack_audio_esp32_sink_buffer; + for (i=btstack_audio_esp32_samples_per_dma_buffer-1;i >= 0; i--){ + buffer16[2*i ] = buffer16[i]; + buffer16[2*i+1] = buffer16[i]; + } } + btstack_audio_esp32_sink_buffer_ready = true; + } else { + memset(btstack_audio_esp32_sink_buffer, 0, data_len); } - } else { - memset(buffer, 0, data_len); } - i2s_write(BTSTACK_AUDIO_I2S_NUM, buffer, data_len, &bytes_written, 0); - if (bytes_written != data_len){ - log_error("i2s_write: only %u of %u!!!", (int) bytes_written, (int) sizeof(buffer)); + i2s_write(BTSTACK_AUDIO_I2S_NUM, btstack_audio_esp32_sink_buffer, data_len, &bytes_written, 0); + + // check if all data has been written. tolerate writing zero bytes (->retry), but assert on partial write + if (bytes_written == data_len){ + btstack_audio_esp32_sink_buffer_ready = false; + } else if (bytes_written == 0){ + ESP_LOGW(LOG_TAG, "i2s_write: couldn't write after I2S_EVENT_TX_DONE\n"); + } else { + ESP_LOGE(LOG_TAG, "i2s_write: only %u of %u!!!\n", (int) bytes_written, data_len); + btstack_assert(false); } - btstack_assert(bytes_written == data_len); } static int btstack_audio_esp32_sink_init( @@ -344,7 +360,7 @@ static int btstack_audio_esp32_sink_init( btstack_audio_esp32_sink_samplerate = samplerate; btstack_audio_esp32_sink_state = BTSTACK_AUDIO_ESP32_INITIALIZED; - + // init i2s and codec btstack_audio_esp32_init(); return 0; @@ -369,7 +385,8 @@ static void btstack_audio_esp32_sink_start_stream(void){ // state btstack_audio_esp32_sink_state = BTSTACK_AUDIO_ESP32_STREAMING; - + btstack_audio_esp32_sink_buffer_ready = false; + // note: conceptually, it would make sense to pre-fill all I2S buffers and then feed new ones when they are // marked as complete. However, it looks like we get additoinal events and then assert below, // so we just don't pre-fill them here