非同期音声通知

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:// のどちらの URL も受け付けます。HTTP はローカルネットワークでの開発に便利で、本番環境では URL の生成時に HTTPS を強制できます。

subtitles は省略可能です。各項目には、メディアの開始時刻(ミリ秒)と表示するテキストが入ります。デバイスは項目を start_ms で並べ替え、再生位置が新しい字幕の項目を越えたときだけ表示を更新します。

このメッセージには、確認応答、通知 ID、状態、種別、有効期限のフィールドはありません。配信はベストエフォートで、オンラインのデバイスにのみ適用されます。

音声レスポンス

audio_url は、モノラル・単一ストリームの Ogg Opus ファイルを含む 2xx の成功レスポンスを返す必要があります。Content-Length を持つレスポンスも、チャンク転送エンコーディングのレスポンスも使えます。デバイスはリダイレクトに従いません。

ファイルは少しずつ読み込まれます。デバイスはレスポンス全体の長さに応じてメモリを確保せず、再生前にファイル全体をダウンロードすることもありません。通知のストリーミングでは、既存の容量制限付き HTTP レスポンスキュー、2 KB の Ogg 論理パケットバッファ、共有の 20 パケットの Opus デコードキュー、既存の 2 フレーム分の PCM 再生キューを使います。デコードキューは、ファームウェアの通常のパケット長 60 ms で 1.2 秒分に相当します。これらのバッファが TCP のバックプレッシャーを働かせるので、PSRAM のないデバイスでもこの機能を使えます。

独立した LiteAudioEngine の WakeNet を使うデバイスでは、通知の再生中は WakeNet のリソースを解放し、デバイスが Idle に戻るときに再作成します。AFE を使うデバイスは、これまでのローカルウェイクの動作を維持します。

Opus のパケット長は、MQTT メッセージで指定するのではなく、Opus の TOC バイトから読み取ります。ファームウェアのデコーダーが対応する、5 ms から 120 ms までの整数ミリ秒のパケット長を受け付けます。現在の実装は、ステレオのストリーム、検出された Ogg または Opus の構造エラー、不完全なストリーム、サイズ超過の論理パケット、2.5 ms のパケットを拒否します。

デバイスの動作

デバイスは Idle 状態のときだけ notify を受け付けます。受け付けると、次の動作を行います。

  1. 内部の Notifying 状態に入り、ボードをパフォーマンスモードに切り替えます。
  2. 通常の音声処理とマイクのアップリンクを無効にします。
  3. 以前の再生内容を消去し、内蔵のポップアップ音をキューに入れます。
  4. バックグラウンドタスクで HTTP GET を 1 回開始します。
  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 に戻ります。デバイスがビジー状態のときに届いた 2 件目の通知は無視されます。

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