异步语音通知(notify)

notify 消息允许云端在设备空闲时播放一条单向语音通知。它不会打开对话音频通道,也不会开启麦克风上行。要开始对话,仍然需要用户明确的唤醒动作。

消息格式

云端通过设备当前协议的控制连接发送下面的 JSON。对于 MQTT 设备,就是现有的 MQTT 控制主题:

{
  "type": "notify",
  "audio_url": "https://cdn.example.com/audio/task-complete.ogg",
  "subtitles": [
    {
      "start_ms": 0,
      "text": "Your task is complete."
    },
    {
      "start_ms": 1800,
      "text": "The result has been saved."
    }
  ]
}

audio_url 为必填,http:// 和 https:// 均可接受。HTTP 便于局域网开发,生产环境可以在生成 URL 时强制使用 HTTPS。

subtitles 为可选。每个条目包含媒体起始时间(毫秒)和要显示的文本。设备按 start_ms 排序,仅在播放进度越过新的字幕条目时更新显示。

该消息没有确认、通知 ID、状态、类型或过期字段。投递是尽力而为的,且只对在线设备生效。

音频响应

audio_url 必须返回成功的 2xx 响应,内容为单流、单声道的 Ogg Opus 文件。支持带 Content-Length 或分块传输编码的响应。设备不会跟随重定向。

文件按增量方式读取。设备不会按完整响应长度分配内存,也不会在播放前下载完整文件。通知流式播放使用现有的有界 HTTP 响应队列、2 KB 的 Ogg 逻辑包缓冲、共享的 20 包 Opus 解码队列以及现有的两帧 PCM 播放队列。在固件默认的 60 ms 包时长下,解码队列相当于 1.2 秒。这些缓冲提供 TCP 反压,使该功能可以在没有 PSRAM 的设备上使用。

在使用独立 LiteAudioEngine WakeNet 的设备上,通知播放期间会释放 WakeNet 资源,设备回到 Idle 时再重新创建。基于 AFE 的设备保持原有的本地唤醒行为。

Opus 包时长从 Opus TOC 字节读取,而不是由 MQTT 消息提供。固件解码器支持的 5 ms 至 120 ms 整数包时长均可接受。当前实现会拒绝立体声流、检测到的 Ogg 或 Opus 结构错误、不完整的流、超长的逻辑包以及 2.5 ms 的包。

设备行为

设备仅在 Idle 状态下接受 notify,随后执行以下动作:

  1. 进入内部的 Notifying 状态,并将开发板切换到性能模式。
  2. 关闭常规语音处理和麦克风上行。
  3. 清除之前的播放内容,并将内置提示音加入队列。
  4. 在后台任务中发起一次 HTTP GET。
  5. 增量解复用 Ogg 包,并直接送入现有的 Opus 解码队列。
  6. 按照到达音频输出任务的 Opus 包的媒体位置显示字幕。
  7. 仅在 HTTP 流成功结束且所有排队音频播放完毕后才回到 Idle。

提示音在 HTTP 任务启动之前就已入队,因此远程音频不会早于它播放。HTTP 连接建立仍会与提示音的实际播放重叠。

如果在远程音频开始播放后、HTTP 流结束前,所有播放队列都被清空,设备会记录日志 Notification playback underrun #<count> at <position> ms。从提示音到流的切换以及正常播放结束,都不会被报告为欠载。

设备不会打开 UDP 音频通道、发送 start-listening,也不会自动进入 Listening。基于 AFE 的设备在播放期间可以继续本地唤醒词检测。没有播放回声消除的设备保持现有的说话模式唤醒行为;硬件唤醒控制仍然可以取消通知。

唤醒动作会取消 HTTP 生产者、清除已排队的通知音频,并继续走正常的唤醒流程。网络中断、HTTP 错误和无效音频同样会取消播放并让设备回到 Idle。设备忙碌时收到的第二条通知会被忽略。

xz-mqtt 转发

xz-mqtt 通过其现有的 Redis RPC 方法转发该消息:

{
  "method": "forward",
  "clientId": "device-client-id",
  "params": {
    "type": "notify",
    "audio_url": "https://cdn.example.com/audio/task-complete.ogg",
    "subtitles": [
      {
        "start_ms": 0,
        "text": "Your task is complete."
      }
    ]
  }
}

RPC 结果包含 MQTT 发送操作返回的布尔值:

{
  "success": true
}

该结果表示 xz-mqtt 已把发布消息写入当前在线的设备连接,并不确认设备已收到或已播放。xz-mqtt 不会创建 UDP 会话、不会启动对话桥接、不会代理 Ogg 文件,也不会为离线设备保留通知。

来源:78/xiaozhi-esp32 的 docs/notify.md