ESP32-S3-CAM:豆包语音识别文字后控制小车(三)------SD卡本地音频识别转文字
本地音频文件已经识别成功了,接下来就是增加mic拾音转文字功能。
1、上硬件,GPIO口都和之前帖子保持一致
ESP32-S3-CAM:连接 INMP441 麦克风实现音频回环

给kimi说要改一下逻辑了,提示词如下:
现在修改一下代码逻辑,当终端输入 r 文件名 命令 的时候,就从SD卡读对应文件去识别,当终端输入audio的时候,就增加从mic读取 音频到豆包后台识别,分片大小也按照之前代码设置,mic的gpio口设置如下
WS : GPIO 1
SCK : GPIO 2
SD: GPIO 42
后来又给它说:
输入audio命令后只从mic采集3秒音频就结束了,不要一直采集,然后继续等终端输入新的命令
最终代码如下:
cpp
/*
* ESP32-S3 豆包ASR语音识别 - 交互版
* 支持: 1) SD卡文件识别 2) 麦克风实时识别
*
* 硬件: GOOUUU ESP32-S3-CAM v1.3
* SD卡: CLK=39, CMD=38, D0=40
* 麦克风(I2S): WS=GPIO1, SCK=GPIO2, SD=GPIO42
*/
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <SD_MMC.h>
#include <ArduinoJson.h>
#include <driver/i2s.h>
// ============== 配置区域 ==============
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// 豆包ASR配置
const char* appid = "你的 APP ID";
const char* token = "你的 Access Token";
const char* cluster = "volcengine_input_common";
const char* ws_host = "openspeech.bytedance.com";
const int ws_port = 443;
const char* ws_path = "/api/v2/asr";
// SD卡引脚
#define SD_CLK 39
#define SD_CMD 38
#define SD_D0 40
// 麦克风I2S引脚
#define I2S_WS 1
#define I2S_SCK 2
#define I2S_SD 42
#define I2S_PORT I2S_NUM_0
// 音频配置
const int audio_rate = 16000;
const int audio_bits = 16;
const int audio_channel = 2;
// 分片大小
const size_t CHUNK_SIZE = 64000; // 64KB大分片
// 协议常量
#define PROTOCOL_VERSION 0x01
#define DEFAULT_HEADER_SIZE 0x01
#define CLIENT_FULL_REQUEST 0x01
#define CLIENT_AUDIO_ONLY_REQUEST 0x02
#define SERVER_FULL_RESPONSE 0x09
#define SERVER_ACK 0x0B
#define SERVER_ERROR_RESPONSE 0x0F
#define NO_SEQUENCE 0x00
#define NEG_SEQUENCE 0x02
#define JSON_SERIALIZATION 0x01
#define NO_COMPRESSION 0x00
// ============== 全局变量 ==============
WebSocketsClient webSocket;
bool ws_connected = false;
bool asr_completed = false;
bool asr_running = false;
bool result_displayed = false; // 防止重复打印结果
String recognition_result = "";
// SD卡文件模式
File audio_file;
size_t audio_total_size = 0;
size_t audio_sent_size = 0;
String target_filename = "";
// 麦克风模式
bool mic_mode = false;
int16_t* mic_buffer = nullptr;
const int MIC_BUFFER_SAMPLES = 1024; // 每次采集1024样本
unsigned long mic_start_time = 0; // 录音开始时间
const unsigned long MIC_RECORD_DURATION = 5000; // 录音时长3秒
// ============== 函数声明 ==============
bool initSD();
void listSDFiles();
void showPrompt();
void processCommand(String cmd);
bool loadAndRecognizeFile(String filename);
bool startMicRecognition();
void initWebSocket();
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length);
void sendFullClientRequest(bool from_mic);
void sendNextChunk();
void sendMicAudio();
void sendLastMicChunk(); // 发送麦克风结束标记
void parseResponse(uint8_t* data, size_t len);
void generateHeader(uint8_t* header, uint8_t msg_type, uint8_t flags);
bool initI2SMic();
void stopI2SMic();
// ============== 协议头生成 ==============
void generateHeader(uint8_t* header, uint8_t msg_type, uint8_t flags) {
header[0] = (PROTOCOL_VERSION << 4) | DEFAULT_HEADER_SIZE;
header[1] = (msg_type << 4) | flags;
header[2] = (JSON_SERIALIZATION << 4) | NO_COMPRESSION;
header[3] = 0x00;
}
// ============== 设置 ==============
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n========================================");
Serial.println("ESP32-S3 豆包ASR语音识别(交互版)");
Serial.println("========================================");
Serial.println("命令:");
Serial.println(" r <文件名> - 识别SD卡中的音频文件");
Serial.println(" audio - 从麦克风实时识别");
Serial.println(" list - 列出SD卡文件");
Serial.println(" help - 显示帮助");
Serial.println("========================================\n");
// 初始化SD卡
if (!initSD()) {
Serial.println("⚠️ SD卡初始化失败,文件模式不可用");
}
// 连接WiFi
Serial.print("[WiFi] 连接 ");
Serial.print(ssid);
Serial.print(" ...");
WiFi.begin(ssid, password);
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 30) {
delay(500);
Serial.print(".");
retry++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println(" ❌ 失败");
while (1) delay(1000);
}
Serial.println(" ✅ 已连接");
Serial.print("[WiFi] IP: ");
Serial.println(WiFi.localIP());
// 初始化麦克风缓冲区
mic_buffer = (int16_t*)malloc(MIC_BUFFER_SAMPLES * sizeof(int16_t));
if (!mic_buffer) {
Serial.println("⚠️ 麦克风缓冲区分配失败");
}
showPrompt();
}
// ============== 主循环 ==============
void loop() {
webSocket.loop();
// 处理串口命令
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd.length() > 0) {
processCommand(cmd);
}
}
// 麦克风模式:采集5秒后自动结束
if (mic_mode && ws_connected && !asr_completed) {
// 检查是否超过5秒
if (millis() - mic_start_time >= MIC_RECORD_DURATION) {
Serial.println("\n[MIC] 3秒录音时间到,停止采集");
// 发送最后一片标记
sendLastMicChunk();
mic_mode = false;
} else {
sendMicAudio();
}
}
// 识别完成处理(只执行一次)
if (asr_completed && !result_displayed) {
result_displayed = true; // 标记已显示结果
Serial.println("\n========================================");
Serial.println("🎉 ASR识别完成!");
Serial.println("----------------------------------------");
Serial.print("📢 最终识别结果: ");
Serial.println(recognition_result);
Serial.println("========================================");
// 断开WebSocket并禁用自动重连
webSocket.setReconnectInterval(0); // 禁用自动重连
webSocket.disconnect();
ws_connected = false;
// 清理
if (audio_file) audio_file.close();
if (mic_buffer) stopI2SMic(); // 麦克风模式清理
asr_running = false;
mic_mode = false;
showPrompt();
}
}
// ============== 显示提示符 ==============
void showPrompt() {
Serial.println("\n[等待命令] > ");
}
// ============== 处理命令 ==============
void processCommand(String cmd) {
cmd.toLowerCase();
if (cmd.startsWith("r ")) {
// 识别文件
String filename = cmd.substring(2);
filename.trim();
if (!filename.startsWith("/")) filename = "/" + filename;
result_displayed = false; // 重置标志
loadAndRecognizeFile(filename);
}
else if (cmd == "audio") {
// 麦克风识别
result_displayed = false; // 重置标志
startMicRecognition();
}
else if (cmd == "list") {
// 列出文件
listSDFiles();
showPrompt();
}
else if (cmd == "help") {
Serial.println("命令:");
Serial.println(" r <文件名> - 识别SD卡音频文件 (如: r audio1.wav)");
Serial.println(" audio - 从麦克风实时识别");
Serial.println(" list - 列出SD卡文件");
Serial.println(" help - 显示帮助");
showPrompt();
}
else {
Serial.println("❌ 未知命令,输入 help 查看帮助");
showPrompt();
}
}
// ============== SD卡初始化 ==============
bool initSD() {
Serial.println("[SD] 初始化SD卡...");
SD_MMC.setPins(SD_CLK, SD_CMD, SD_D0);
if (!SD_MMC.begin("/sdcard", true)) {
Serial.println("[SD] ❌ 失败");
return false;
}
uint8_t type = SD_MMC.cardType();
Serial.print("[SD] 类型: ");
if (type == CARD_SD) Serial.println("SD");
else if (type == CARD_SDHC) Serial.println("SDHC");
else if (type == CARD_MMC) Serial.println("MMC");
else Serial.println("未知");
Serial.printf("[SD] 容量: %llu MB\n", SD_MMC.cardSize() / 1048576);
return true;
}
// ============== 列出SD卡文件 ==============
void listSDFiles() {
File root = SD_MMC.open("/");
if (!root || !root.isDirectory()) {
Serial.println("[SD] ❌ 无法打开目录");
return;
}
Serial.println("\n[SD] 文件列表:");
int count = 0;
File f = root.openNextFile();
while (f) {
if (!f.isDirectory()) {
Serial.printf(" [FILE] %-20s %6d KB\n", f.name(), f.size() / 1024);
count++;
}
f = root.openNextFile();
}
Serial.printf("[SD] 共 %d 个文件\n", count);
}
// ============== 加载并识别文件 ==============
bool loadAndRecognizeFile(String filename) {
if (asr_running) {
Serial.println("❌ 当前有识别任务在运行,请等待完成");
return false;
}
Serial.printf("\n[文件] 准备识别: %s\n", filename.c_str());
if (!SD_MMC.exists(filename)) {
Serial.println("❌ 文件不存在");
showPrompt();
return false;
}
audio_file = SD_MMC.open(filename, FILE_READ);
if (!audio_file) {
Serial.println("❌ 无法打开文件");
showPrompt();
return false;
}
audio_total_size = audio_file.size();
audio_sent_size = 0;
target_filename = filename;
mic_mode = false;
Serial.printf("[文件] 大小: %d bytes (%.1f KB)\n", audio_total_size, audio_total_size / 1024.0);
// 重置状态
asr_completed = false;
asr_running = true;
recognition_result = "";
// 连接WebSocket并开始识别
Serial.println("[ASR] 连接服务器...");
initWebSocket();
return true;
}
// ============== 初始化麦克风 ==============
bool initI2SMic() {
Serial.println("[MIC] 初始化I2S麦克风...");
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = audio_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_SD
};
esp_err_t err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("[MIC] ❌ I2S驱动安装失败: %d\n", err);
return false;
}
err = i2s_set_pin(I2S_PORT, &pin_config);
if (err != ESP_OK) {
Serial.printf("[MIC] ❌ I2S引脚设置失败: %d\n", err);
i2s_driver_uninstall(I2S_PORT);
return false;
}
Serial.println("[MIC] ✅ I2S麦克风初始化成功");
return true;
}
// ============== 停止麦克风 ==============
void stopI2SMic() {
i2s_driver_uninstall(I2S_PORT);
Serial.println("[MIC] I2S已停止");
}
// ============== 启动麦克风识别 ==============
bool startMicRecognition() {
if (asr_running) {
Serial.println("❌ 当前有识别任务在运行,请等待完成");
return false;
}
Serial.println("\n[MIC] 启动麦克风识别...");
if (!initI2SMic()) {
showPrompt();
return false;
}
mic_mode = true;
asr_completed = false;
asr_running = true;
recognition_result = "";
mic_start_time = millis(); // 记录开始时间
Serial.println("[MIC] 请开始说话(3秒自动停止)...");
// 连接WebSocket
Serial.println("[ASR] 连接服务器...");
initWebSocket();
return true;
}
// ============== WebSocket初始化 ==============
void initWebSocket() {
webSocket.beginSSL(ws_host, ws_port, ws_path);
String auth = "Authorization: Bearer; ";
auth += token;
webSocket.setExtraHeaders(auth.c_str());
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(5000); // 启用自动重连(5秒)
}
// ============== WebSocket事件处理 ==============
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println("[WS] ❌ 断开");
ws_connected = false;
break;
case WStype_CONNECTED:
Serial.println("[WS] ✅ 已连接");
ws_connected = true;
sendFullClientRequest(mic_mode);
break;
case WStype_BIN:
parseResponse(payload, length);
break;
case WStype_ERROR:
Serial.println("[WS] ❌ 错误");
break;
default:
break;
}
}
// ============== 发送配置 ==============
void sendFullClientRequest(bool from_mic) {
Serial.println("[ASR] 📤 发送配置...");
JsonDocument doc;
doc["app"]["appid"] = appid;
doc["app"]["token"] = token;
doc["app"]["cluster"] = cluster;
doc["user"]["uid"] = "esp32_asr";
doc["request"]["reqid"] = "esp32-" + String(millis());
doc["request"]["nbest"] = 1;
doc["request"]["workflow"] = "audio_in,resample,partition,vad,fe,decode,itn,nlu_punctuate";
doc["request"]["show_utterances"] = false;
doc["request"]["result_type"] = "full";
doc["request"]["sequence"] = 1;
doc["audio"]["format"] = from_mic ? "raw" : "wav";
doc["audio"]["rate"] = audio_rate;
doc["audio"]["language"] = "zh-CN";
doc["audio"]["bits"] = audio_bits;
doc["audio"]["channel"] = from_mic ? 1 : audio_channel; // 麦克风通常是单声道
doc["audio"]["codec"] = "raw";
String json;
serializeJson(doc, json);
size_t msg_len = 4 + 4 + json.length();
uint8_t* msg = (uint8_t*)malloc(msg_len);
generateHeader(msg, CLIENT_FULL_REQUEST, NO_SEQUENCE);
msg[4] = (json.length() >> 24) & 0xFF;
msg[5] = (json.length() >> 16) & 0xFF;
msg[6] = (json.length() >> 8) & 0xFF;
msg[7] = json.length() & 0xFF;
memcpy(msg + 8, json.c_str(), json.length());
webSocket.sendBIN(msg, msg_len);
free(msg);
Serial.println("[ASR] ✅ 配置已发送");
// 如果是文件模式,立即开始发送第一片音频
if (!from_mic && audio_file) {
delay(100); // 稍等服务器响应
sendNextChunk();
}
}
// ============== 发送文件下一片 ==============
void sendNextChunk() {
if (!audio_file || !asr_running) return;
size_t remaining = audio_total_size - audio_sent_size;
if (remaining == 0) {
// 文件发送完成,发送结束标记
Serial.println("[ASR] 文件读取完成,等待最终结果...");
return;
}
size_t chunk = (remaining > CHUNK_SIZE) ? CHUNK_SIZE : remaining;
bool is_last = (remaining <= CHUNK_SIZE);
// 从文件读取
uint8_t* buffer = (uint8_t*)malloc(chunk);
if (!buffer) {
Serial.println("[ERR] ❌ 内存不足");
return;
}
size_t read = audio_file.read(buffer, chunk);
if (read != chunk) {
Serial.println("[ERR] ❌ 读取失败");
free(buffer);
return;
}
// 构建消息
size_t msg_len = 4 + 4 + chunk;
uint8_t* msg = (uint8_t*)malloc(msg_len);
uint8_t flags = is_last ? NEG_SEQUENCE : NO_SEQUENCE;
generateHeader(msg, CLIENT_AUDIO_ONLY_REQUEST, flags);
msg[4] = (chunk >> 24) & 0xFF;
msg[5] = (chunk >> 16) & 0xFF;
msg[6] = (chunk >> 8) & 0xFF;
msg[7] = chunk & 0xFF;
memcpy(msg + 8, buffer, chunk);
webSocket.sendBIN(msg, msg_len);
free(buffer);
free(msg);
audio_sent_size += chunk;
Serial.printf("[ASR] 📤 已发送 %d/%d KB (%d%%)%s\n",
audio_sent_size / 1024, audio_total_size / 1024,
audio_sent_size * 100 / audio_total_size,
is_last ? " [最后一片]" : "");
}
// ============== 发送最后一片麦克风音频(带结束标记)==============
void sendLastMicChunk() {
if (!mic_buffer) return;
Serial.println("[MIC] 发送结束标记...");
// 发送一个空包或静音包作为结束标记
size_t msg_len = 4 + 4; // header + size = 8 bytes (空payload)
uint8_t* msg = (uint8_t*)malloc(msg_len);
if (!msg) return;
generateHeader(msg, CLIENT_AUDIO_ONLY_REQUEST, NEG_SEQUENCE); // NEG_SEQUENCE表示最后一片
msg[4] = 0;
msg[5] = 0;
msg[6] = 0;
msg[7] = 0; // payload size = 0
webSocket.sendBIN(msg, msg_len);
free(msg);
Serial.println("[MIC] 结束标记已发送,等待识别结果...");
}
// ============== 发送麦克风音频 ==============
void sendMicAudio() {
if (!mic_buffer || !asr_running) return;
// 从I2S读取数据
size_t bytes_read = 0;
esp_err_t err = i2s_read(I2S_PORT, mic_buffer, MIC_BUFFER_SAMPLES * sizeof(int16_t), &bytes_read, portMAX_DELAY);
if (err != ESP_OK || bytes_read == 0) return;
int samples = bytes_read / sizeof(int16_t);
// 构建消息(实时发送,不分片累积)
size_t msg_len = 4 + 4 + bytes_read;
uint8_t* msg = (uint8_t*)malloc(msg_len);
if (!msg) return;
generateHeader(msg, CLIENT_AUDIO_ONLY_REQUEST, NO_SEQUENCE);
msg[4] = (bytes_read >> 24) & 0xFF;
msg[5] = (bytes_read >> 16) & 0xFF;
msg[6] = (bytes_read >> 8) & 0xFF;
msg[7] = bytes_read & 0xFF;
memcpy(msg + 8, mic_buffer, bytes_read);
webSocket.sendBIN(msg, msg_len);
free(msg);
static int mic_packets = 0;
mic_packets++;
if (mic_packets % 50 == 0) {
Serial.print("."); // 每50包打印一个点表示正在采集
}
}
// ============== 解析响应 ==============
void parseResponse(uint8_t* data, size_t len) {
if (len < 8) return;
uint8_t msg_type = data[1] >> 4;
uint8_t serialization = data[2] >> 4;
uint8_t header_size = data[0] & 0x0F;
size_t payload_offset = header_size * 4;
if (len < payload_offset + 4) return;
int32_t payload_size = ((int32_t)data[payload_offset] << 24) |
((int32_t)data[payload_offset + 1] << 16) |
((int32_t)data[payload_offset + 2] << 8) |
((int32_t)data[payload_offset + 3]);
if (payload_size <= 0 || len < payload_offset + 4 + payload_size) return;
uint8_t* payload = data + payload_offset + 4;
if (serialization == JSON_SERIALIZATION) {
JsonDocument doc;
DeserializationError err = deserializeJson(doc, payload, payload_size);
if (err) return;
int code = doc["code"] | -1;
int sequence = doc["sequence"] | 0;
if (code == 1000) {
// 提取文本
JsonArray arr = doc["result"].as<JsonArray>();
if (arr.size() > 0) {
const char* txt = arr[0]["text"];
if (txt && strlen(txt) > 0) {
recognition_result = String(txt);
Serial.printf("\n[识别] %s\n", txt);
}
}
// 检查是否完成
if (sequence < 0) {
asr_completed = true;
Serial.println("[ASR] 收到最终响应标记(sequence<0)");
return; // 立即返回,不再处理后续逻辑
}
// 文件模式:继续发送下一片
if (!mic_mode && audio_file) {
sendNextChunk();
}
// 麦克风模式:继续采集(自动在loop中处理)
} else {
const char* msg = doc["message"] | "Unknown";
Serial.printf("[ERR] %d: %s\n", code, msg);
}
}
}
测试结果如下:
bash
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce2820,len:0x10cc
load:0x403c8700,len:0xc2c
load:0x403cb700,len:0x30c0
entry 0x403c88b8
========================================
ESP32-S3 豆包ASR语音识别(交互版)
========================================
命令:
r <文件名> - 识别SD卡中的音频文件
audio - 从麦克风实时识别
list - 列出SD卡文件
help - 显示帮助
========================================
[SD] 初始化SD卡...
[SD] 类型: SDHC
[SD] 容量: 15200 MB
[WiFi] 连接 rm1234 ....... ✅ 已连接
[WiFi] IP: 192.168.137.241
[等待命令] >
[文件] 准备识别: /hello.wav
[文件] 大小: 288044 bytes (281.3 KB)
[ASR] 连接服务器...
[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
[ASR] 📤 已发送 62/281 KB (22%)
[ASR] 📤 已发送 125/281 KB (44%)
[ASR] 📤 已发送 187/281 KB (66%)
[ASR] 📤 已发送 250/281 KB (88%)
[ASR] 📤 已发送 281/281 KB (100%) [最后一片]
[ASR] 文件读取完成,等待最终结果...
[识别] 你好!
[ASR] 收到最终响应标记(sequence<0)
========================================
🎉 ASR识别完成!
----------------------------------------
📢 最终识别结果: 你好!
========================================
[WS] ❌ 断开
E (16913) i2s(legacy): i2s_driver_uninstall(1586): I2S port 0 has not installed
[MIC] I2S已停止
[等待命令] >
[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
[MIC] 启动麦克风识别...
[MIC] 初始化I2S麦克风...
[MIC] ✅ I2S麦克风初始化成功
[MIC] 请开始说话(3秒自动停止)...
[ASR] 连接服务器...
[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
[识别] 小
[识别] 小
[识别] 小
.
[识别] 小
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车前进
[识别] 小车前进
[识别] 小车前进
[识别] 小车前进
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[MIC] 3秒录音时间到,停止采集
[MIC] 发送结束标记...
[MIC] 结束标记已发送,等待识别结果...
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[识别] 小车前进!
[ASR] 收到最终响应标记(sequence<0)
========================================
🎉 ASR识别完成!
----------------------------------------
📢 最终识别结果: 小车前进!
========================================
[WS] ❌ 断开
[MIC] I2S已停止
[等待命令] >
[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
[MIC] 启动麦克风识别...
[MIC] 初始化I2S麦克风...
[MIC] ✅ I2S麦克风初始化成功
[MIC] 请开始说话(3秒自动停止)...
[ASR] 连接服务器...
.[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
[识别] 小
[识别] 小
[识别] 小
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车
[识别] 小车后
[识别] 小车后
[识别] 小车后
[识别] 小车后腿
[识别] 小车后腿
[识别] 小车后腿
[识别] 小车后腿
[识别] 小车后腿
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[MIC] 3秒录音时间到,停止采集
[MIC] 发送结束标记...
[MIC] 结束标记已发送,等待识别结果...
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[识别] 小车后退!
[ASR] 收到最终响应标记(sequence<0)
========================================
🎉 ASR识别完成!
----------------------------------------
📢 最终识别结果: 小车后退!
========================================
[WS] ❌ 断开
[MIC] I2S已停止
[等待命令] >
[WS] ✅ 已连接
[ASR] 📤 发送配置...
[ASR] ✅ 配置已发送
下一步,连接L298N电机,控制小车前进和后退。