Quectel Pi M2 语音助手阶段性调试:Windows 控制台、音频排查与真机验收

.> 硬件与系统:Quectel Pi M2(RK3576)/ Debian 13(trixie)aarch64 / Windows 10/11

项目目标:在 M2 上搭建语音采集、唤醒、识别、问答和播报链路,并通过独立的 Windows 控制台统一查看状态和调试服务。
当前进度:Windows 控制台及 Agent 软件链路已完成验证;M2 已完成页面显示和历史音频采集验证,完整语音闭环仍待现场验收。

这篇文章记录的不是一个已经完成验收的语音产品,而是一次端云协同语音系统的阶段性工程实践。重点包括:如何拆分语音链路、如何区分演示数据和真机数据、如何解决 Windows 打包问题,以及如何为后续真机验收建立明确标准。

一、项目背景

端侧语音助手表面上只是“说一句话,设备回答一句话”,实际需要多条链路共同工作:

麦克风采集 → 唤醒词检测 → ASR 语音识别 → Agent 问答 → TTS 合成 → 扬声器播放
                                      ↓
                              页面状态 / HTTP API / Windows 控制台

调试过程中,页面、SSH、日志、音频命令和模型请求分散在不同入口。页面能显示,不等于语音服务在线;接口返回成功,不等于扬声器确实出声;Windows 界面出现传感器数值,也不一定代表数据来自 M2。

因此,本阶段完成了两项工作:

  1. 将采集、唤醒、识别、问答、合成和播放拆成可独立观察的环节;
  2. 开发 Windows 原生控制台,统一处理连接测试、状态查看、文字问答和调试反馈。

二、系统组成与 M2 的角色

模块 当前方案 运行位置
语音采集 板载麦克风,PipeWire/arecord M2
唤醒与 ASR Vosk 本地模型 M2
开放域问答 DeepSeek API 云端
本地状态问答 规则与本地降级逻辑 M2/客户端
语音合成 Azure Speech,缺少配置时回退本地 TTS 云端/M2
状态与问答接口 HTTP 服务 M2
调试控制台 PySide6 桌面程序 Windows
联网搜索 DuckDuckGo HTML 搜索,可选启用 Windows

M2 在当前版本中主要承担音频输入、本地 Vosk 推理、页面与接口服务以及音频输出。RK3576 的 NPU 尚未接入当前推理链路,因此本文不把“6 TOPS NPU”作为已经使用或验证的成果。后续若迁移到支持 RKNN 的模型,还需要单独补充模型转换、精度和性能测试。

板端服务默认只监听 127.0.0.1:8787。当前路由分为两类:

  • 页面路由:/screen、/window;
  • API:/health、/window.json、/chat。

Windows 访问开发板时使用 SSH 本地端口转发,避免将调试接口直接暴露到公网或不可信局域网。

三、先定义什么才算“通过”

为了避免把单点成功误认为整条链路完成,我为每项能力设置了独立的验收条件。

能力 当前状态 通过标准
Windows 控制台启动 已通过 EXE 可启动并正常显示中文界面
Agent 软件装配 已通过 动态模块加载完成,engine errors=0
演示问答 已通过 使用合成数据返回正确的本地状态回答
M2 页面显示 已通过 HDMI 页面可打开并提交文字问题
M2 麦克风采集 历史单次通过 录音存在有效电平且无明显削波
唤醒词 待真机验收 真人说出唤醒词后 wake_count 增长
ASR 待真机验收 asr_count 增长且文本与语音一致
TTS 与扬声器 待真机验收 tts_count 增长且现场听到播报
完整语音闭环 未完成 唤醒、识别、回答、播报连续完成

这个状态表也是本文的结论边界:软件模块通过测试,不等同于硬件现场闭环已经完成。

四、Windows 控制台

4.1 区分演示模式和开发板模式

控制台使用 PySide6 原生控件实现,没有嵌入 WebView。界面包含三个入口:

  • 对话:发送文字问题,显示回答、搜索状态和播报状态;
  • 设备概览:查看连接、模型、音频链路和运行组件;
  • 连接设置:在“本机演示”和“M2 开发板”之间切换。

默认进入本机演示模式,并注入合成传感器数据。只有切换到“M2 开发板”并通过连接测试后,界面数据才可能来自板端。

!

图 1:控制台连接设置。运行目标和实际服务地址同时显示,避免把本机演示误认为真机连接。

4.2 请求过程可观察

模型和搜索请求运行在独立 worker 中,界面会按阶段显示:

正在联网搜索...
正在思考...
正在通过开发板播报...

板端 /window.json 同时提供 thinking、speaking 等状态,便于页面轮询。这样可以区分“程序卡死”“模型仍在请求”和“正在等待板端播放”。

4.3 快速问答策略

为了缩短交互等待时间,快速问答场景采用以下约束:

  • 关闭 DeepSeek Thinking Mode;
  • 回答最多 128 tokens;
  • temperature=0.35;
  • 模型调用失败时不自动重复请求;
  • 提示词要求直接回答,通常不超过三句话。

这些设置减少的是生成量和重试次数,不能消除网络抖动,也不属于模型训练中的 L1/L2 正则化。

4.4 联网搜索与语音配置

启用搜索后,Windows 客户端会先查询 DuckDuckGo HTML 页面,将限长后的摘要和来源交给 Agent。搜索超时为 5 秒;超时或无结果时继续使用模型知识回答,并明确标记降级状态。该能力运行在 Windows 客户端,不代表 M2 本身实现了联网搜索。

