ロイド、しゃべり続ける――無音3.5秒で音声会話ループが成立

Android版Lloyd(ロイド)の音声会話が、ようやく「1回しゃべって終わり」ではなく、会話として回るところまで来た。

現在の流れはこうなっている。

話す
↓
発話後の無音を検出
↓
Stop dictation
↓
ChatGPT標準Dictationが文字起こし確定
↓
自動送信
↓
ChatGPTが回答
↓
VOICEVOXで読み上げ
↓
読み上げ完了
↓
次のDictationを自動開始
↓
また話す

これが複数ラリーで連続して動いた。

終了ワードを毎回言うのをやめた

もともとは、音声入力を終えるために「クローズ」のような終了ワードを検出する方式を考えていた。

しかしAndroid WebViewでは、ChatGPTのDictationと別のSpeechRecognitionを同時に動かしても、Recognition自体はSTART/ENDするのに有効な認識文字列が取れなかった。

さらに独立SpeechRecognitionは約5秒ごとに再起動し、端末から「ピコン」と音まで鳴る。

そこで考え方を変えた。

文字をもう一度認識する必要はない。話し終わったことだけ分かればいい。

ChatGPTがすでに取得しているマイクstreamをcloneし、WebAudioのAnalyserで音量だけ監視する。ロイド側ではこの監視役を「Yor」と呼んでいる。

現在の発話・無音判定

  • 強い発話:RMS 4.5以上なら即発話扱い
  • 弱い発話:RMS 2.0以上が2フレーム連続したら発話扱い
  • 無音判定:RMS 1.2以下
  • 発話後の無音待ち:3.5秒
  • 判定周期:約200ms
  • 今回の無音帯実測:おおむねRMS 0.69〜0.74

最初は1.8秒で試したが、これは短すぎた。普通に考えながら話しているだけで、すぐ確定してしまう。

3秒まで伸ばすとかなり自然になったが、通常会話としてはもう少し余裕が欲しい。現在は3.5秒にしている。

雑音を「発話」と勘違いした

単純に「RMS 2.0を超えたら発話」としていた時、RMS 3前後の一瞬の雑音を人の声だと誤認したことがあった。

そこで判定を二段にした。

大きな音
→ すぐ発話とみなす

小さめの音
→ 連続した時だけ発話とみなす

単発ノイズ
→ 捨てる

この変更後は、短い発話を拾いつつ、瞬間ノイズで勝手に会話が終わる現象がかなり減った。

ロイドがやっているのは「音声認識」ではない

ここが今回一番大きかった。

ChatGPTの音声認識はそのまま使う。ロイドは、それを置き換えない。

不足していた「いつ話し終わったか」だけを横から補う。

つまり役割は、

音声→文字
ChatGPTに任せる

発話終了判定
Lloyd / Yorが補う

回答
ChatGPT

声
VOICEVOX

という分離になった。

新しい音声認識器を作るより、すでに良く動いているものを壊さず、足りないところだけ足す。その方がずっと小さくて安定する。

手動Stopで止まった件

テスト中、一度だけログに「STOP DICTATION BUTTON NOT FOUND」が出た。

一瞬、新しい不具合かと思ったが、原因は単純だった。

私が無音待ちの途中で手動Stopを押していた。

そのためYorが3.5秒後にStopボタンを探した時には、すでにChatGPT側が停止状態へ移っていて、ボタンが存在しなかった。

むしろログは正しかった。

ここで一度、完成を止める

エミュレータで詰めるべき基本構造はかなり出来た。

ここから先は実機でなければ意味が薄い。

  • 車内の走行音
  • 実マイクの感度
  • スピーカーからマイクへの回り込み
  • 端末の処理速度と発熱
  • 長時間会話での安定性
  • 3.5秒という待ち時間の実際の体感

このあたりは、実機を手に入れてから最終調整する。

そして最後に残しているのが「クローズ」。

もう毎回の発話を送るための言葉にはしない。

「クローズ」は音声会話ループそのものを終了する言葉にする予定だ。

一つの発話の終わりは無音。会話そのものの終わりはクローズ。

ようやく役割がきれいに分かれてきた。


※ここに書いたRMS値や3.5秒という値は、現在のエミュレータ環境で得た調整値です。端末・マイク・周囲の騒音によって適切な値は変わります。

コメントを残す

SKULLYNET

AI・音声・Android・WebViewなどを、実際に作って試した記録を残しています。成功だけでなく、失敗や原因究明の過程も公開します。

更新情報

新しい開発記録や記事の更新をメールで受け取れます。

カテゴリー