Web アプリでマイクを取得するとき、getUserMedia({ audio: true }) と書くだけで音は取れる。ただしこの「取れる」は、ブラウザが既定で有効にする音声前処理を全部通った後の音である。ノイズ抑制が代表格で、定常ノイズを推定して引き算する処理が常時かかっている。
これが効くのは、マイクが口元にあって、消したいものがエアコンやファンの連続音である場合だ。会議室でノートPCを机の真ん中に置き、その端末から離れた席の人が話す状況では前提が崩れる。話者の声そのものが減衰して到達し、残響を含み、レベルとしては抑制側が「背景」と判断してもおかしくない大きさになる。結果として、消してほしくない遠くの音声が背景音と同じ扱いを受け、削られる。波形としては確かに静かになっているが、認識に必要な情報は減っている。
厄介なのは、この劣化がブラウザ内部で完結して不可逆である点だ。アプリに渡ってくるのは処理後のストリームで、元の音は残らない。クラウド STT に送る前の段階で、すでに情報が落ちている。
こちらの構成では文字起こしに Solaria-1 を使っている。クラウド側で背景音の扱いまで済むので、ブラウザ側で先に引き算しておく理由がない。そこで取得時の制約を次のように固定している。
const MIC_CAPTURE_CONSTRAINTS: MediaTrackConstraints = {
noiseSuppression: false,
echoCancellation: true,
autoGainControl: true,
};
noiseSuppression: false
意図的に切っている。ノイズ除去はクラウド側の責務とし、ブラウザには生に近い音を通してもらう。前処理を二重にかけると、片方が削った情報をもう片方が復元することはない。遠くの話者の音声を保つという目的に対して、この二段構成は損しかしない。なお、この判断は STT 側が雑音耐性を持っていることが前提であり、ブラウザの音声をそのまま人間が聞く用途には当てはまらない。
echoCancellation: true
これは有効のままにする。ノートPCやヘッドセットなしの会議環境では、スピーカーとマイクが同一筐体にある。相手の音声がスピーカーから出て、同じ端末のマイクに回り込む。エコーキャンセルを切ると、この回り込みがそのまま文字起こしに乗り、相手の発話が二重に記録される。参照信号を使って出力音を差し引く処理なので、ノイズ抑制とは性質が違い、話者の声を推定で削ることはない。
autoGainControl: true
有効にしている。マイクから離れた話者の音声は入力レベルが低く、そのままでは小さい。自動ゲイン調整はこれを持ち上げる。ノイズ抑制と違って周波数成分を推定削除するのではなく、全体の増幅率を動かす処理であり、小さい音声を救うという今回の目的と向きが揃っている。背景音も一緒に持ち上がるが、その処理は STT 側に任せる方針なので矛盾しない。
既定値は「口元のマイクで通話する」という典型的なユースケースに最適化されている。会議室の音声をクラウド STT に流すという構成では、その典型から外れていることを自覚して、一つずつ指定し直す必要がある。
この構成は LiveSuggest の中で動いている。