AiXiaoZhi 项目结构与关键逻辑分析
生成方式:基于源码阅读整理
平台:ESP32 系列 (ESP32-S3 / ESP32-C3 / ESP32-P4)
框架:ESP-IDF 5.4
当前目标板:HiwonderExploit_S3
顶层版本:PROJECT_VER = 1.8.9
一、项目概述
小智 AI 语音助手,基于 ESP-IDF 5.4 的 ESP32 系列语音对话设备固件。
核心链路:本地唤醒词 → Opus 音频上行 → 云端 ASR/LLM/TTS → 音频下行播放。
支持:
- 本地唤醒词检测(AFE/ESP/Custom 三种实现)
- 音频前端处理(降噪 NS、回音消除 AEC、语音活动检测 VAD)
- 双协议传输:MQTT+UDP(默认,AES-CTR 加密音频)、WebSocket
- 云端 ASR + LLM + TTS 完整对话流程
- LVGL 显示系统(LCD/OLED/ESP-Log,多表情)
- OTA 远程升级 + 设备激活
- MCP 设备控制协议(JSON-RPC 2.0)
- 90+ 种开发板适配、多语言支持
二、目录结构(顶层)
AiXiaoZhi/
├── main/ # 全部源码(业务核心)
│ ├── main.cc # 入口(app_main)
│ ├── application.cc/h # 应用核心(单例 + 事件循环 + 状态机)
│ ├── ota.cc/h # OTA 升级 + 服务器配置获取
│ ├── mcp_server.cc/h # MCP 设备控制协议
│ ├── settings.cc/h # NVS 键值存储封装
│ ├── system_info.cc/h # 系统信息(MAC/堆内存等)
│ ├── device_state*.h # 设备状态机与事件
│ ├── audio/ # 音频子系统(多任务流水线)
│ │ ├── audio_service.cc/h # 音频服务(3 个 FreeRTOS 任务)
│ │ ├── audio_codec.h # 编解码抽象
│ │ ├── codecs/ # ES8311/8388/8389/Box 等实现
│ │ ├── processors/ # AFE 降噪/AEC/VAD 处理器
│ │ └── wake_words/ # 唤醒词(AFE/ESP/Custom 三种)
│ ├── protocols/ # MQTT+UDP(默认)与 WebSocket(策略模式)
│ ├── display/ # Display 基类 + LCD(LVGL)/OLED/ESP-Log
│ ├── led/ # 单色 LED / 圆形灯带 / GPIO 状态灯
│ ├── boards/ # 90+ 种开发板 + common 公共基类
│ └── assets/ # 多语言 UI 文本 + OGG 音效
├── partitions/ # 16MB 分区表(v0/v1)
├── docs/ # 协议文档
├── sdkconfig(.defaults) # 构建配置
└── CMakeLists.txt # 顶层构建(PROJECT_VER=1.8.9)
三、分层架构
| 层 | 主要内容 |
|---|---|
| 应用层 | Application(状态机 / 调度 / 业务逻辑) |
| 服务层 | AudioService、Protocol(MQTT/WebSocket)、McpServer、Ota |
| 抽象层 | Board(板级单例)、AudioCodec、Display、Led 等虚基类 |
| 硬件层 | ESP-IDF 驱动 + boards/<厂商>/ 具体板实现 |
四、关键逻辑
1. 入口与启动流程(main/application.cc:329 Start())
app_main 只做事件循环 / NVS 初始化,随后启动 Application 单例。Start() 顺序:
- 初始化显示;初始化并启动
AudioService(3 个任务);注册回调事件 board.StartNetwork()(WiFi 连接,失败则进入 WiFi 配置 AP 模式,见wifi_board.cc:71)CheckNewVersion(ota)------ 阻塞直到完成版本检查:失败指数退避重试(最多 10 次);有新版本直接 OTA 升级;否则处理激活码 / 激活挑战(application.cc:70)- 依据 OTA 服务器返回配置选择协议:
HasMqttConfig()→ MQTT,HasWebsocketConfig()→ WebSocket,否则默认 MQTT protocol_->Start()→ 进入kDeviceStateIdle
2. 状态机(main/application.cc:641 SetDeviceState)
Unknown → Starting → WifiConfiguring → Idle
Idle ←→ Listening ←→ Connecting ←(唤醒词/按键) Speaking
↘ Upgrading / Activating / AudioTesting / FatalError
- 状态切换统一驱动:显示状态栏、表情、LED 变化(
led->OnStateChanged()) - 启停唤醒词检测 / 语音处理 :
- Idle:开启唤醒词、关闭语音处理
- Listening:打开 AFE 语音处理、关闭唤醒词,发送
start_listening - Speaking:视 AEC 模式而定(Realtime 模式保持监听,可被二次唤醒打断)
3. 事件驱动主循环(main/application.cc:547 MainEventLoop)
FreeRTOS EventGroup 驱动,事件位定义(application.h:20):
MAIN_EVENT_SCHEDULE (1<<0) 任务调度
MAIN_EVENT_SEND_AUDIO (1<<1) 发送音频
MAIN_EVENT_WAKE_WORD_DETECTED (1<<2) 唤醒词检测到
MAIN_EVENT_VAD_CHANGE (1<<3) VAD 状态变化
MAIN_EVENT_ERROR (1<<4) 错误事件
MAIN_EVENT_CHECK_NEW_VERSION_DONE (1<<5)
Schedule()为线程安全的异步任务入队机制 :回调统一进main_tasks_队列,由主循环串行执行(application.cc:536)。协议 / 音频回调均通过它安全访问 UI 和连接状态。- 主循环优先级提到 3,避免被后台任务抢占。
4. 音频流水线(main/audio/audio_service.cc)------ 3 个任务
上行: MIC →[AudioInputTask]→ Resampler→16k → AFE处理器(NS/AEC/VAD) → Encode队列
→[OpusCodecTask → Opus编码(60ms/16k)] → Send队列 →[主循环 SendAudio]→ Server
下行: Server → Decode队列 →[OpusCodecTask 解码] → Playback队列
→[AudioOutputTask]→ Resampler→codec采样率 → Speaker
AudioInputTask(核 1,优先级 8,仅启用 AFE 时):按事件位分支喂给「音频测试 / 唤醒词 / AFE 处理器」AudioOutputTask(优先级 3):PCM 播出,记录时间戳供服务器端 AECOpusCodecTask(优先级 2):双向编解码,维护 4 个有界队列 + 条件变量- 关键参数(audio_service.h):
OPUS_FRAME_DURATION_MS=60、上行 16kHz、MAX_DECODE/SEND_PACKETS_IN_QUEUE=40 - 服务器端 AEC:播出的时间戳与上传数据对齐(
PushTaskToEncodeQueue,audio_service.cc:408) - 节能:输入 / 输出静默 15s 自动关闭 codec(
CheckAndUpdateAudioPowerState)
5. 协议层(策略模式,main/protocols/protocol.h)
抽象基类 Protocol 提供统一接口:
Start / OpenAudioChannel / CloseAudioChannel / SendAudio / SendWakeWordDetected / SendStartListening / SendAbortSpeaking / SendMcpMessage
握手流程:连接后互换 Hello 消息(type:"hello",含 transport、audio_params=16000Hz/1ch/60ms)。
- MQTT+UDP(默认) :控制消息走 MQTT;服务器 Hello 下发
udp.server/port/key/nonce,音频用 AES-CTR 加密后走 UDP(mqtt_protocol.cc:135) - WebSocket :单通道。
BinaryProtocol2(带时间戳,用于服务器 AEC)与BinaryProtocol3两种帧头;音频二进制帧 + JSON 文本帧复用同一连接
下行 JSON 分发(application.cc:405):
tts(start/stop/sentence_start)、stt、llm(emotion)、mcp、system(reboot)、alert、custom(需 CONFIG_RECEIVE_CUSTOM_MESSAGE)
6. Board 抽象(main/boards/common/board.h)
- 单例工厂 + 多态:
Board::GetInstance()通过create_board()生成当前编译目标的板子(DECLARE_BOARD宏) - 板子选型在
main/CMakeLists.txt由CONFIG_BOARD_TYPE_*决定(当前HiwonderExploit_S3),并把boards/<type>/*.cc+boards/common/*.cc加入编译 - 基类提供:音频 codec、显示、网络、电池、温湿度 / 光照 / IMU / 超声波 / 点阵屏 / 串口舵机等可选能力(默认返回空实现 / false)
7. OTA(main/ota.cc)与设备激活
- 启动即请求
CONFIG_OTA_URL(默认 https://api.tenclass.net/xiaozhi/ota/) - 服务器返回:MQTT/WebSocket 配置 + 新版本信息 + 激活信息(如有)
- 有新版本 →
esp_ota下载 → 升级 → 重启;失败则恢复音频服务继续运行 - 无新版但需激活 → 显示 / 播报激活码,等待用户激活(
ShowActivationCode逐个数字 TTS 播报) PrintOtaConfig():启动时打印完整 OTA 配置(最近新增功能)
8. MCP 设备控制(main/mcp_server.h,单例)
- 内嵌 JSON-RPC 2.0 风格 MCP Server
McpTool+Property(支持 bool/int/string,int 范围校验)+PropertyList生成 JSON Schema- 支持
tools/list(带 cursor 分页)、tools/call - 工具
Call()在独立线程执行,避免阻塞音频链路 AddCommonTools()注册通用能力;协议收到的type=mcp消息交给ParseMessage
五、数据流小结
- 音频上行:唤醒词触发 → 开音频通道 → 编码唤醒词并上传 → 进入 Listening → 语音处理(NS/AEC/VAD)→ Opus 编码 → 发送队列 → 协议上传
- 音频下行:会话(Speaking)状态下接收服务器音频 → 解码队列 → Opus 解码 → 重采样 → 播放队列 → 扬声器
- 控制命令:服务器 JSON → 协议回调 →
Schedule入队 → 主循环处理(切状态 / 显示 / 播放音效 / 设备控制)
六、构建与配置关键点
- 顶层
CMakeLists.txt:set(PROJECT_VER "1.8.9"),project(AiXiaoZhi_Hiwonder) sdkconfig.defaults:16MB 自定义分区表(partitions/v1/16m.csv)、LVGL 9.2.2 精简配置、MBEDTLS 动态缓冲等main/Kconfig.projbuild:OTA_URL、LANGUAGE_*(当前 zh-CN)、BOARD_TYPE_*(当前 HiwonderExploit_S3)、USE_AFE_WAKE_WORD=y、USE_AUDIO_PROCESSOR=y、USE_DEVICE_AEC/SERVER_AEC(互斥)- 编译:
idf.py set-target esp32s3 && idf.py build && idf.py flash && idf.py monitor
七、注意事项 / 观察
- 项目自带
readme.md既是使用说明也是架构文档,与源码基本一致 Initialize()中CONFIG_USE_DEVICE_AEC与CONFIG_USE_SERVER_AEC同时开启会编译报错(#error,application.cc:40)- 唤醒词方案三选一:AFE / ESP-SR / Custom(由 Kconfig 决定,audio_service.cc 中按编译开关创建对应实例)
- 主任务承担网络等待与版本检查,音频由独立高优先级任务保证实时性