目标云端语音配置为:

voice: zh-CN-XiaoxiaoNeural
style: affectionate

Azure Key 和区域只从环境变量读取,缺少配置时回退本地 TTS。配置读取和 SSML 构造已有单元测试,但 Azure 请求成功、M2 播放和现场听音仍需要一次完整验收。

五、两个值得记录的 Windows 打包问题

5.1 Qt DLL 加载失败

初版单文件 EXE 启动时报 PySide6.QtGui DLL load failure。排查后发现,单文件解包产生的 Qt/Shiboken DLL 目录没有稳定加入 Windows DLL 搜索路径,同时机器中其他 Qt/ICU DLL 可能发生冲突。

最终处理方式包括:

  • 在导入 PySide6 前通过 runtime hook 注册 bundled DLL 目录;
  • 过滤 PATH 中可能冲突的外部 Qt 目录;
  • 预加载所需 ICU;
  • 重新构建单文件产物。

修复后的 EXE 已在当前 Windows 机器上完成启动验证。其他 Windows 版本和全新机器的兼容性仍需要单独测试。

5.2 窗口正常,但 Agent 能力缺失

PyInstaller 可以发现静态 import,但项目中的 Skills、drivers 和 adapters 由运行时动态装配。初版产物因此出现“窗口能打开、Agent 回答为空”的情况。

在构建脚本中显式收集动态模块后,自检结果为:

Skills: 43
Agents: 7
engine errors: 0

本机演示模式下询问“当前骑行速度是多少?”,可以返回合成状态数据:

当前配速 18.5 公里每小时。

这项测试证明桌面端问答和动态模块装配工作正常,但不用于证明 M2 传感器已经接入。

六、M2 音频排查

6.1 一次有效的麦克风采集

历史诊断中记录过一次 5 秒、48 kHz、单声道采集:

samples: 240000
RMS: 3300.3
peak: 16392
non-zero samples: 99.88%

这次数据说明当时录到了有效声音,峰值也未达到 16-bit PCM 的削波上限。但单次录音不能替代持续服务测试,更不能证明唤醒识别已经成功。

遇到录音失败时,可以按以下顺序确认音频服务和设备:

systemctl --user status pipewire pipewire-pulse wireplumber
pactl list short sources
pactl get-default-source
arecord -l

调试中 hw:0,0 曾因设备被占用而无法打开,后续改为通过 PipeWire 共享输入。如果 PyAudio 仍选择错误设备,可固定 capture_backend=arecord,并核对实际 card/device 编号。

6.2 为什么唤醒仍不能标记为通过

默认唤醒词配置包含“小爱小爱”的常见识别变体,识别使用本地 Vosk,不依赖云端 Key。但最近留存的 health 数据为:

wake_count = 0
asr_count  = 0
tts_count  = 0

因此目前只能确认模型、配置和模拟测试已经准备好,不能声称真人唤醒已稳定工作。真正的通过标准是:现场说出唤醒词后,计数增长、ASR 文本正确,并最终从扬声器听到回答。

6.3 本地 ASR 不等于全链路离线

Vosk 可以在 M2 本地完成唤醒和语音转文字,但开放域问答仍可能调用 DeepSeek,Azure TTS 也依赖网络。网络断开后,系统只保留本地状态问答和基础降级回答,因此当前方案应描述为“端云协同”,而不是“端到端离线大模型”。

七、板端连接与复现步骤

开发板 HDMI 页面已能够显示问答界面。下面这张照片记录的是早期联调状态,证明页面已在板端显示,但不作为完整语音链路的验收证据。

Quectel Pi M2 HDMI 页面:早期语音助手联调界面

图 2:M2 板端 HDMI 页面早期联调画面。

首先在 M2 上确认服务和端口:

sudo systemctl status voice-agent.service
ss -lntp | grep 8787
curl http://127.0.0.1:8787/health
curl http://127.0.0.1:8787/window.json

然后在 Windows 建立本地端口转发:

ssh -L 8787:127.0.0.1:8787 <user>@<M2_IP>

最后在控制台中选择“M2 开发板”,将地址设置为 http://127.0.0.1:8787 并执行连接测试。只有完成这些步骤后,控制台中的板端状态才具备真机含义。

八、截至 2026-10-20 的验证结果

项目 结果 说明
PySide6 窗口 通过 当前 Windows 机器可正常显示
单文件 EXE 通过 尚未完成多机器兼容性矩阵
动态模块装配 通过 43 Skills、7 Agents、engine errors=0
演示问答 通过 使用合成数据
Python compileall 通过 仅代表语法编译成功
单元/集成测试 199 项通过、5 项跳过 属于留存的软件测试记录,不等同于硬件验收
M2 页面显示 通过 已有 HDMI 联调画面
M2 麦克风 历史单次通过 需要在当前服务状态下复验
M2 语音闭环 未完成 缺少现场唤醒、识别和播报证据
M2 HTTP 服务 最近一次检查不可达 需要恢复连接后重新读取 health

九、总结

本阶段真正完成的成果,是建立了一套可观察、可拆分的端云语音调试链路:Windows 控制台可以统一查看状态和发起问答,Qt DLL 与动态模块打包问题已经解决,本地演示数据和 M2 真机数据也有了明确边界。