En este artículo te voy a enseñar a reproducir un archivo de audio estático en formato WAV usando el DAC Max98357a y la ESP32-WROOM con cámara a través de I2S.
El código es muy parecido al de cómo reproducir audio desde flash de ESP32-CAM solo que ahora en lugar de la ESP32-CAM será con la ESP32-S3-WROOM que tiene cámara integrada.
Vamos a usar el amplificador Max98357a con una frecuencia de muestreo de 8khz que es la mínima para capturar la voz humana.
#define PIN_I2S_AMPLIFICADOR I2S_NUM_0
#define SAMPLE_RATE 8000
Tengo mi i2s así:
void inicializar_i2s_amplificador()
{
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = (i2s_bits_per_sample_t)BITS_PER_SAMPLE,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = true,
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK_AMPLIFICADOR,
.ws_io_num = I2S_LRC_AMPLIFICADOR,
.data_out_num = I2S_DOUT_AMPLIFICADOR,
.data_in_num = I2S_PIN_NO_CHANGE};
esp_err_t err = i2s_driver_install(PIN_I2S_AMPLIFICADOR, &i2s_config, 0, NULL);
if (err != ESP_OK)
{
Serial.printf("Error al instalar I2S amp: %d\n", err);
}
err = i2s_set_pin(PIN_I2S_AMPLIFICADOR, &pin_config);
if (err != ESP_OK)
{
Serial.printf("Error al configurar pines amp: %d\n", err);
}
Serial.println("Driver I2S instalado y configurado amp");
}
Como quiero reproducir grabaciones lo más fácil es enviármelas por Telegram, descargarlas como OGG y luego convertirlas a wav usando el sample rate de 8000 como he configurado en i2s de la esp32.
ffmpeg -i voz.ogg -ar 8000 -c:a pcm_s16le voz_wav.wav
Luego lo quiero poner en la flash así que uso la herramienta https://notisrac.github.io/FileToCArray/ para convertir. Selecciono el archivo, dejo todo en PROGMEM y copio el resultado con Copy to clipboard, eso me va a dar algo como:
// array size is 55334
static const byte voz_wav[] PROGMEM = {
0x52, 0x49, 0x46, 0x46, 0x1e, 0xd8, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20,
0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x80, 0x3e, 0x00, 0x00,
0x02, 0x00, 0x10, 0x00, 0x4c, 0x49, 0x53, 0x54, 0x1a, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x46, 0x4f,
/**Aquí más bytes */
};
Me gusta que la herramienta ya me da el tamaño del array, ese lo vamos a usar más adelante. Como yo no necesito el encabezado wav borro los primeros 44 bytes, que es lo mismo que borrar las 2 primeras filas y dejar solo los últimos 4 bytes de la tercera (porque cada fila mide 16 bytes)
// array size is 55334
static const byte voz_wav[] PROGMEM = {
//0x52, 0x49, 0x46, 0x46, 0x1e, 0xd8, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20,
//0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x80, 0x3e, 0x00, 0x00,
/* 0x02, 0x00, 0x10, 0x00, 0x4c, 0x49, 0x53, 0x54, 0x1a, 0x00, 0x00, 0x00, */0x49, 0x4e, 0x46, 0x4f,
/**Aquí más bytes */
};
Declaro el tamaño del array ya sin el header así:
const size_t my_wav_data_len = 55334-44;
Fíjate que el 55334 es lo que me dio la herramienta donde dice array size is 55334 y el 44 es el tamaño del encabezado wav que borré.
Y luego al momento de reproducir, hago lo siguiente:
void reproducirSonidoEstatico()
{
size_t remaining_bytes = my_wav_data_len;
size_t bytes_written;
esp_err_t err = i2s_write(
PIN_I2S_AMPLIFICADOR,
voz_wav,
my_wav_data_len,
&bytes_written,
portMAX_DELAY);
if (err != ESP_OK)
{
Serial.printf("Error al escribir I2S: %d\n", err);
}
else if (bytes_written != remaining_bytes)
{
Serial.printf("Advertencia: No se escribieron todos los bytes. Escritos: %u / Total: %u\n", bytes_written, remaining_bytes);
}
else
{
Serial.println("Reproducción de WAV completada.");
}
// delay(1000);
}
Obviamente tú puedes avanzar en el puntero del array para omitir los 44 bytes iniciales, aquí te muestro una forma para hacer pruebas rápidas.
Evitar comentar los 44 bytes
Aquí tengo otro fragmento de código que toma en cuenta el tamaño del encabezado WAV para omitirlo.
Primero definimos el tamaño que sería de 44:
#define LONGITUD_HEADER_WAV 44
Ya no comentamos el array ni restamos manualmente. Dejamos el audio así:
#include "Arduino.h"
#define LONGITUD_HEADER_WAV 44
// array size is 23762
static const byte sonido_reproducir_foto_enviada_exitosamente[] PROGMEM = {
0x52, 0x49, 0x46, 0x46, 0xca, 0x5c, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20,
/*Aquí más bytes*/
};
// array size is 23762
const size_t longitudSonidoFotoEnviadaExitosamente = 23762;
Y luego:
void reproducirSonidoEstatico()
{
size_t bytes_faltantes = longitudSonidoFotoEnviadaExitosamente - LONGITUD_HEADER_WAV;
size_t bytes_reproducidos;
esp_err_t err = i2s_write(
PIN_I2S_AMPLIFICADOR,
/*Aquí avanzamos 44 bytes para omitir el header WAV*/
sonido_reproducir_foto_enviada_exitosamente + LONGITUD_HEADER_WAV,
longitudSonidoFotoEnviadaExitosamente - LONGITUD_HEADER_WAV,
&bytes_reproducidos,
portMAX_DELAY);
if (err != ESP_OK)
{
Serial.printf("Error al escribir I2S: %d\n", err);
}
else if (bytes_reproducidos != bytes_faltantes)
{
Serial.printf("Advertencia: No se escribieron todos los bytes. Escritos: %u / Total: %u\n", bytes_reproducidos, bytes_faltantes);
}
else
{
Serial.println("Reproducción de WAV completada.");
}
}
Con eso ya no es necesario hacer cosas manualmente.