当前位置: 首页 > news >正文

OpenHarmony 小鸿 AI 开发实战 10:CI1302 语音上行的 UART 帧、Opus 与 VAD

CI1302 不是把一段 WAV 文件一次性交给 WS63。它通过 UART2 连续发送带帧头、命令字、payload 长度和帧尾的协议帧:唤醒事件触发会话,0x0105携带一小帧 Opus,0x0106表示本段 VAD 结束。WS63 在中断回调里只收字节,在任务中解析,再把 Opus 交给 Agent 通过 WebSocket 上行。

本文依据当前 OpenHarmonymini、LiteOS-M 上的 CI1302 UART ring、帧解析、Opus bridge 和 Agent 排序逻辑整理。源码分支与第 08、09 篇相同,本轮重新执行了后端protocol_smoke.py,确认模拟的一帧上行能够进入 ASR→LLM→TTS 流程。但这不是新的实机麦克风录音,也没有覆盖串口噪声、真实 VAD 阈值和弱网堆积。

UART 中断只搬运数据,不在回调里解析协议

ci1302_audio_listen_task.cUART_INT_MODE下注册 RX callback。回调检查长度后把收到的字节压入 ring,并立即返回;AudioListenTask循环调用parse_rx_ring_buff()。这样复杂状态机、日志和 Agent 事件不会运行在 UART 回调上下文。

void uart_read_rx_int_handler( const void *buffer, uint16_t length, bool error) { unused(error); if (buffer == NULL || length == 0) { return; } if ((uint32_t)length > CI1302_UART_RING_SIZE) { return; } ci1302_uart_ring_push( (const uint8_t *)buffer, (uint32_t)length); }

当前 UART2 参数在板级代码中为 921600、8N1。高波特率减少音频帧排队时间,但并不保证业务不丢包;中断禁用过久、ring 满、Agent staging 未清空或 WebSocket 背压都可能成为后续瓶颈。

4 KiB ring 隔离中断节奏与任务节奏

环形缓冲大小是4 * 1024。push 在临界区更新写指针;当下一位置追上读指针时,当前实现通过推进读指针丢弃最旧字节,为新数据腾空间。pop 每次取一个 byte,解析器可以跨多次 UART callback 延续状态。

#define CI1302_UART_RING_SIZE (4 * 1024) void ci1302_uart_ring_push( const uint8_t *data, uint32_t len); bool ci1302_uart_ring_pop_byte(uint8_t *out); uint32_t ci1302_uart_ring_used(void);

丢旧字节能避免生产者永久阻塞,但会破坏一帧的头、长度或尾。解析器因此必须能重新寻找帧头,而不能假设 ring 中第一个 byte 永远是完整帧起点。

帧解析器按字节状态推进并重新同步帧头

当前帧头固定为A5 A5 5A 5A。解析器逐字节匹配头部,再读取命令字、payload length、payload 和 tail。0x0105的最大 payload 被限制为 80 bytes;超长会打印 overflow 并丢弃,避免长度字段把静态数组写穿。

uint8_t frame_head[4] = { 0xA5, 0xA5, 0x5A, 0x5A }; #define CI1302_MAX_FRAME_PAYLOAD (80) static uint8_t s_0105_payload[ CI1302_MAX_FRAME_PAYLOAD];

帧尾不是只检查一个固定值;源码保存多组控制帧 tail,并对音频帧进行对应处理。文章不把完整私有协议表复制出来,只保留理解上行链所需的命令和边界。

0x0102、0x0105 与 0x0106 承担不同职责

0x0102进入唤醒词事件路径;0x0105是一帧 Opus payload;0x0106结束当前 VAD segment。另一些命令用于播放拉取和状态反馈,例如0x020A请求 PCM、0x020D表示播放侧状态。这些下行命令属于第 15 篇,本文只解释为何 parser 必须区分控制帧和音频帧。

