Some doubts about memory handling to caputre I2S audio and transmitting over mqtt

KaosESP32
Posts: 12
Joined: Tue Oct 31, 2023 9:17 am

Some doubts about memory handling to caputre I2S audio and transmitting over mqtt

Postby KaosESP32 » Tue Oct 31, 2023 10:20 am

Hi,
I'm developing a prototype to capture audio (expectation is 16bit 44.1 kHz, nice to have: stereo) along some other sensor data and transmitting to an Mqtt broker.

I'm trying to do that using an esp32 (4Mb flash) and/or an esp32 Wrover (8Mb flash)
IDE: Vscode with Espressif extension latest version

I've declared a tiny callback to be aware of samples lost and initialized i2s microphone with the following code:
**************************
static bool i2s_rx_queue_overflow_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
{
// handle RX queue overflow event ...
i2s_ovw_counter++;
esp_rom_printf("Overflow! %lu éssima ocurrència d'Overflow del I2S DMA\n", i2s_ovw_counter);
return false;
}

void initINMP441(i2s_port_t i2s_port){
const char *TAG = "Cesc initINMP441";

i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port, I2S_ROLE_MASTER);
chan_cfg.dma_frame_num = 1024;
chan_cfg.dma_desc_num = 10;
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
//.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_8BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = I2S_SCK,
.ws = I2S_WS,
.dout = I2S_GPIO_UNUSED,
.din = I2S_SD,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};

i2s_event_callbacks_t cbs = {
.on_recv = NULL,
.on_recv_q_ovf = i2s_rx_queue_overflow_callback,
.on_sent = NULL,
.on_send_q_ovf = NULL,
};

ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_register_event_callback(rx_handle, &cbs, NULL));
//ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));

ESP_LOGI(TAG, "Sample Rate:%lu, Bit Sample:%lu, Num. Canals(Stereo/Mono):%lu",
(u_long)std_cfg.clk_cfg.sample_rate_hz,
(u_long)std_cfg.slot_cfg.data_bit_width,
(u_long)std_cfg.slot_cfg.slot_mode);

}
***************************

Please, notice the dma_frame_num and the dam_desc_num inits

Find below the Mqtt client initialization:
*******************************************
void mqtt_app_start(void)
{
const char *TAG = "Cesc mqtt_app_start";

const esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = CONFIG_BROKER_URL,
.broker.verification.certificate = (const char *)server_cert_pem_start,
.credentials = {
.client_id = "Beta01Board",
.authentication = {
.certificate = (const char *)client_cert_pem_start,
.key = (const char *)client_key_pem_start,
},
},
.buffer.size = 1024,
.buffer.out_size = 1024*30,
};

ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
mqttClient = esp_mqtt_client_init(&mqtt_cfg);
/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
ESP_ERROR_CHECK(esp_mqtt_client_register_event(mqttClient, (esp_mqtt_event_id_t)ESP_EVENT_ANY_ID, mqtt_event_handler, NULL));
ESP_ERROR_CHECK(esp_mqtt_client_start(mqttClient));
}
*******************************************

Please, notice the buffer.size and the buffer.out.size inits

And here is the Wifi initialization
*******************************************
void wifi_init_sta(void)
{
static const char *TAG = "Cesc wifi_init_sta";

//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

ESP_ERROR_CHECK(esp_netif_init());
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS, // No necessari per WAP2-Enterprise
.scan_method = WIFI_FAST_SCAN
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_password((uint8_t *)EXAMPLE_ESP_WIFI_PASS, strlen(EXAMPLE_ESP_WIFI_PASS)) );
ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_ttls_phase2_method(ESP_EAP_TTLS_PHASE2_MSCHAPV2) );
/****************************************************/
#ifdef EXAMPLE_ESP_WIFI_USER
ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_username((uint8_t *)EXAMPLE_ESP_WIFI_USER, strlen(EXAMPLE_ESP_WIFI_USER)) );
ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_enable() );
#endif
/****************************************************/
ESP_ERROR_CHECK(esp_wifi_start() );

ESP_LOGD(TAG, "wifi_init_sta finished.");

/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);

/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
//ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
ESP_LOGI(TAG, "connected to ap SSID:%s", EXAMPLE_ESP_WIFI_SSID);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Failed to connect to SSID:%s, password:%s",EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
ESP_LOGI(TAG, "Failed to connect to SSID:%s", EXAMPLE_ESP_WIFI_SSID);
esp_restart();
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
********************************************
Finally, here is the snippet code in charge of reading DMA/i2S data and move it to MQTT output

*************************************

DRAM_ATTR int16_t sBuffer[1024*10];


void i2s_INMP441_read_task(void *args){

const char *TAG = "Cesc tasca i2s INMP441";
ESP_LOGI(TAG, "Inici tasca Micro INMP441");
vTaskDelay(2000 / portTICK_PERIOD_MS);

char topic[100];
strlcpy(topic, "xxxxxxxxx/rawSound", sizeof(topic));
//strlcpy(topic, mainTopic, sizeof(topic));
//strlcat(topic, "rawSound", sizeof(topic));

size_t i2s_bytes_llegits = 0;



vTaskDelay(2000 / portTICK_PERIOD_MS);

ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));

while(true){
ESP_ERROR_CHECK(i2s_channel_read(rx_handle, &sBuffer, sizeof(sBuffer), &i2s_bytes_llegits, portMAX_DELAY));
// Ací l'algoritme de compressió
ESP_ERROR_CHECK(esp_mqtt_client_publish(mqttClient, topic, (const char *)sBuffer, i2s_bytes_llegits, 0, 0));
//ESP_ERROR_CHECK(esp_mqtt_client_enqueue(mqttClient, topic, (const char *)sBuffer, i2s_bytes_llegits, 0, 0,true));
}
/* Have to stop the channel before deleting it */
i2s_channel_disable(rx_handle);
/* If the handle is not needed any more, delete it to release the channel resources */
i2s_del_channel(rx_handle);
vTaskDelete(NULL);
}


*****************************************


My goal is to have quality audio with no packet loss, I'm not concerned about audio delay.
My problem is that I have data loss (i2s samples) due lack of outogin throughput (mqtt/wifi)

Here some doubts/quesitons

Is there a way to use the additional 4Mb of the Wrover to handle the buffers involved? (DMA/I2S, App Buffer, MQTT & Wifi)
I'm aware of the himem api for storage beyond 4Mb but do not know if it can be used for DMA/ISS, MQTT & WIfi. Can you confirm/deny?
I assume that the App audio buffer (that read from DMA/i2S and passed to MQTT) can be placed on high 4mb bank using himem api, can you confirm and provide snippet code?
I'm happy to provide config files and/or output files if needed.

Thanks in advance

KaosESP32
Posts: 12
Joined: Tue Oct 31, 2023 9:17 am

Re: Some doubts about memory handling to caputre I2S audio and transmitting over mqtt

Postby KaosESP32 » Mon Nov 13, 2023 10:28 am

I've found a workaround to use high memory for stream buffer
I use the "xQueueCreateWithCaps" with MALLOC_CAP_SPIRAM option to use high memory as a buffer.

testQueue = xQueueCreateWithCaps((UBaseType_t) 2000, (UBaseType_t) 1024*2, (UBaseType_t) MALLOC_CAP_SPIRAM);

Who is online

Users browsing this forum: No registered users and 19 guests