reSpeaker XVF3800 USB Mic Array com streaming de áudio via WebSocket usando XIAO ESP32S3
Introdução
Este guia descreve como capturar áudio de um array de microfones XVF3800 usando um ESP32-S3 (XIAO ESP32S3), gravar 5 segundos de áudio PCM estéreo de 32 bits a 48 kHz e armazenar os dados na PSRAM para lidar com buffers de áudio grandes. O áudio gravado é então transmitido via Wi-Fi para um servidor WebSocket baseado em Python, onde é recebido e salvo como um arquivo WAV padrão. Esta abordagem é bem adequada para aplicações como captura de voz, registro remoto de áudio e pipelines de processamento de fala, incluindo conversão de fala em texto (STT) e análise de áudio baseada em aprendizado de máquina.

Código Arduino
Você deve habilitar a PSRAM, caso contrário malloc() falhará.
Este sketch usa o firmware XVF3800 para atuar como mestre I2S. Grave o seguinte firmware DFU no XVF3800 antes de fazer o upload:
respeaker_xvf3800_i2s_master_dfu_firmware_v1.0.x_48k_test5.bin
Atualize estes campos antes de fazer o upload:
// WiFi credentials
const char* ssid = "your SSID";
const char* password = "your password";
// WebSocket server (your PC)
const char* wsHost = "192.168.x.xx"; // replace with your PC IP
const uint16_t wsPort = 12345;
Código Arduino completo
#include "WiFi.h"
#include "AudioTools.h"
#include <WebSocketsClient.h>
const char* ssid = "your SSID";
const char* password = "your password";
const char* wsHost = "192.168.x.xx";
const uint16_t wsPort = 12345;
const char* wsPath = "/";
WebSocketsClient webSocket;
AudioInfo info(48000, 2, 32);
I2SStream i2s_in;
I2SConfig i2s_config;
#define BUFFER_SIZE 1024
#define TOTAL_BYTES 1920000 // 5 sec @ 48 kHz, stereo, 32-bit
uint8_t* audio_buffer = nullptr;
bool wsConnected = false;
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
if (type == WStype_CONNECTED) {
Serial.println("WebSocket connected");
wsConnected = true;
} else if (type == WStype_DISCONNECTED) {
Serial.println("WebSocket disconnected");
wsConnected = false;
}
}
void connectWiFi() {
Serial.printf("Connecting to WiFi: %s\n", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("\nConnected!");
Serial.printf("IP Address: %s\n", WiFi.localIP().toString().c_str());
}
void setupI2SInput() {
i2s_config = i2s_in.defaultConfig(RX_MODE);
i2s_config.copyFrom(info);
i2s_config.pin_bck = 8;
i2s_config.pin_ws = 7;
i2s_config.pin_data = 44;
i2s_config.pin_data_rx = 43;
i2s_config.is_master = false;
i2s_in.begin(i2s_config);
Serial.println("I2S input started.");
}
void setup() {
Serial.begin(115200);
while (!Serial);
AudioLogger::instance().begin(Serial, AudioLogger::Info);
connectWiFi();
if (psramFound()) {
audio_buffer = (uint8_t*) ps_malloc(TOTAL_BYTES);
Serial.println("Using PSRAM for audio buffer.");
} else {
audio_buffer = (uint8_t*) malloc(TOTAL_BYTES);
Serial.println("WARNING: no PSRAM found - allocation may fail.");
}
if (!audio_buffer) {
Serial.println("Failed to allocate audio buffer!");
return;
}
setupI2SInput();
delay(500);
// ---- PHASE 1: capture only ----
Serial.println("Capturing 5 seconds of audio...");
size_t total_captured = 0;
while (total_captured < TOTAL_BYTES) {
size_t to_read = min((size_t)BUFFER_SIZE, (size_t)TOTAL_BYTES - total_captured);
size_t bytes_read = i2s_in.readBytes(audio_buffer + total_captured, to_read);
if (bytes_read > 0) {
total_captured += bytes_read;
if (total_captured % 64000 == 0) {
Serial.printf("Captured %d bytes (%.1f sec)\n", total_captured, total_captured / 384000.0);
}
} else {
delay(1);
}
}
Serial.printf("Capture complete: %d bytes\n", total_captured);
// ---- PHASE 2: connect WebSocket and send ----
webSocket.begin(wsHost, wsPort, wsPath);
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(2000);
Serial.println("Waiting for WebSocket connection...");
unsigned long waitStart = millis();
while (!wsConnected && millis() - waitStart < 10000) {
webSocket.loop();
delay(10);
}
if (!wsConnected) {
Serial.println("Failed to connect to WebSocket server!");
return;
}
Serial.printf("Sending %d bytes via WebSocket...\n", total_captured);
const size_t SEND_CHUNK = 4096;
size_t total_sent = 0;
while (total_sent < total_captured) {
size_t chunk = min(SEND_CHUNK, (size_t)(total_captured - total_sent));
webSocket.sendBIN(audio_buffer + total_sent, chunk);
webSocket.loop();
total_sent += chunk;
}
Serial.printf("Finished! Sent %d bytes total\n", total_sent);
webSocket.disconnect();
free(audio_buffer);
}
void loop() {
webSocket.loop();
}
Código do servidor Python (WebSocket)
Você precisa instalar a biblioteca websockets: pip install websockets
import asyncio
import websockets
import wave
WS_HOST = "0.0.0.0"
WS_PORT = 12345
SAMPLE_RATE = 48000
CHANNELS = 2
SAMPLE_WIDTH = 4 # 32-bit = 4 bytes
BYTES_PER_SEC = SAMPLE_RATE * CHANNELS * SAMPLE_WIDTH # 384000
async def handle_connection(websocket):
print(f"Client connected: {websocket.remote_address}")
audio_data = bytearray()
async for message in websocket:
if isinstance(message, (bytes, bytearray)):
audio_data.extend(message)
if len(audio_data) % 64000 < len(message):
print(f"Received {len(audio_data)} bytes ({len(audio_data) / BYTES_PER_SEC:.1f} seconds)")
else:
print(f"Received text: {message}")
print(f"\nClient disconnected. Total received: {len(audio_data)} bytes")
if len(audio_data) > 0:
print("Saving to output.wav...")
with wave.open("output.wav", "wb") as wav_file:
wav_file.setnchannels(CHANNELS)
wav_file.setsampwidth(SAMPLE_WIDTH)
wav_file.setframerate(SAMPLE_RATE)
wav_file.writeframes(bytes(audio_data))
print("Done! Audio saved to output.wav")
else:
print("No data received!")
async def main():
print(f"WebSocket server listening on {WS_HOST}:{WS_PORT}...")
async with websockets.serve(handle_connection, WS_HOST, WS_PORT):
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())
Suporte técnico e discussão sobre o produto
Obrigado por escolher nossos produtos! Estamos aqui para oferecer diferentes tipos de suporte para garantir que sua experiência com nossos produtos seja a mais tranquila possível. Oferecemos vários canais de comunicação para atender a diferentes preferências e necessidades.