Lazy loaded image
📢基于 KickPi K2B + Sherpa-ONNX 的双模式 AI 智能音箱
字数 3417阅读时长 9 分钟
2025-11-26
2026-9-15
😀
项目概述
项目名称: 双模式 AI 智能音箱系统
技术栈: C/C++、Qt、Libevent、MySQL、Sherpa-ONNX、千问模型(qwen_max)
硬件平台: KickPi K2B 嵌入式开发板
项目类型: 嵌入式 AI 语音交互系统
notion image
notion image
notion image
notion image
notion image
notion image

项目架构

系统组成: 该项目是一个集成语音交互、音乐播放控制、远程 APP 管理于一体的智能音频系统,采用三层架构设计。

1. 嵌入式端(KickPi K2B)

  • 本地语音处理: 部署小米 Sherpa-ONNX 框架下的 ASR(语音识别)、TTS(语音合成)、KWS(关键词检测)模型
  • 音频控制: 通过 ALSA 音频驱动和 mPlayer 实现歌曲播放与音量控制
  • 本地控制: 单按钮支持单击、双击、长按操作,实现播放 / 暂停、上一首、下一首功能

2. Qt 客户端应用

  • 用户界面: 基于 Qt 框架开发的跨平台应用
  • 用户管理: 注册、登录功能
  • 设备管理: 设备绑定、状态监控
  • 远程控制: 播放控制、音量调节、歌单显示
  • 状态同步: 实时显示设备在线状态和播放信息

3. Libevent 服务器端

  • 网络通信: 基于 Libevent 框架的高性能网络服务器
  • 歌曲存储: 基于apache实现的Web服务器,用于存储歌曲,供嵌入式端的mplayer在线播放
  • 数据存储: MySQL 数据库,管理用户数据和设备信息,密码SHA256加密、SQL指令均格式化,防止恶意注入
  • 消息转发: 处理 APP 与嵌入式设备之间的指令转发
  • 状态管理: 维护设备在线状态和用户绑定关系

核心功能模块

1. 语音处理模块(sherpa-asr-kws)

功能描述:
  • 实时音频采集与处理
  • 关键词唤醒(如 "小刘同学"、"小米小米")
  • 语音指令识别(如 ”开始播放“、”下一首“、”上一首“、”离线模式“、”换一个音色“等)
  • 在线 / 离线模式切换
技术特点:
  • ALSA 音频驱动
  • 重采样器适配模型输入格式
  • 双模式工作:KWS 唤醒模式 → ASR 识别模式
  • 通过管道(FIFO)与主控制模块通信
  • 根据主控制模块的信号(signal)切换工作模式

2. 语音合成模块(sherpa-tts)

功能描述:
  • 文本转语音合成
  • 多音色切换
  • 语音播放控制
技术特点:
  • 实时音频合成与播放
  • 支持说话人音色切换(sid 参数控制)
  • 说话打断机制(SIGUSR1 信号)
  • float 转 int16_t 音频格式转换

3. AI 对话模块(千问)

功能描述:
  • 智能问答处理
  • 语音交互反馈
技术特点:
  • 自定义sh脚本,千问api发送数据,通过管道读取脚本回复
  • JSON 格式数据交互
  • 回答结果自动转换为语音

4. 核心控制模块(player.c)

功能描述:
  • 模块协调与控制
  • 音乐播放管理
  • 网络通信处理
  • 状态维护与同步
技术特点:
  • select 机制实现多事件监听
  • 链表数据结构管理播放列表
  • 共享内存(shm)和信号量(sem)实现父、子、孙三进程间通信
  • 掉线模式模式自动切换(在线 → 离线)

双模式工作机制

在线模式

  • 网络连接: 通过 Libevent 服务器实现网络通信
  • 功能特点:
    • 在线音乐播放(Apache 服务器)
    • 千问 AI 聊天功能
    • APP 远程控制
    • 实时状态同步
    • MySQL 数据存储

离线模式

  • 触发条件:
    • 网络连接异常时自动切换
    • 用户呼叫关键词,然后通过语音识别到“离线模式”
  • 功能特点:
    • U 盘本地音乐播放
    • 基础语音控制(仅 KWS)
    • 本地按钮操作
    • 关闭网络相关功能

模式切换

  • 语音切换: 通过特定语音指令在两种模式间切换
  • 自动切换: 网络状态变化时自动切换模式
  • 手动切换: 嵌入式端手动切换模式

通信协议设计

内部通信

  • 进程间通信: 管道(FIFO)机制,实现模型、千问AI、主进程三者通信
  • 共享内存: 播放状态和配置信息,父子孙三进程共享
  • 信号机制: SIGUSR1、SIGUSR2 等信号控制,实现状态通知(如切换离线模式时通知asr—kws进程切换到离线模式)

