跳到主要内容

百炼「实时多模态交互」API 验证记录

4247 字约 11 分钟更新于 2026-10-162 次阅读#rv1106#嵌入式#语音交互#大模型

安全说明 本文中的 API Key、Workspace ID、App ID 等账号信息已全部脱敏(写作 sk-****、llm-****、mm_****),只保留字段结构与调用方式;历史命令保留可复现的字段名,不含真实凭据。

背景 在这个项目里,语音对话客户端要跑在 RV1106G3 板端(armv7l + uClibc)。官方只提供 x86_64 与 aarch64 的预编译 SDK,板子用不了;但底层的 WebSocket 协议是公开的。于是先在虚拟机上**用纯标准库(不装任何第三方包)**把协议跑通,再往板子上移植。

一、为什么不用官方 SDK

方案 结论
官方 Linux C++ SDK 只提供 x86_64 / aarch64 预编译包;板子是 armv7l + uClibc,架构和 C 库都对不上
官方服务端 Python SDK(dashscope) 能跑在虚拟机或服务器上,但板子装不了,还引入大量依赖
WebSocket 裸协议自研客户端 ✅ 只依赖 libc 与 OpenSSL,协议公开且完整

二、协议要点

  • 服务地址:wss://dashscope.aliyuncs.com/api-ws/v1/inference
  • 鉴权:WebSocket 握手(HTTP Upgrade)时带 Authorization: Bearer <API Key>
  • 消息分两类:二进制 = 音频;文本 = JSON(header + payload)
  • header 固定三字段:task_id(本次连接唯一)、streaming: "duplex"、action 取 run-task / continue-task / finish-task
  • Start 里的固定值直接照抄:task_group=aigc、task=multimodal-generation、function=generation、model=multimodal-dialog
  • 关键时序:Start → 服务端回 Started → 必须再等到 DialogStateChanged 的 state=Listening,才可以发音频或发文本请求

音频格式(push2talk 模式)

方向 要求
上行 PCM,16 kHz、16 bit、单声道、有符号、little-endian
分包 建议每 100 ms 一包 → 16000 × 2 × 0.1 = 3200 字节
下行 默认 24 kHz,可按需选 pcm / mp3 / opus

三、验证环境

  • Ubuntu 22.04 虚拟机,Python 3.10.12 + OpenSSL 3.0.2
  • 不装任何第三方库:握手用 socket + ssl 自己拼 HTTP Upgrade,WebSocket 帧编解码按 RFC6455 手写(客户端帧加掩码、服务端帧不加),PCM 重采样用标准库 audioop
  • 音频来源:把 TTS 下行音频重采样成 16 kHz 后回灌给 ASR,全程不需要麦克风

四、四项验证

1. 文本触发(打通大模型 + 语音合成)

Start(run-task) → Started → DialogStateChanged(Listening)
→ RequestToRespond(type=prompt, text="…")
→ SpeechContent → DialogStateChanged(Thinking) → DialogStateChanged(Responding)
→ RespondingStarted → 二进制音频帧… → RespondingEnded → Stop

RespondingContent 是流式推送的(finished=false 一路推到 true),可以直接拿来做屏幕字幕。

2. 语音识别(ASR)

Start(mode=push2talk) → 等 Listening
→ SendSpeech → 按 100 ms / 3200 字节送 PCM → StopSpeech
→ SpeechContent(识别文本,流式全量)

实测:把 TTS 生成的一句话重采样成 16 kHz 后送上去,识别结果与原句完全一致。识别完成后服务端会继续走大模型与语音合成,等于一次拿到「识别 → 回复 → 合成」全链路。

3. 对话打断

播报中(已收到 27048 字节音频)→ 发 RequestToSpeak
→ RequestAccepted → DialogStateChanged(Listening)

打断被接受后服务端立刻停止下发音频,并回到可以继续说话的状态。

4. 心跳保活

服务端规则:任意连续 60 秒没有消息就会断开。客户端每 50 秒发一次 HeartBeat,服务端会原样回一条。

实测往返 0.06 秒。

五、协议覆盖情况

消息 状态
Start / Stop ✅
DialogStateChanged(Listening / Thinking / Responding) ✅
RequestToRespond(文本触发) ✅
SendSpeech / StopSpeech / 二进制音频上行 ✅
SpeechContent ✅
RespondingStarted / RespondingContent / RespondingEnded ✅
二进制音频下行 ✅
RequestToSpeak ✅
HeartBeat ✅
尚未验证:VQA 图片、UpdateInfo、CancelSpeech、自定义对话历史 —

