En este tutorial se estudiará los formatos audio digital mediante la implementación práctica de un sistema de grabación y transmisión de audio en tiempo real. El objetivo principal es utilizar el microcontrolador ESP32 junto con el protocolo I2S y el micrófono digital MEMS INMP441 para capturar muestras de audio y enviarlas a un servidor remoto a través de una conexión WiFi.
Este proyecto ofrece a los estudiantes explorar la intersección entre la teoría y la aplicación práctica. Desde la configuración del protocolo I2S hasta la transmisión de datos a través de una red WiFi, tendrán la oportunidad de profundizar en cada etapa del proceso y comprender cómo se integran estos componentes para crear un sistema funcional de grabación y transmisión de audio.
Se presentan los principios fundamentales del audio digital y la comunicación inalámbrica, este proyecto también ofrece la oportunidad de desarrollar habilidades prácticas en el diseño y la implementación de sistemas embebidos. A lo largo del proceso, los estudiantes podrán enfrentarse a desafíos reales, resolver problemas técnicos y adquirir experiencia en la creación de soluciones innovadoras en el campo del audio digital y la comunicación de datos.
En el despliegue de la aplicacion se puede observar que el software embebido contiene el software embebido asi como el micrófono INMP441 que captura las muestras de audio y las formatea como un archivo con formato de audio wav
(16KHz, 16 bits, monofónico) y lo envía como un flujo por medio de la red hacia un servicio de audio que almacena el archivo.
Debe disponer de los siguientes elementos:
Además de la configuración del software en el lado del ESP32, es necesario establecer un servicio del lado del servidor que pueda recibir y procesar los archivos de audio enviados desde el dispositivo. Aquí se detallan los pasos necesarios para configurar este servicio utilizando Express.js, un marco de aplicación web muy popular para Node.js:
mkdir servicio-audio
cd servicio-audio
npm init
Ahora llene los datos que se solicitan asi:This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See npm help init for definitive documentation on these fields
and exactly what they do.
Use npm install <pkg> afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
description: Servicio de recepcion de archivos de audio wav
entry point: (index.js)
test command:
git repository:
keywords:
author: Alvaro Salazar
license: (ISC) MIT
About to write to C:\Users\lashe\temporal\package.json:
{
"name": "servicio-audio",
"version": "1.0.0",
"description": "Servicio de recepcion de archivos de audio wav",
"main": "index.js",
"scripts": {
t": "echo \"Error: no test specified\" && exit 1"
},
"author": "Alvaro Salazar",
"license": "MIT"
}
Is this OK? (yes)
``npm install express
app.js
) y configurar un servidor Express básico en él. Puedes usar el bloc de notas o aplicacion similar:notepad app.js
Se pregunta si desea crear el archivo, de clic en Si
.app.js
, debes importar Express y crear una instancia de la aplicación Express. Luego, puedes definir las rutas y los manejadores de solicitudes necesarios para manejar las solicitudes entrantes.const express = require('express');
const app = express();
const fs = require('fs');
const port = 8888;
app.use(express.raw({type: 'audio/wav', limit: '50mb'}));
POST
para manejar las solicitudes de carga de archivos de audio desde el ESP32 al servidor (/uploadAudio
). Después de procesar el archivo de audio, el servidor debe enviar una respuesta al ESP32 para indicar que la solicitud se procesó correctamente. app.post('/uploadAudio', (req, res) => {
const audioData = req.body;
console.log(`Recibido audio con tamaño: ${audioData.length}`);
fs.writeFileSync('audio_received.wav', audioData);
res.send('Audio recibido con exito');
});
app.listen(port, () => {
console.log(`Servidor escuchando en http://localhost:${port}`);
});
// Middleware de manejo de errores
app.use((err, req, res, next) => {
console.error('Se ha producido un error:', err);
if (err.type === 'entity.too.large')
return res.status(413).send('Archivo demasiado grande');
// Si no se ha manejado el error especificamente, envia una respuesta generica
return res.status(500).send('Ocurrio un error en el servidor');
});
Se debe guardar el archivo para su posterior ejecución.node app.js
`` Si el sistema solicita permisos para redes publicas y privadas seleccione todo y acepte. Debe salir la siguiente salida en pantalla:Servidor escuchando en http://localhost:8888
``archivo.wav
en la carpeta del proyecto:notepad archivo.wav
Coloca cualquier contenido y guarda el archivo.Postman
, Curl
, etc.):Invoke-WebRequest -Uri http://localhost:8888/uploadAudio -Method Post -Headers @{"Content-Type" = "audio/wav"} -InFile "archivo.wav"
Y deberás recibir el archivo en la carpeta, además obtendrás una salida similar a esta:StatusCode : 200
StatusDescription : OK
Content : Audio recibido con exito
RawContent : HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 24
Content-Type: text/html; charset=utf-8
Date: Tue, 16 Apr 2024 02:54:51 GMT
ETag: W/"18-TWR+47Z6W0ligVp9pm1UkjaqyRU...
Forms : {}
Headers : {[Connection, keep-alive], [Keep-Alive, timeout=5], [Content-Length, 24], [Content-Type,
text/html; charset=utf-8]...}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 24
``console.log()
en Node.js y herramientas de desarrollo web del navegador para identificar y solucionar cualquier error.La siguiente es la estructura del programa diseñado para el ESP32 que envia continuamente el flujo de audio capturado y lo envia como un archivo wav a un servicio HTTP:
Ahora el diagrama de flujo de la aplicacion embebida es la siguiente:
/**
* MIT License
Copyright (c) 2024 Alvaro Salazar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "Arduino.h"
#include "driver/i2s.h"
#include "WiFi.h"
// Definición de pines para el micrófono INMP441
#define I2S_SCK_PIN 18 // Pin Serial Clock (SCK)
#define I2S_WS_PIN 15 // Pin Word Select (WS)
#define I2S_SD_PIN 13 // Pin Serial Data (SD)
// Configuración del driver I2S para el micrófono INMP441
#define I2S_PORT I2S_NUM_0 // I2S port number (0)
#define SAMPLE_BUFFER_SIZE 1024 // Tamaño del buffer de muestras (1024)
#define I2S_SAMPLE_RATE (16000) // Frecuencia de muestreo (16 kHz)
#define RECORD_TIME (10) // segundos de grabación
// Credenciales de la red WiFi
const char* ssid = "virus2"; // Reemplaza con el nombre de tu red WiFi
const char* password = "a1b2c3d4"; // Reemplaza con tu contraseña WiFi
// Servidor HTTP (IP y puerto)
const char* serverName = "192.168.137.1";
const int port = 8888;
// Constantes para la cabecera del archivo WAV
const int HEADERSIZE = 44;
// Cliente WiFi y cliente HTTP
WiFiClient cliente;
// Prototipos de funciones
void micTask(void* parameter);
void setWavHeader(uint8_t* header, int wavSize);
// Buffer de muestras de audio (32 bits) y buffer de muestras de audio (8 bits)
int32_t i2s_read_buffer[SAMPLE_BUFFER_SIZE];
int8_t i2s_read_buff8[SAMPLE_BUFFER_SIZE*sizeof(int32_t)];
// Estructura de parámetros para la tarea del micrófono
struct MicTaskParameters {
int duracion;
int frecuencia;
int bufferSize;
} micParams;
// Implementacion de las funciones
/**
* @brief Función de configuración del driver I2S para el micrófono INMP441
*/
void i2s_config_setup() {
const i2s_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Modo RX (recepción)
.sample_rate = I2S_SAMPLE_RATE, // Frecuencia de muestreo
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // Ajustado para alineación de 32 bits
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // El INMP441 es mono, usar solo canal izquierdo
.communication_format = I2S_COMM_FORMAT_STAND_I2S, // Ajustado para alineación de 32 bits (MSB)
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1 (1-7) significa prioridad baja
.dma_buf_count = 40, // Número de buffers, 8
.dma_buf_len = 1024, // Tamaño de cada buffer
.use_apll = false, // No usar APLL para la frecuencia de muestreo (no es necesario, pero es más preciso)
};
// Configuración de pines para I2S (I2S0) en el ESP32
const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK_PIN, // Serial Clock (SCK): Señal de reloj de bits
.ws_io_num = I2S_WS_PIN, // Word Select (WS): Señal de reloj de muestreo (LRCLK)
.data_out_num = I2S_PIN_NO_CHANGE, // No se usa en modo RX (recepción): DOUT
.data_in_num = I2S_SD_PIN // DIN // Serial Data (SD): Datos de audio
};
// Instalar el driver de I2S con la configuración anterior y sin buffer de eventos (0) ni callback (NULL)
if(ESP_OK != i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL)) {
Serial.println("i2s_driver_install: error");
}
// Configurar los pines del ESP32 para el I2S (I2S0) con la configuración anterior (pin_config)
if(ESP_OK != i2s_set_pin(I2S_PORT, &pin_config)) {
Serial.println("i2s_set_pin: error");
}
}
/**
* @brief Función de configuración de la board ESP32 y conexión a la red WiFi
* Se ejecuta una sola vez al inicio del programa
*/
void setup() {
Serial.begin(230400); // Iniciar el puerto serie a 230400 baudios
WiFi.begin(ssid, password); // Conectar a la red WiFi con las credenciales proporcionadas
Serial.println("Conectando a WiFi");
while (WiFi.status() != WL_CONNECTED) { // Esperar a que se conecte a la red WiFi
delay(1000); // Esperar un segundo
Serial.println("."); // Imprimir un punto cada segundo
}
Serial.println("Conectado a WiFi"); // Imprimir mensaje de conexión exitosa
Serial.print("IP local: "); // Imprimir mensaje de dirección IP local
Serial.println(WiFi.localIP());
if(!cliente.connect(serverName, port)){ // Conectar al servidor HTTP en el puerto 8888 (HTTP)
Serial.println("Conexion fallida!"); // Imprimir mensaje de error si la conexión falla
delay(10000); // Esperar 10 segundos
return; // Salir de la función setup()
}
Serial.println("Conectado al servidor"); // Imprimir mensaje de conexión exitosa
// Crear una estructura de parámetros para la tarea del micrófono
micParams.duracion = RECORD_TIME; // Duración de la grabación en segundos
micParams.frecuencia = I2S_SAMPLE_RATE; // Frecuencia de muestreo de 16 kHz (16000 muestras por segundo)
micParams.bufferSize = SAMPLE_BUFFER_SIZE; // Tamaño del buffer de muestras (1024 muestras)
Serial.println("Configuracion de la tarea del microfono");
Serial.println("Duracion: " + String(micParams.duracion));
Serial.println("Frecuencia: " + String(micParams.frecuencia));
Serial.println("Tamaño del buffer: " + String(micParams.bufferSize));
// Crea tarea para leer muestras de audio del micrófono y enviarlas al servidor
// - Asigna 10000 bytes de stack, los bytes de stack deben ser suficientes para la tarea y sirven
// para guardar las variables locales de la tarea
// - Los parámetros de la tarea se pasan como un puntero en la estructura micParams
// - La prioridad 1 es la más baja (0-24), y el núcleo asignado a la tarea es el núcleo 1 del ESP32.
// (La prioridad 0 se reserva para el sistema)
xTaskCreatePinnedToCore(micTask, "micTask", 10000, &micParams, 1, NULL, 1);
}
/**
* @brief Función principal del programa (bucle infinito)
*/
void loop() {
// No se hace nada en el loop
}
/**
* @brief Tarea para leer muestras de audio del micrófono I2S y enviarlas a un servidor HTTP
* @param parameter Puntero a los parámetros de la tarea (no se usa)
*/
void micTask(void* parameter) {
struct MicTaskParameters {
int duracion;
int frecuencia;
int bufferSize;
};
// Convertir el puntero a los parámetros de la tarea a un puntero de tipo MicTaskParameters
MicTaskParameters * params = (MicTaskParameters *) parameter;
int duracion = params->duracion; // Duración de la grabación en segundos
int frecuencia = params->frecuencia; // Frecuencia de muestreo de 16 kHz (16000 muestras por segundo)
int bufferSize = params->bufferSize; // Tamaño del buffer de muestras (1024 muestras)
int numBuffers = (int)(params->duracion * params->frecuencia / bufferSize); // Número de buffers a enviar al servidor
int recordBytes = numBuffers * params->bufferSize * sizeof(int16_t);
i2s_config_setup(); // Configurar el I2S para la lectura de muestras de audio
size_t elements_read=0; // Número de elementos leídos del I2S
uint8_t header[HEADERSIZE]; // Buffer para la cabecera del archivo WAV (44 bytes)
setWavHeader(header, recordBytes); // Configurar la cabecera del archivo WAV (44 bytes)
// Enviar la petición HTTP POST al servicio indicando el tamaño del archivo WAV
cliente.println("POST /uploadAudio HTTP/1.1"); // Método POST y ruta del servidor
cliente.println("Content-Type: audio/wav"); // Tipo de contenido de audio WAV
cliente.println("Content-Length: " + String(recordBytes+HEADERSIZE)); // Usa el tamaño del archivo aqui
cliente.println("Host: " + String(serverName)); // Especifica el host al que se está realizando la solicitud.
cliente.println("Connection: keep-alive"); // Establece la conexión para mantenerse viva después de que se complete la solicitud actual.
cliente.println(); // Línea en blanco para indicar el fin de las cabeceras HTTP
cliente.write(header, HEADERSIZE); // Este es el body de la petición HTTP POST (la cabecera del archivo WAV)
//Eliminamos los primeros 8 buffers para que no se envie ruido (se descartan los primeros 512ms de grabacion)
for(int i=0; i<8; i++){
i2s_read(I2S_PORT, &i2s_read_buffer, SAMPLE_BUFFER_SIZE*sizeof(int32_t), &elements_read, portMAX_DELAY);
}
// Leer muestras de audio del micrófono y enviarlas al servidor
for(int j = 0; j < numBuffers; j++ ){
// Leer muestras de audio del micrófono (I2S) y enviarlas al servidor (HTTP)
// un total de SAMPLE_BUFFER_SIZE veces es decir 1024 veces ya que el buffer es de 1024 muestras de 32 bits
// cada una y un portMAX_DELAY para que espere indefinidamente hasta que se llene el buffer de muestras
esp_err_t result = i2s_read(I2S_PORT, &i2s_read_buffer, SAMPLE_BUFFER_SIZE*sizeof(int32_t), &elements_read, portMAX_DELAY);
if (result != ESP_OK) {
Serial.println("Error en la lectura de I2S");
while(1);
} else {
for(int i=0; i<SAMPLE_BUFFER_SIZE; i++){ // Convertimos los datos de 32 bits a 16 bits (2 bytes por muestra)
i2s_read_buff8[2*i] = (int8_t) (i2s_read_buffer[i]>>24&0xFF); // Convertimos los datos de 32 bits (8 bits mas altos) a 8 bits MSB
i2s_read_buff8[2*i+1] = (int8_t) (i2s_read_buffer[i]>>16&0xFF); // Convertimos los datos de 32 bits (8 bits del bit 23 al 16) a 8 bits LSB
}
cliente.write((uint8_t *) i2s_read_buff8, SAMPLE_BUFFER_SIZE*sizeof(int16_t));
}
}
cliente.flush();
Serial.println("Se enviaron " + String(recordBytes+HEADERSIZE) + " bytes al servicio");
unsigned long tiempoInicial = millis(); // Guardar el tiempo actual en milisegundos
while(cliente.available()==0){ // Mientras no haya datos disponibles
if(millis() - tiempoInicial > 5000){ // Reviso si ya pasaron mas de 5 segundos
Serial.println("Expiro el tiempo de espera");
cliente.stop(); // Detengo la conexion al servicio
while(1); // Me quedo en un bucle infinito
}
}
while(cliente.available()){ // Mientras haya datos disponibles
String linea = cliente.readStringUntil('\r'); // Leer una línea de texto hasta el enter
Serial.println(linea); // Imprimir la línea de texto
}
Serial.println("Fin de conexion");
cliente.stop(); // Detener la conexión al servidor
while(1); // Bucle infinito para detener la tarea
}
/**
* @brief Función para configurar el encabezado de un archivo WAV
* @param header Puntero a la cabecera del archivo WAV (un arreglo de 44 bytes)
* @param wavSize Tamaño del archivo WAV en bytes (sin incluir la cabecera)
*/
void setWavHeader(uint8_t* header, int wavSize){
int fileSize = wavSize + HEADERSIZE - 8; // Tamaño del archivo WAV en bytes (sin incluir los primeros 8 bytes)
Serial.println("Tamaño del archivo: " + String(fileSize));
// Formato de la cabecera del archivo WAV (44 bytes)
header[0] = 'R'; // RIFF: Marca el archivo como archivo riff (Resource Interchange File Format)
header[1] = 'I'; // |
header[2] = 'F'; // |
header[3] = 'F'; // |______
header[4] = (uint8_t)(fileSize & 0xFF); // Tamaño de todo el archivo - 8 bytes, en bytes (32-bit integer)
header[5] = (uint8_t)((fileSize >> 8) & 0xFF); // |
header[6] = (uint8_t)((fileSize >> 16) & 0xFF); // |
header[7] = (uint8_t)((fileSize >> 24) & 0xFF); // |______
header[8] = 'W'; // WAVE: Cabecera del tipo de archivo
header[9] = 'A'; // |
header[10] = 'V'; // |
header[11] = 'E'; // |______
header[12] = 'f'; // fmt: Marcador de fragmento de formato. Incluye null al final
header[13] = 'm'; // |
header[14] = 't'; // |
header[15] = ' '; // |______
header[16] = 0x10; // Longitud del formato de los datos (16 bytes)
header[17] = 0x00; // |
header[18] = 0x00; // |
header[19] = 0x00; // |______
header[20] = 0x01; // Tipo de formato 1 (PCM: Pulse Code Modulation)
header[21] = 0x00; // |______
header[22] = 0x01; // Numero de canales: 1
header[23] = 0x00; // |______
header[24] = 0x80; // Frecuencia de muestreo: 00003E80 (16000 Hz)
header[25] = 0x3E; // |
header[26] = 0x00; // |
header[27] = 0x00; // |______
header[28] = 0x00; // (Frecuencia de muestreo * (Numero de Bits por muestra) * Numero de Canales) / 8.
header[29] = 0x7D; // | 0x00007D00 = 32000 = 16000 * 16 * 1 / 8
header[30] = 0x00; // |
header[31] = 0x00; // |______
header[32] = 0x02; // (Numero de bits por muestra * Numero de canales) / 8 = (1: 8 bit mono) (2: 8 bit stereo o 16 bit mono) (4: 16 bit stereo)
header[33] = 0x00; // |______
header[34] = 0x10; // Bits por muestra: 16
header[35] = 0x00; // |______
header[36] = 'd'; // Data: Marca el comienzo de una seccion de datos
header[37] = 'a'; // |
header[38] = 't'; // |
header[39] = 'a'; // |______
header[40] = (uint8_t)(wavSize & 0xFF); // Tamaño de la seccion de datos, en bytes (32-bit integer)
header[41] = (uint8_t)((wavSize >> 8) & 0xFF); // |
header[42] = (uint8_t)((wavSize >> 16) & 0xFF); // |
header[43] = (uint8_t)((wavSize >> 24) & 0xFF); // |______
}
setup()
inicializa el ESP32, se conecta a la red WiFi y configura la tarea del micrófono.loop()
se ejecuta en un bucle infinito, no realiza ninguna acción en este ejemplo.micTask()
lee muestras de audio del micrófono INMP441 utilizando el driver I2S y las envía a un servidor HTTP.setup()
, se inicia la comunicación serial y se intenta conectar el ESP32 a la red WiFi especificada. Se espera hasta que la conexión sea exitosa antes de continuar.micTask
) que se encarga de capturar las muestras de audio del micrófono y enviarlas al servidor HTTP. Se utilizan parámetros como la duración de la grabación, la frecuencia de muestreo y el tamaño del búfer de muestras.micTask
, se leen las muestras de audio del micrófono utilizando el driver I2S y se envían al servidor HTTP a través de una conexión TCP. Se utilizan bucles para leer y enviar los datos de audio en bloques de muestras.El bucle for
recorre un número determinado de veces (numBuffers
), que representa la cantidad de bloques de muestras de audio que se van a leer para que cumplan con la duración requerida y sean enviados al servidor.
// Leer muestras de audio del micrófono y enviarlas al servidor
for(int j = 0; j < numBuffers; j++ ){
// Leer muestras de audio del micrófono (I2S) y enviarlas al servidor (HTTP)
// un total de SAMPLE_BUFFER_SIZE veces es decir 1024 veces ya que el buffer es de 1024 muestras de 32 bits
// cada una y un portMAX_DELAY para que espere indefinidamente hasta que se llene el buffer de muestras
esp_err_t result = i2s_read(I2S_PORT, &i2s_read_buffer, SAMPLE_BUFFER_SIZE*sizeof(int32_t), &elements_read, portMAX_DELAY);
if (result != ESP_OK) {
Serial.println("Error en la lectura de I2S");
while(1);
} else {
for(int i=0; i<SAMPLE_BUFFER_SIZE; i++){ // Convertimos los datos de 32 bits a 16 bits (2 bytes por muestra)
i2s_read_buff8[2*i] = (int8_t) (i2s_read_buffer[i]>>24&0xFF); // Convertimos los datos de 32 bits (8 bits mas altos) a 8 bits MSB
i2s_read_buff8[2*i+1] = (int8_t) (i2s_read_buffer[i]>>16&0xFF); // Convertimos los datos de 32 bits (8 bits del bit 23 al 16) a 8 bits LSB
}
cliente.write((uint8_t *) i2s_read_buff8, SAMPLE_BUFFER_SIZE*sizeof(int16_t));
}
}
i2s_read()
para leer muestras de audio del micrófono a través del protocolo I2S.portMAX_DELAY
), lo que significa que esperará indefinidamente hasta que se capturen suficientes muestras.i2s_read_buffer
.result
.result != ESP_OK
), se imprime un mensaje de error y el programa entra en un bucle infinito (while(1)
). Esto detiene la ejecución del programa y permite identificar y solucionar el problema.for
que recorre todas las muestras en el buffer i2s_read_buffer
.i2s_read_buff8
, que está formateado como un arreglo de 8 bits. Una muestra de 16 bits correspondería a las posiciones 0 y 1, la siguiente en las posiciones 2 y 3, y asi sucesivamente.cliente.write()
para enviar los datos al servidor HTTP.i2s_read_buff8
, que contiene las muestras de audio convertidas a 16 bits, con un tamaño específico que corresponde al tamaño de un bloque de muestras (SAMPLE_BUFFER_SIZE
= 1024) multiplicado por 2 ya que se requieren 2 bytes por muestra (sizeof(int16_t)
) obteniendo asi el tamaño del buffer a enviar.En la imagen se muestra un diagrama de timing con la estructura de cada muestra y como se procesa hasta obtener los bytes a enviar.
El codigo de ambos, del sistema embebido y del servicio de audio lo puede clonar desde el siguiente repositorio Repositorio del proyecto.
git clone https://github.com/alvaro-salazar/esp32-audio-i2s-wav-http
Terminal
y de clic en New Terminal
.npm install
.platformio.ini
y verificar su configuración para la board ESP32.Audacity
.