switch (cmd_type) { case 0x0102: agent_send_event(AGENT_EVT_WAKE_WORD_DETECTED); break; case 0x0106: { uint32_t total = s_segment_audio_data_len; s_segment_audio_data_len = 0; g_ci1302_last_vad_segment_bytes = total; agent_send_event(AGENT_EVT_CI1302_VAD_SEGMENT_END); break; } case 0x020A: msg_send = eAud_SendAudioData; ci1302_post_audio_event_with_retry( msg_send, "020A"); break; }

0x0105 当前就是 Opus,不在 WS63 重新编码

当 parser 完成0x0105,先检查当前 Agent 状态是否允许上行,再累计本段字节数,最后调用ci1302_uplink_send_opus_frame()。bridge 函数不解码、不重采样,只把 payload 交给agent_feed_audio_data()

if (s_cmd_type == 0x0105) { if (agent_should_uplink_ci1302_opus() && (s_pl_len > 0U)) { s_segment_audio_data_len += (uint32_t)s_pl_len; (void)ci1302_uplink_send_opus_frame( s_0105_payload, (size_t)s_pl_len); } }

当前mongoose_protocol.c的实际宏也声明"opus"。该文件顶部仍有一段历史注释写“上行为 PCM”,但它与当前 parser、bridge 和服务端libopus解码路径冲突。事实检查应以可执行分支为准,并把旧注释视为需要修正的维护债务。

Agent 只保留 80-byte staging,不能无限排队

agent_task.cAGENT_UPLINK_AUDIO_DATA_MAX同样为 80。agent_feed_audio_data()把一帧复制到 staging,并投递AGENT_EVT_SEND_AUDIO;Agent 任务取出后构造protocol_audio_packet_t。如果当前帧尚未发送完成,新帧不能无界覆盖。

#define AGENT_UPLINK_AUDIO_DATA_MAX (80) static uint8_t g_uplink_audio_data_staging[ AGENT_UPLINK_AUDIO_DATA_MAX];

Mongoose 发送侧还有 4096-byte backlog 上限。超过限制时mg_send_audio()返回 false,让上层知道当前网络发送缓存已经过深。这个机制是背压保护,不是零丢包保证;真实弱网下仍要统计 drop、延迟和队列恢复。

VAD stop 必须排在最后一帧 Opus 之后

最容易出现的时序错误是:CI1302 连续给出最后一个0x01050x0106,两个事件进入不同处理路径;若 Agent 先发送 JSONlisten/stop,服务端可能立即开始 ASR,而最后一帧二进制音频还在 staging。

当前代码收到0x0106后只设置s_pending_listen_stop_after_uplinktry_flush_pending_listen_stop()会检查 staging 是否已经清空、状态是否允许,再调用protocol_send_stop_listening()。源码注释明确把这个顺序作为修复目标。

最后一帧 0x0105 -> copy 到 staging -> AGENT_EVT_SEND_AUDIO -> WebSocket binary 发出 0x0106 -> pending listen/stop -> staging drained -> JSON listen/stop

短段、重复 VAD 与启动抖动都要过滤

Agent 为 VAD stop 设定三层过滤:进入 LISTENING 后 500 ms settle window;段累计不足 400 bytes 时忽略;两次 stop 小于 800 ms 时去重。这样可以抑制 CI1302 重复上报0x0106或空段造成的多次 ASR。

#define AGENT_VAD_STOP_DEDUP_WINDOW_MS (800U) #define AGENT_VAD_MIN_SEGMENT_BYTES (400U) #define AGENT_LISTENING_SETTLE_MS (500U)

这些阈值是当前工程选择,不是 CI1302 或 Opus 的通用标准。更换 VAD、帧长或语音场景后,应通过真实语料统计重新调整,而不是把源码常量当成硬件规格。

auto、manual 与 realtime 决定何时允许 stop 和上行