六、踩过的坑

1. 音色必须与合成模型对应

应用配置的是多模态交互轻量版语音合成(AppSpecificTTS),它要求显式指定音色,否则报 426 InvalidTtsVoice: tts voice error, need AppSpecificTTS voice.

  • 可用:xiaoyun、aixia、aimei、aiqi、zhixiaoxia
  • 不可用:longxiaochun(属于 CosyVoice 大模型的音色)

2. 「插件」与「指令」是两回事

  • 插件:云端工具(天气、万年历、新闻…),回答"问什么"
  • 指令:云端下发、设备执行的动作(驱动灯效、舵机…),让设备"动起来"

开了意图识别但没配任何工具时,extra_info 里能看到判定结果:

{"agent_info":{"intent_infos":[{"domain":"text_chat","intent":"chat"}]},"tool_infos":[]}

tool_infos 为空,说明本轮没有工具被调用。

3. extra_info.commands 是 JSON 字符串

若启用「指令」,下发内容位于 RespondingContent.extra_info.commands,且是字符串形式,需要二次解析:

[{"name":"VOLUME_SET","params":[{"name":"series","normValue":"70","value":"70"}]}]

4. 没有 AEC 的板子优先选 push2talk

tap2talk / duplex 都要求客户端持续上传音频,Listening 状态超过 20 秒不传就会报错;push2talk 只在按键期间上传,逻辑最简单,也不会出现"设备听见自己喇叭"的自激问题。

七、板端内存实测(Python 版)

在目标板(RV1106G3,Buildroot,Python 3.11.6)上实测了这套脚本的常驻内存。结论:Python 方案可用,但很吃内存。

模块可用性

os / sys / json / socket / struct / base64 / time / uuid / wave / audioop / ssl 全部可用(audioop 仅有「Python 3.13 将移除」的弃用警告),TLS 1.3 握手正常。也就是说同一套脚本原则上可以直接在板端运行,只需补根证书与凭据。

分阶段 VmRSS

阶段 VmRSS
Python 解释器启动 5660 kB
导入全部所需标准库后 9184 kB
建立 TLS 连接后 10840 kB

加上运行时缓冲

场景 额外缓冲 峰值
文本触发(累积 TTS 音频) 约 48 KB/秒 → 30 秒约 1.4 MB ≈ 12 MB
语音识别(录音与回复都驻留内存) 5 秒录音 160 KB + 回复 1.4 MB ≈ 12.5 MB
语音识别(录 30 秒) 960 KB + 1.4 MB ≈ 13.2 MB

放进板子的内存预算

板子总量            45060 kB (约 44 MB)
移除 Samba 后可用    约 21860 kB (约 21 MB)
Python 客户端        约 11000 kB (约 11 MB)   ← 占掉一半
剩余                约 10860 kB (约 10 MB)

与 C 实现的对比

项目 Python 版 C 版(预计)
语言运行时 约 10.8 MB 0
TLS 库状态 已含在上面 约 0.3~0.8 MB(OpenSSL / mbedTLS)
收发与音频缓冲 1~2.5 MB 约 0.1~0.3 MB(流式,无需全量驻留)
合计 约 11~13 MB 约 0.5~1.5 MB

结论:板端可用内存只有约 21 MB,Python 单进程就要占掉一半,之后还要给屏幕显示与 NPU 人脸检测留空间,因此正式实现仍走 C;Python 版保留作为板端排障工具——协议一致、无额外依赖,出问题时在板端原样跑一遍,就能快速区分是网络/协议问题还是 C 实现的问题。

八、结论与下一步

  • 协议层面已验证完整:四条典型路径(文本触发 / 语音识别 / 打断 / 心跳)都有可运行的参考实现
  • 下一步是用 C 重写这套逻辑并交叉编译到 RV1106G3 板端,解决板端的 TLS(OpenSSL 1.1.1 + 根证书)与 uClibc 问题
  • 数据流规划:麦克风采集(16 kHz / 16 bit / 单声道)→ 上行;下行 PCM → 喇叭播放;SpeechContent 与 RespondingContent → 屏幕字幕

相关文章


评论

评论区由 giscus 提供(基于 GitHub Discussions),内容会保存在 GitHub 上;详见《隐私政策》。