RK3588 离线视觉语音助手优化实战:从几十秒等待到秒级首声
引言
本文记录一次运行在 RK3588S 平台上的离线视觉语音助手优化过程。项目把 C310 摄像头、麦克风、KWS 唤醒、VAD、SenseVoice ASR、Qwen3-VL 视觉问答、VITS TTS、ALSA 播放和 RTMP 推流组合在一起,目标是完成一条可以持续运行的本地多模态交互链路:
用户说话
→ KWS 唤醒
→ 确认音“我在”
→ VAD 采集命令
→ SenseVoice ASR
→ Qwen3-VL 读取画面并生成回答
→ TTS 分段合成
→ ALSA 播放
摄像头推流作为独立并行链路运行:
C310 1280×720 MJPEG
→ 解码与色彩转换
→ h264_rkmpp 硬编码
→ SRS 推流
这次工作的重点放在时间测量、进程交接、队列可靠性、图像特征复用和回声处理,保留 Qwen3-VL-2B、SenseVoiceSmall 和离线 VITS。8GB 级别的板卡内存和 NPU 资源有限,模型、运行库和摄像头链路都需要保持稳定。
github可以部署尝试: https://github.com/johnjiamzhong-project/EdgeSightAssistant
一、硬件和软件约束
目标设备为 RK3588S 板卡,视觉模型通过 RKNN/RKLLM 运行,ASR 和 TTS 使用 sherpa-onnx C API。设备上的资源约束决定了几个设计边界:
- 图像编码、语言模型推理和视频编码都要共享板端计算资源,需要关注 NPU 竞争和推流稳定性。
- 当前 VITS 入口属于 offline TTS,输入是一段文本,输出是一段完整 PCM,无法直接把 LLM 的每个 token 变成即时音频。
- C310 摄像头和麦克风需要持续工作,视觉问答和 TTS 播放期间不能暂停视频推流。
- 目标板上的模型和运行库放在仓库外部,代码仓库只保存配置示例、脚本、测试和文档。
因此,本次采用句子级模拟流式 TTS、唤醒阶段图像预编码、有限队列和单调时钟埋点,保持内存边界和进程边界清晰。
二、先统一计时口径
完整交互可以从唤醒开始计时,也可以从用户说完开始计时。两者回答的问题不同:
| 指标 | 起点 | 终点 | 用途 |
|---|---|---|---|
| 唤醒到首声 | wake_detected |
tts_playback_started |
衡量用户整体等待感受 |
| 录音结束到首声 | command_ended |
tts_playback_started |
衡量系统处理速度 |
| 录音结束到播完 | command_ended |
tts_finished |
衡量完整回答交付时间 |
| ASR 收尾 | command_ended |
asr_final |
衡量识别收尾开销 |
本文主要采用“录音结束到首声”。command_ended 是 VAD 判定用户停止讲话的时间点,tts_playback_started 是回答 PCM 开始送入播放会话的时间点。所有进程使用 Linux CLOCK_MONOTONIC 域的 monotonic_ms 记录事件,跨进程计算时不会受到系统时间校准影响。
早期版本
早期链路中,图像编码、视觉推理、完整文本生成、TTS 合成和播放存在较强的串行等待。按同一口径折算,录音结束后约 55 秒才能听到第一句话,约 88 秒完成全部播放。这个数字描述的是旧链路历史基线,保留它的意义在于展示优化前后的量级变化。
最新短回答
问题为“最上面的药盒是什么颜色?”,ASR 识别正确,视觉回答为“白色”。
command_ended → 32214583 ms
asr_final → 32214797 ms
tts_playback_started → 32216176 ms
tts_finished → 32216828 ms
由此得到:
- 录音结束到 ASR 最终结果:214 ms;
- 录音结束到回答首声:1593 ms,约 1.6 秒;
- 录音结束到播放结束:2245 ms,约 2.2 秒。
最新长回答
问题为“请描述画面中所有物体,并说明它们的颜色和位置”。回答包含黑色手套、绿色手机、两支白色药膏管和蓝色封面的说明书。
command_ended → 32153465 ms
asr_final → 32153880 ms
vision_first_token → 32154794 ms
tts_request_sent → 32155753 ms
vision_finished → 32157232 ms
tts_playback_started → 32159336 ms
tts_finished → 32167896 ms
按录音结束计算:
- 录音结束到 ASR 最终结果:415 ms;
- 录音结束到回答首声:5871 ms,约 5.9 秒;
- 录音结束到播放结束:14431 ms,约 14.4 秒;
- TTS 两段合成约 7127 ms,实际播放约 8109 ms。
长回答的首声等待明显高于短回答,主要受首段文本生成、VITS 合成量和段数影响。两类问题需要分别统计,单个百分比无法覆盖所有文本长度。
三、ASR:收窄过滤规则,保留有效短问句
项目的命令 ASR 使用离线 SenseVoiceSmall。离线模型在 command_ended 后解码最终文本,当前链路还没有 n-best 候选和声学置信度输出,因此文本过滤必须保持保守。
早期版本根据归一化后的字符数量过滤单 token。这个规则会把“谁?”“几?”等合法的单字中文问题误判为噪声。现在 AsrTextFilter 采用以下顺序:
- 去除空白和标点,空结果标记为
empty_asr; - 与配置的唤醒确认语完全相同时标记为
ack_echo; - 对“嗯、啊、呃、哦”等明确语气词进行过滤;
- 仅把一个字节的 ASCII 碎片标记为
single_token; - 其他中文文本全部接受。
被过滤的结果仍输出结构化日志,便于区分用户确实说了语气词、确认音回声进入了麦克风,还是模型发生了近音误识别。后续如果运行库提供 n-best 或分数,可以在“垫子/电子”这类可疑词上增加确认流程。
四、VAD 和确认音期间的边播边录
唤醒后,音频状态机先进入等待命令状态。确认音发送成功后会记录:
tts_ack_sent
ack_input_gate_armed
asr_started
command_ended
asr_final
当前目标板默认 wake_ack_guard_ms=0,确认音播放期间仍将麦克风数据送入 VAD/ASR。这个选择可以减少用户等待,也会把扬声器回声带入采集端,所以需要 AEC 处理。现场如果要求确认音完全结束后再说话,可以临时启用时间门控;项目当前保留边播边录作为默认测试路径。
五、视觉预编码和“帧龄”定义
检测到“你好小视”后,视觉 worker 立即从 latest-frame store 取出当前画面并执行图像编码。用户说完问题时,如果准备好的 embedding 仍然有效,语言模型可以直接使用它。
代码中的上限为:
constexpr std::uint64_t kPreparedImageMaxAgeMs = 10000;
这里的“帧龄”指推理开始时间减去摄像头帧采集时间。准备帧帧龄不超过 10 秒时复用 embedding;超过 10 秒时丢弃准备结果,改用最新帧重新编码。
最近几轮日志中的准备帧帧龄约为 4~8 秒,图像编码约 2.1 秒,通常可以在用户说话期间完成。image_encoder_reused=true 表示问句处理阶段复用了唤醒时的图像特征。
这个优化适合静态桌面场景。帧龄 4~8 秒说明模型实际看到的画面距采集已有几秒,并不等同于画面实时更新。快速移动物体、数量统计和小字识别可以缩短复用窗口,或者在问句到达后重新取帧。预编码还会占用 NPU 时间,因此当前采用“唤醒阶段准备、问句阶段复用”的串接策略,持续观察帧龄、视觉延迟和视频 FPS。
六、视觉回答约束和安全分段
Qwen3-VL 的回答需要适合语音播放。当前提示词要求模型先给出核心结论、减少重复和展开说明,并限制回答长度。这样可以减少 VITS 合成量,也能控制 TTS 输入队列。
文本分段器有几条具体规则:
- 所有切分位置都落在完整 UTF-8 字符边界;
- 短回答优先等到句末,避免把最后几个字拆成孤立短尾;
- 较长回答优先在句号、问号、逗号和顿号等安全位置切分;
- 引号、括号、书名号内部尽量等待成对结构闭合;
- 纯标点段不进入 VITS;
- 首段和后续段共用一个持久 ALSA 播放会话。
这些规则解决了两类听感问题:首声等待过长,以及句末标点单独合成后产生明显停顿。当前 VITS 仍然按文本段生成完整 PCM,句子级切分属于兼容现有模型的过渡架构。
七、流式 TTS 协议:从“发出”变成“确认接收”
原始 Unix datagram 调用只能知道本地 sendto() 返回成功,发送端无法确认 TTS 服务是否真正接受了文本。当 TTS 输入队列已满时,文本可能在应用层被拒绝,回答尾段就会出现缺失。
现在的流式控制消息仍然很小:
__edgesight_tts_stream_begin__:<首段文本>
__edgesight_tts_stream_segment__:<后续文本>
__edgesight_tts_stream_end__
视觉端为每次控制消息创建临时 Unix datagram 回包地址,TTS 收到消息后返回三种结果:
__edgesight_tts_ack__
__edgesight_tts_retry__
__edgesight_tts_reject__
关键参数如下:
ACK 等待超时:1000 ms
重试间隔:20 ms
最大重试次数:250
TTS 尚未取出的输入片段:最多 2 段
已合成待播放片段:最多 2 段
收到 retry 后,发送端保留当前文本,等待队列腾出空间再发送。收到 reject 或重试耗尽时,当前流会记录失败原因,避免把未完成的文本当成完整回答结束。
目标板真实测试中,三段测试文本收到的控制结果为:
ACK、ACK、RETRY、ACK、ACK
TTS 日志随后出现 segments=3,三段均完成合成和播放,队列满造成的瞬时压力没有转化为尾段丢失。
八、AEC:发送播放参考信号
AEC 需要知道扬声器正在播放什么内容。TTS 侧会把即将播放的 44.1 kHz PCM 重采样为 16 kHz,再按 160 samples 一帧发送到音频端:
采样率:16000 Hz
单帧:160 samples
单帧时长:10 ms
SpeexDSP 自适应滤波:约 200 ms
默认播放/采集参考偏移:20 ms
参考包携带单调时钟时间戳,音频端按照采集帧时间戳取相应的 far-end PCM。参考发送使用可靠路径,发送失败会记录 errno;--aec-reference-delay-ms 和 EDGESIGHT_AEC_REFERENCE_DELAY_MS 可以调节板端实际声学延迟。
真人回归时,用户在“我在”播放期间立即说“手套是什么颜色的?”。日志同时出现 aec=enabled、aec_reference=enabled、正确的 asr_final 和视觉回答“黑色”。确认音与回答音的参考帧发送均为 errno=0。
这个结果证明基础链路可工作,声学效果仍需要在不同距离、音量和环境噪声下继续做 A/B。评估指标应包含有效语音识别率、确认音回声误识别率和残余回声能量。
九、TTS 合成线程和端到端收益
同一段文本的无播放合成测试中,TTS 推理线程从 2 个调整到 4 个后,合成耗时由约 13.8 秒降到约 7.7 秒,下降约 44%。这个参数保留在启动脚本中,目标板也完成了实际播放验证。
当前最新短回答的处理路径大致为:
录音结束
→ 214 ms:ASR 收尾
→ 约 892 ms:视觉首 token
→ 约 1.6 s:回答开始播放
→ 约 2.2 s:回答播放完成
长回答则受到两段 VITS 合成影响:
录音结束
→ 415 ms:ASR 收尾
→ 914 ms:视觉首 token
→ 约 5.9 s:第一段开始播放
→ 约 14.4 s:两段全部播放完成
从这些数据可以看出,短回答的主要体验已经进入秒级;长回答的关键瓶颈集中在首段生成、VITS 合成和音频播放长度。后续继续优化时,需要分别优化首段交接和完整回答吞吐,不能只看单个总耗时。
十、测试和验收结果
主机测试
执行 scripts/test.sh,CTest 共 8 项:
edgesight_config_test
edgesight_activation_test
edgesight_command_vad_test
edgesight_asr_text_filter_test
edgesight_wake_ack_gate_test
edgesight_vision_image_test
edgesight_latest_frame_test
edgesight_tts_text_segmentation_test
结果为 8/8 通过。新增测试覆盖单字中文问题、TTS 分段安全边界、纯标点过滤和唤醒确认门控。
目标板测试
目标板完成 Audio、TTS 和视觉构建,完整链路使用 start_all.sh 启动,stop_all.sh 收尾。测试覆盖:
- C310 真人唤醒和命令 ASR;
- 确认音播放期间立即说有效问题;
- 短回答实际扬声器播放;
- 长回答两段连续播放;
- TTS 队列满时的 retry;
- AEC 参考帧发送;
- 视觉问答期间视频持续推流。
测试期间视频统计保持约 29.9 FPS,编码错误为 0。目标板日志能同时观察到音频、视觉和 TTS 的单调时间戳,便于复核每一个等待阶段。
十一、当前边界和后续方向
- VITS 仍然是离线入口。想继续压缩首声,需要评估支持增量输入和增量音频输出的端侧 TTS。
- 准备帧允许复用 10 秒,静态桌面效果较好,动态目标和细小文字需要更严格的取帧策略。
- SenseVoice 当前输出最终文本,没有 n-best 和声学置信度;近音词纠错需要增加上下文确认。
- Qwen3-VL-2B 的数量统计和小目标识别仍有波动,后续可以加入逐项列举、再计数的提示和校验。
- AEC 已完成基础真人验证,还需要系统比较 0、20、40、60 ms 等延迟参数,并测试不同扬声器音量和麦克风距离。
- 本轮重点是低延迟和可靠交接,尚未把长时间稳定性、自动断流重连和 GUI 纳入同一验收范围。
总结
这次优化的核心工作集中在四个方向:统一时钟、唤醒阶段预编码、TTS 分段可靠交接、播放参考回声抵消。
统一计时让“等待很久”变成可定位的阶段;预编码把图像处理移到用户说话期间;ACK 和 retry 让队列压力不会静默丢掉回答;AEC 让确认音播放和命令采集可以在同一时间窗口内工作。
对于端侧多模态系统,模块平均速度无法完整代表交互体验。真正需要关注的是用户说完之后多久听到第一声、较长回答能否完整播完、回声是否会污染下一轮 ASR,以及并行优化是否影响视频推流。把这些指标写进日志并用目标板真人测试复核,优化结果才具备可重复性。
项目代码、测试脚本和技术记录保存在仓库中,模型、运行库和真实设备凭据保持在仓库之外。

浙公网安备 33010602011771号