agent_listen_mode_t包含 AUTO、MANUAL 和 REALTIME。AUTO 与 REALTIME 接受 VAD segment stop;MANUAL 由按键或显式结束控制。REALTIME 在 SPEAKING 时仍允许 CI1302 Opus 上行,用于语音打断;其他模式播放 TTS 时不继续上传麦克风帧。

这说明“是否上行”不能只看 WebSocket 是否连接,还要同时看 Agent 状态、监听模式、pending stop 和 stop 已发送标志。把这些条件散落在 UART parser 中会很难维护,所以 parser 只调用agent_should_uplink_ci1302_opus()

验证应覆盖字节流、协议顺序与真实语音三层

字节流层用拆分帧头、错误长度、错误帧尾和 ring overflow 测重新同步;协议层确认二进制帧先于 stop、短段被过滤、背压返回失败;真实语音层再检查录音、VAD、服务端 Opus 解码、ASR 文本和弱网体验。

本轮重新通过的protocol_smoke.py使用替身 ASR/LLM/TTS 验证一帧上行和协议编排,输出asr_to_llm=okprotocol_headers=ok。它能证明 Python 逻辑和协议顺序,没有替代 CI1302 麦克风、UART 921600、真实 Opus 内容和无线链路的实机测试。因此本文结论是“当前源码已接通 CI1302 Opus 上行与 VAD stop 排序”,不是“今天完成了新的端到端语音质量验收”。

http://www.jsqmd.com/news/1282739/

相关文章:

  • TPIC7710EVM评估模块:汽车电子EPB系统ASIC性能验证实战指南
  • Python自动化办公脚本开发指南
  • 做了11年的老牌排版工具,微信公众号排版新手也能轻松上手|企业新媒体选型报告 - 一串葡萄
  • 2026 还在找去水印工具?这两款免安装小程序用了三年没卸载 - 免费软件工具方法教程
  • 文档下载革命:从“看得见“到“拿得到“的35+平台终极解决方案
  • Golang的内存管理--学习笔记
  • 物联网设备电池寿命优化与NBM5100A应用解析
  • 线上报价20万到店变15万|上海浦东爱马仕包包回收五大高频套路一次拆穿+真实案例 - 讯息早知道
  • 破局与红利:深职大职业本科生和专科生的“同校不同质” - 博客湾
  • OpenHarmony 小鸿 AI 开发实战 11:WS63 Agent 与 WebSocket 协议状态机
  • java 练习02 - 判断数字奇偶性
  • 抖音批量下载工具终极指南:从零基础到高效管理的完整解决方案
  • 全体目光看过来,2026信誉好的ai网站建设平台推荐来啦!
  • 2026年亦城人才·人工智能超级个体(OPC)认定办事指南
  • 良固阀门(河南)有限公司——郑州弹簧安全阀领域值得信赖的源头厂家 - 优企名品
  • 发现一篇很好的面经,分享给大家
  • 守护消费权益:北京通州黄金回收无套路合规交易自查攻略 - 生活时报
  • Linux运维实战:从零部署Web应用到Docker容器化完整指南
  • 儿童大脑潜能开发:科学基础与日常实践
  • Django学习1---安装 Django
  • 2026 年 7 月西城多款老翡翠比对,不同种质玉石分开评判 - 生活时报
  • 如何一键实现OBS多平台直播同步:obs-multi-rtmp插件完整指南
  • 鸿蒙应做自己的框架同时兼容安卓APP
  • 数据中心液冷技术:高效散热与节能解决方案
  • Java Lambda表达式实战:5大核心场景与性能优化
  • 寄大件快递Top1推荐!2026年7月最新寄件省钱榜单 - 快递物流资讯
  • KubeRay + Volcano构建云原生分布式AI训练调度体系
  • 2026手机网校搭建平台推荐,零基础也能即刻上手吗?
  • 石景山复古系列奢品浏览,工艺细节纳入藏品综合考量范围 - 生活时报
  • 五大神经网络核心原理与实战:从CNN到Transformer的深度学习指南