EPS-NOW Audio Streaming

I’m working on low-latency audio streaming with ESP32-WROOM.

Setup: Platformio with ESP-IDF platform.

Platformio VS Code extension v3.3.1` Espressif 32 platform inside Platformio was version 6.4.0.

At this point I’m trying to transfer hard-coded array which contains sine-wave samples (aligned so the array contains one full period of the sine wave), and it’s 240 bytes long.

The audio specs that I’m using right now are 32kHz sampling rate, 16 bits. I have tried just feeding these samples to the I2S on one ESP32, and it work fine, no skips in the audio, no errors reported by the I2S queue.

When I try transmitting these samples over ESP-NOW, I’m getting occasional skips in the audio. After some debugging, I’m pretty sure that it’s ESP-NOW that’s the bottleneck.

I was lucky to capture this event with a scope, and it looks like this: Yellow signal is a pin that’s toggled on every received packet. Cyan signal is the resulting audio. Usually the incoming packets are ~ 3.875ms apart. In this occasion only 3 packets are received in ~25ms time interval.

Here are some code snippets:
Audio init:

#define ESPNOW_MSG_LEN                      240
#define ESPNOW_MSG_LEN_INT16                (ESPNOW_MSG_LEN/sizeof(int16_t))

void IRAM_ATTR i2s_evt_task(void *param)
{
    printf("Starting I2S writer task");
    while (true)
    {
        // wait for some data to be requested
        i2s_event_t evt;
        if (xQueueReceive(x_i2s_evt_queue, &evt, portMAX_DELAY) == pdPASS)
        {
            if(evt.type == I2S_EVENT_RX_DONE)
            {
                printf("I2S_EVENT_RX_DONE\n");
            }
            else if(evt.type == I2S_EVENT_DMA_ERROR)
            {
                printf("I2S_EVENT_DMA_ERROR\n");
            }
            else if (evt.type == I2S_EVENT_TX_DONE)
            {
                // device_toggle_testing_pin();
                // printf("I2S_EVENT_TX_DONE\n");
            }
            else if(evt.type == I2S_EVENT_TX_Q_OVF)
            {
                printf("I2S_EVENT_TX_Q_OVF\n");
            }
            else if(evt.type == I2S_EVENT_RX_Q_OVF)
            {
                printf("I2S_EVENT_RX_Q_OVF\n");
            }
        }
    }
}

void esp_audio_init(void)
{
    esp_err_t err;

    i2s_config_t i2s_cfg = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
        .sample_rate = EXAMPLE_SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_MSB),
        .intr_alloc_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM,
        .dma_buf_count = 2,
        .dma_buf_len = ESPNOW_MSG_LEN_INT16*3,
        .use_apll = true,
        .tx_desc_auto_clear = true,
    };

    err = i2s_driver_install(AUDIO_OUT_I2S, &i2s_cfg, 40, &x_i2s_evt_queue);
    if(err != ESP_OK)
    {
        printf("i2s_driver_install error");
    }

    i2s_pin_config_t i2s_pin_cfg = {
        .mck_io_num = GPIO_NUM_0, // 
        .bck_io_num = GPIO_NUM_5, // BCLK
        .ws_io_num = GPIO_NUM_25, // LRC
        .data_out_num = GPIO_NUM_26, // DIN
        .data_in_num = -1
    };

    err = i2s_set_pin(AUDIO_OUT_I2S, &i2s_pin_cfg);
    if(err != ESP_OK)
    {
        printf("i2s_set_pin error");
    }

    err = i2s_zero_dma_buffer(AUDIO_OUT_I2S);
    if(err != ESP_OK)
    {
        printf("i2s_zero_dma_buffer error");
    }

}

ESP-NOW init:

void IRAM_ATTR i2s_feed_task(void *param)
{
    size_t      bytes_written;
    esp_err_t   err;

    while(true)
    {
        xQueueReceive(m_esp_now_rec_queue, &p_most_recent_packet, portMAX_DELAY);

        err = i2s_write(AUDIO_OUT_I2S, (uint8_t *)(p_most_recent_packet), ESPNOW_MSG_LEN, &bytes_written, portMAX_DELAY);
        if(err != ESP_OK)
        {
            printf("i2s_feed_task: i2s_write failed. Err code %d\n", err);
        }
    }
}

static void example_espnow_recv_cb(const esp_now_recv_info_t * esp_now_info, const uint8_t *data, int data_len)
{
    BaseType_t x_higher_prio_task_woken = pdFALSE;
    xQueueSendFromISR(m_esp_now_rec_queue, (void*)&data, &x_higher_prio_task_woken);
    if(x_higher_prio_task_woken)
    {
        /* Actual macro used here is port specific. */
        portYIELD_FROM_ISR();
    }
    device_toggle_testing_pin();
}

static esp_err_t service_init_espnow(void)
{
    esp_err_t ret;

    ret = esp_now_init();
    ERR_CHECK(ret);
    ret = esp_now_register_send_cb(example_espnow_send_cb);
    ERR_CHECK(ret);
    ret = esp_now_register_recv_cb(example_espnow_recv_cb);
    ERR_CHECK(ret);

    esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
    memset(peer, 0, sizeof(esp_now_peer_info_t));
    peer->channel = CONFIG_ESPNOW_CHANNEL;
    peer->ifidx = ESPNOW_WIFI_IF;
    peer->encrypt = false;
    memcpy(peer->peer_addr, esp_now_peer, ESP_NOW_ETH_ALEN);
    ret = esp_now_add_peer(peer); 
    ERR_CHECK(ret);
    free(peer);
    printf("Remote Peer(the sender) Added!\r\n");

    return ESP_OK;
}

This is the sdkconfig.defauls (I’m not using menuconfig to modify any of the configs additionally)

# sdkconfig.defaults
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=64
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=20
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=64
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_DMA_RX_BUF_NUM=20
CONFIG_DMA_TX_BUF_NUM=20
CONFIG_IP_LOST_TIMER_INTERVAL=120

# I2S
CONFIG_I2S_SUPPRESS_DEPRECATE_WARN=y

Also, this is the platformio.ini file:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200

When the audio skips I2S_EVENT_TX_Q_OVF event is generated.

Does anyone have any tips on how to fix this? Please let me know if you need any additional info.

Can’t offer any insight into this but ESP-NOW packet loss characteristics sounds like something Espressif employes can advise you better on than PlatformIO forum members. Try https://github.com/espressif/esp-now/issues or https://www.esp32.com/.