ESP32-S3-CAM豆包语音识别文字后控制小车三——SD卡本地音频识别转文字本地音频文件已经识别成功了接下来就是增加mic拾音转文字功能。1、上硬件GPIO口都和之前帖子保持一致ESP32-S3-CAM连接 INMP441 麦克风实现音频回环给kimi说要改一下逻辑了提示词如下现在修改一下代码逻辑当终端输入 r 文件名 命令 的时候就从SD卡读对应文件去识别当终端输入audio的时候就增加从mic读取 音频到豆包后台识别分片大小也按照之前代码设置mic的gpio口设置如下WS GPIO 1SCK GPIO 2SD GPIO 42后来又给它说输入audio命令后只从mic采集3秒音频就结束了不要一直采集然后继续等终端输入新的命令最终代码如下/* * ESP32-S3 豆包ASR语音识别 - 交互版 * 支持: 1) SD卡文件识别 2) 麦克风实时识别 * * 硬件: GOOUUU ESP32-S3-CAM v1.3 * SD卡: CLK39, CMD38, D040 * 麦克风(I2S): WSGPIO1, SCKGPIO2, SDGPIO42 */ #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].asJsonArray(); 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] 收到最终响应标记(sequence0)); return; // 立即返回不再处理后续逻辑 } // 文件模式继续发送下一片 if (!mic_mode audio_file) { sendNextChunk(); } // 麦克风模式继续采集自动在loop中处理 } else { const char* msg doc[message] | Unknown; Serial.printf([ERR] %d: %s\n, code, msg); } } }测试结果如下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] 收到最终响应标记(sequence0) 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] 收到最终响应标记(sequence0) ASR识别完成! ---------------------------------------- 最终识别结果: 小车前进 [WS] ❌ 断开 [MIC] I2S已停止 [等待命令] [WS] ✅ 已连接 [ASR] 发送配置... [ASR] ✅ 配置已发送 [MIC] 启动麦克风识别... [MIC] 初始化I2S麦克风... [MIC] ✅ I2S麦克风初始化成功 [MIC] 请开始说话3秒自动停止... [ASR] 连接服务器... .[WS] ✅ 已连接 [ASR] 发送配置... [ASR] ✅ 配置已发送 [识别] 小 [识别] 小 [识别] 小 [识别] 小车 [识别] 小车 [识别] 小车 [识别] 小车 [识别] 小车 [识别] 小车 [识别] 小车后 [识别] 小车后 [识别] 小车后 [识别] 小车后腿 [识别] 小车后腿 [识别] 小车后腿 [识别] 小车后腿 [识别] 小车后腿 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [MIC] 3秒录音时间到停止采集 [MIC] 发送结束标记... [MIC] 结束标记已发送等待识别结果... [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [识别] 小车后退 [ASR] 收到最终响应标记(sequence0) ASR识别完成! ---------------------------------------- 最终识别结果: 小车后退 [WS] ❌ 断开 [MIC] I2S已停止 [等待命令] [WS] ✅ 已连接 [ASR] 发送配置... [ASR] ✅ 配置已发送下一步连接L298N电机控制小车前进和后退。