网络通信

  • 数据格式: JSON 格式消息
  • 核心指令:
    • device_report - 设备状态上报
    • get_music - 获取音乐列表
    • app_start_play/app_stop_play - APP 控制指令
    • app_register/app_login/app_bind - 用户管理指令
    • 还有很多,例如回复等。下面是接口定义
    • 接口定义
      1. 嵌入式端 上报信息 当前歌手、当前歌曲、、当前模式、当前状态、当前设备、当前音量 嵌入式端发送给服务器:{ "cmd": "device_report", "cur_singer": "未知歌手", "cur_music": "稻香-周杰伦.mp3", "cur_mode": 0, "state": "play", "deviceid": "0001", "cur_volume": 78 } 服务器返回:....
      1. APP 上报信息 { "cmd": "app_report", "appid":"0001", "deviceid":"0001"} 服务器返回:....
      1. 嵌入式端 向服务器获取播放列表 嵌入式端发送给服务器:{ "cmd": "get_music", "singer: "其他"} 服务器返回:{ "cmd": "reply_music", "music":["其他/1.MP3","其他/2.MP3","其他/3.MP3"]}
      1. APP 向服务器 请求开始播放 APP发送给服务器: { "cmd": "app_start_play"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_start_play", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_start_play"} 音箱返回给服务器: { "cmd": "reply_app_start_play", "result": "success"} { "cmd": "reply_app_start_play", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_start_play", "result": "success"} { "cmd": "reply_app_start_play", "result": "failure"}
      1. APP 向服务器 请求结束播放 APP发送给服务器: { "cmd": "app_stop_play"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_stop_play", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_stop_play"} 音箱返回给服务器: { "cmd": "reply_app_stop_play", "result": "success"} { "cmd": "reply_app_stop_play", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_stop_play", "result": "success"} { "cmd": "reply_app_stop_play", "result": "failure"}
      1. APP 向服务器 请求暂停播放 APP发送给服务器: { "cmd": "app_suspend_play"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_suspend_play", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_suspend_play"} 音箱返回给服务器: { "cmd": "reply_app_suspend_play", "result": "success"} { "cmd": "reply_app_suspend_play", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_suspend_play", "result": "success"} { "cmd": "reply_app_suspend_play", "result": "failure"}
      1. APP 向服务器 请求继续播放 APP发送给服务器: { "cmd": "app_continue_play"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_continue_play", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_continue_play"} 音箱返回给服务器: { "cmd": "reply_app_continue_play", "result": "success"} { "cmd": "reply_app_continue_play", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_continue_play", "result": "success"} { "cmd": "reply_app_continue_play", "result": "failure"}
      1. APP 向服务器 请求下一首歌 APP发送给服务器: { "cmd": "app_play_next_song"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_play_next_song", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_play_next_song"} 音箱返回给服务器: { "cmd": "reply_app_play_next_song", "result": "success"} { "cmd": "reply_app_play_next_song", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_play_next_song", "result": "success"} { "cmd": "reply_app_play_next_song", "result": "failure"}
      1. APP 向服务器 请求上一首歌 APP发送给服务器: { "cmd": "app_play_prev_song"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_play_prev_song", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_play_prev_song"} 音箱返回给服务器: { "cmd": "reply_app_play_prev_song", "result": "success"} { "cmd": "reply_app_play_prev_song", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_play_prev_song", "result": "success"} { "cmd": "reply_app_play_prev_song", "result": "failure"}
      1. APP 向服务器 请求增加音量 APP发送给服务器: { "cmd": "app_add_volume"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_add_volume", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_add_volume"} 音箱返回给服务器: { "cmd": "reply_app_add_volume", "result": "success"} { "cmd": "reply_app_add_volume", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_add_volume", "result": "success"} { "cmd": "reply_app_add_volume", "result": "failure"}
      1. APP 向服务器 请求减小音量 APP发送给服务器: { "cmd": "app_sub_volume"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_sub_volume", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_sub_volume"} 音箱返回给服务器: { "cmd": "reply_app_sub_volume", "result": "success"} { "cmd": "reply_app_sub_volume", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_sub_volume", "result": "success"} { "cmd": "reply_app_sub_volume", "result": "failure"}
      1. APP 向服务器 请求顺序播放模式 APP发送给服务器: { "cmd": "app_order_mode"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_order_mode", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_order_mode"} 音箱返回给服务器: { "cmd": "reply_app_order_mode", "result": "success"} { "cmd": "reply_app_order_mode", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_order_mode", "result": "success"} { "cmd": "reply_app_order_mode", "result": "failure"}
      1. APP 向服务器 请求单曲循环模式 APP发送给服务器: { "cmd": "app_single_mode"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_single_mode", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_single_mode"} 音箱返回给服务器: { "cmd": "reply_app_single_mode", "result": "success"} { "cmd": "reply_app_single_mode", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_single_mode", "result": "success"} { "cmd": "reply_app_single_mode", "result": "failure"}
      1. APP 向服务器 请求随机播放模式 APP发送给服务器: { "cmd": "app_random_mode"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_random_mode", "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_random_mode"} 音箱返回给服务器: { "cmd": "reply_app_random_mode", "result": "success"} { "cmd": "reply_app_random_mode", "result": "failure"} 服务器转发给APP: { "cmd": "reply_app_random_mode", "result": "success"} { "cmd": "reply_app_random_mode", "result": "failure"}
      1. APP 获取 嵌入式端歌曲列表 APP发送给服务器: { "cmd": "app_get_music_list"} 如果音箱不在线, 服务器回复APP:{ "cmd": "reply_app_get_music_list, "result": "offline"} 如果音箱在线, 服务器转发 嵌入式端 { "cmd": "app_get_music_list"} 音箱返回给服务器: { "cmd": "upload_music_list", "music":["1.mp3","2.mp3","3.mp3","4.mp3","5.mp3"]} 服务器转发给APP: { "cmd": "upload_music_list", "music":["1.mp3","2.mp3","3.mp3","4.mp3","5.mp3"]}
      1. 当嵌入式端 更新了歌曲列表(当前列表播放完毕,切换歌手 也会触发upload_music_list
      1. APP 注册 APP发送给服务器 { "cmd": "app_register","appid":"0001","password":"1111111"} 服务器成功 回复APP: { "cmd": "reply_app_register","result":"success"} 服务器失败 回复APP: { "cmd": "reply_app_register","result":"xxxx"} result["result"] = "idshort"; //账号太短 result["result"] = "passhort"; //密码太短 result["result"] = "idexist"; //用户已存在 result["result"] = "failure"; // 服务器发生错误(查询失败)
      1. APP 请求绑定设备 APP发送给服务器: { "cmd": "app_bind", "appid":"0001", "deviceid":"0001"} 服务器成功 回复APP: { "cmd": "reply_app_bind","result":"success", "deviceid":"0001"} 服务器失败 回复APP: { "cmd": "reply_app_bind","result":"xxxx"} result["result"] = "isbind"; //设备ID已经被绑定了 result["result"] = "devidshort"; //设备ID输入的太短了 result["result"] = "failure"; // 服务器发生错误(查询失败)
      1. APP 登录 APP发送给服务器 { "cmd": "app_login","appid":"0001","password":"1111111"} 服务器成功 回复APP: { "cmd": "reply_app_login","result":"success", "deviceid":"0001"}; 服务器失败 回复APP: { "cmd": "reply_app_login","result":"xxxx"} result["result"] = "idshort"; //账号太短 result["result"] = "passhort"; //密码太短 result["result"] = "idnotexist"; //用户不存在 result["result"] = "notbind"; //未绑定设备(登陆成功) result["result"] = "passerr"; //密码错误 result["result"] = "failure"; // 服务器发生错误(查询失败)

技术亮点

1. 双模式设计

  • 支持在线和离线两种工作模式
  • 网络异常时自动降级,保证基本功能可用

2. 本地 AI 能力

  • 全本地化语音处理,响应速度快
  • 无需网络即可实现基础语音控制

3. 跨平台控制

  • Qt 客户端支持多平台部署
  • 实时设备状态监控和远程控制

4. 鲁棒性设计

  • 完善的错误处理机制
  • 关键资源异常时自动重置
  • 详细的日志记录系统

功能实现细节

音乐播放控制

  • 播放功能: 支持 MP3 格式(其他格式理论都支持,不过没写。因为mplayer支持绝大多数的音乐格式)
  • 控制指令: 播放 / 暂停、上一首、下一首、音量调节
  • 播放模式: 顺序播放、单曲循环、列表循环

语音交互流程

  1. 关键词唤醒: 用户说出唤醒词(如 "小米小米"、"小刘同学")
  1. 指令识别: 系统进入语音识别模式
  1. 指令执行: 解析并执行用户指令
  1. 语音反馈: 通过 TTS 返回执行结果
  1. 执行内容: 判断是否为指令,如果不是则交由AI处理并回复

APP 功能特点

  • 设备管理: 支持设备绑定
  • 状态显示: 实时显示当前播放歌曲、音量、模式、歌单等
  • 推送通知: 设备上线 / 下线通知

项目成果

技术成果

  • 完整的嵌入式 AI 语音交互解决方案
  • 稳定可靠的双模式工作机制
  • 高效的进程间通信和网络通信架构

功能成果

  • 实现了从语音唤醒到指令执行的完整链路
  • 支持在线和离线两种使用场景
  • 提供了便捷的远程控制方式

性能指标

  • 语音识别准确率: 90% (特定关键词)
  • 响应延迟: 本地指令 < 100ms,在线指令 < 100ms
 
💡
有关此项目其他内容,欢迎在下方留言(我会收到邮件通知),留言我会以邮件回复。或联系我的QQ。
 
上一篇
Matter 学习笔记
下一篇
MySQL数据库常用指令

评论
Loading...