音声活動検知による音声合成の使用量とコストの削減

Leonardo Mcarreiro
Leonardo Mcarreiro
Solution Architect at VoicePing

この記事では、VoicePingでのSpeech to Textの実装の詳細と、開発者がどのようにしてクラウドサービスから最高の効率を引き出すことができたのかをご紹介します。

VoicePingは機能満載で、リモートチームにとって理想的なソリューションです。現在、VoicePingの1年分のプレミアムサービスを無料で提供しています。https://voice-ping.com/ までお越しください。

アブストラクト

ゼロからSpeech-to-Textを実装し、良好な認識結果や精度を得ることは非常に困難です。そのため、多くの企業や開発者は、すでにSaaS型ソリューションを提供している既存のプロバイダーを選択し、優れた結果を出しており、さまざまな言語に対応しています。

使い方次第では、「使った分だけ支払う」という課金方法を採用しているため、安価なソリューションになるかもしれませんが、これは使いすぎると高くつく理由でもあります。使い方や量にもよりますが。

この記事では、VoicePingで使用している、ユーザーの会議音声にSTTを適用する際の高額なコストを削減するための戦略をご紹介します。これは、会議ではほとんどのユーザーが聞き役に徹し、ほとんど発言しないという事実に基づいています。この戦略は、VAD(Voice Activity Detection)ライブラリを使用して、この無音の音声がサービスに流れないようにすることに基づいています。

また、このような戦略を実行する際に直面したすべての問題をどのように解決したかを紹介します。

はじめに

Speech-to-Text(STT)は、自動音声認識(ASR)とも呼ばれ、YouTubeで行われているような字幕の自動生成や、Siri、Alexa、Cortanaなどのパーソナルバーチャルアシスタントなど、多くのユースケースがある機能で、この素晴らしい機能を利用した音声対応アプリケーションは数多くあります。

VoicePingでは、会議中に私たちが話している間にリアルタイムで文字起こしを表示し、その文字起こしの上にリアルタイムで翻訳を表示することを可能にしています。また、Slackワークスペースの公開または非公開チャンネルに自動的に同期するように設定することで、社内の仕事上の議論で発生したすべてのコミュニケーションを安全に記録することができます。

これらのテープ起こしがSlackに同期されると、会議中に自分に割り当てられたタスクに関連するメッセージをブックマークしたり、会議で話し合われた特定の項目をチームが忘れないようにチャンネルにメッセージを固定したり、会議が早く終わってすべてを話し合うには時間が足りなかった場合にSlackのスレッドで議論を続けるなど、Slackが提供する優れた機能をすべて使用することができます。

インプリメンテーション

音声をテキストに変換できるAIシステムを実装するのは簡単なことではありません。このような簡単な記事のテーマではありませんし、間違いなく一人でできる仕事ではありません。AIモデルを訓練して検証するためには、トランスクリプションを含む膨大な数の音声ファイルが必要になりますし、サポートする異なる言語も増え、やるべきことが増えます。このテーマを掘り下げたいと思っても、今の私にはどこから手をつけていいのかわかりません。

私の運が良ければ、世の中にはたくさんのサービスがあり、これらのサービスの品質は日々向上しています。場合によっては、その精度はプロの人間のテープ起こしに匹敵するものもありますが、あなたのアプリのユーザー数や収益によっては、これはあまりにも高価で維持できないかもしれません。このコストの問題を解決するために、2つの異なるソリューションを提案します。

1つ目の解決策は、最も安価なオプションである、無料サービスの利用です。ほとんどのサービスには、使用量を制限した無料版があり、同時使用時間や使用できる分数が制限されています。無料版だけでは十分でない場合は、有料のSaaSソリューションはありませんが、実験的なWeb Speech APIがあります。Google ChromeやMicrosoft Edgeのように、すでに実装しているブラウザもあります。

最も明らかなのは、実験的なAPIであるため、すべてのブラウザが実装しているわけではないということです。もうひとつの制限はAPI自体にあります。今のところ、使用する入力デバイスを選択する方法はありません。.start()を呼び出すだけで、常にデフォルトの入力デバイスから音声の録音を開始します。

このため、私たちはサードパーティのサービスを選択し、料金を支払い、コストを削減する方法を見つけることになりました。これが次のセッションでお話しする2つ目のソリューションです。

コスト削減

このようなサービスを利用する場合、サーバーに発生するワークロードの量に応じて支払うのが妥当で、Speech to Textソリューションの場合、1.00~1.50米ドル/時間程度の支払いになります。月に8万時間を超えるような非常に大量の使用量の場合は、0.50米ドル/時間程度になることもあります。最悪なのは、音声ストリームや音声ファイルを処理するために送信した場合、その音声に音声が含まれているかどうかは関係なく、彼らはそれを処理しなければならないバイトの束と見なし、それに関連した受信帯域幅のコストが発生することです。

VoicePingでは、5人の参加者で1時間の会議を行う場合、この1回の会議のためだけに5米ドルを支払う必要があります。同じチームが月に6回このようなミーティングを行った場合は、30米ドルになります。参加者が5人ではなく10人の場合は、このチームだけで60米ドルとなります。つまり、このようなチームが10チーム、当社のアプリを利用した場合、1ヶ月で600米ドルになります。しかし、10人の参加者がいる会議では、全員が同時に発言することはなく、ほとんどの場合、1人が発言します。スクラムのレトロスペクティブミーティングに参加したことがあればわかると思いますが、誰も話さないこともあります。スクリーンにカウントダウンタイマーをセットして、全員が数分で最後のスプリントについての良いところと悪いところを書き出すのです。これは、STTの時間の大きな無駄です。

この無駄をなくすには、マイクがミュートになったときにSTTをオフにするのが一番わかりやすい解決策です。しかし、それだけでは十分ではありません。なぜなら、ユーザーが話していないときにマイクをオフにするかどうかに依存しているからです。多くの会議に参加した人は、そんなことは起こらないことを知っています。私たちは常に、話していないユーザーがマイクをオンにしています。発言しないときにマイクをつけておくことが悪いと言っているわけではありません。発言するたびにマイクをつけたり消したりするのは煩わしいですし、騒音を出さない限りはつけていても問題はありません。

ミュート機能は、電話やオフィスの人と話したいときなど、会議中でなくても話したいときに使うものだと思います。VoicePingでは、デフォルトで有効になっている優れたノイズリダクション機能のおかげで、他の参加者に迷惑をかけることなく、会議中ずっとミュートにしておくことができますが、これについては別の記事でご紹介します。VoicePingのノイズリダクションについては、こちら(https://tagdiwalaviral.medium.com/struggles-of-noise-reduction-in-rtc-part-1-cfdaaba8cde7)をご覧ください。

そこで、音声をサービスに送り、サービスが音声をテキストに変換するという大変な作業を行う前に、まずクライアント/ブラウザ上で、Voice Active Detectionというものを使って、音声を前処理します。

コードを見てみましょう。初期設定と期待される結果から始めます。

初期設定

ReactとTypeScriptを使っているので、Reactのフックを使った例を紹介します。また、Azure Speech to Textサービスを使用しているので、例ではAzure APIを使用しますが、使用量を減らすための戦略はどのサービスにも有効です。

カスタムフックuseSpeechToTextを作成しました。後ほどコードを紹介しますが、まずルートコンポーネントの例を示します。

上のコードには、たった2つのことが書かれています。

  1. マイクをミュート/アンミュートするボタン
  2. 認識された音声のリストを

  • パーシャル/インターミディエイトの結果は赤で表示
  • 最終結果は黒で表示

コンポーネントの状態に結果の配列を定義し、showMessage関数は、最後のSTTの結果を配列に挿入するか、部分的な結果の場合は最後の要素を置き換えるだけです。下のビデオの例で、どのように動作するかを見ることができます。ご覧ください。

  • クリックしてミュートを解除した後、テキストを認識するだけです。
  • 開発ツールの「ネットワーク」タブを見ると、ミュートされていないときはデータを送信していることがわかります。

上のコードを見ればわかるように、STTサービスを呼び出すためのロジックはありません。このロジックはすべて useSpeechToText フックの中にあり、以下のコードで確認することができます。

 

細かい点はたくさんありますが、ここで最も重要なのは、データをサービスに送信する場所で、onAudioProcess関数の中、具体的にはpushStream.write(block.bytes);の呼び出し(73行目)で行われます。サービスの使用量を減らすための最も重要な変更点は、この行の周辺にあります。

上のビデオで確認できるように、マイクがミュートされていない場合、サービスに継続的にデータを送信しています。これを修正するために、このフックにVADを統合します。

ボイスアクティブディテクション(VAD

しかし、無音の音声には音声が含まれていませんし、音声が含まれているかどうかを識別することはそれほど複雑ではなく、私たちにもできます。他の人がすでにやっていて、そのオープンソースコードは無料で入手できるので、車輪の再発明をする必要はありません。

私たちは、非常に使いやすいharkパッケージを使うことにして、この再利用可能なフックを作りました。

このコードでは、すでに持っているメディアストリームオブジェクトをパラメータとして受け取り、このストリーム上でharkをインスタンス化し、harkのイベントspeakingとstopped_speakingの両方に、ユーザーが話しているかどうかを示すブール値の状態を設定する関数をバインドします。この状態を利用して、STTのオン/オフを切り替えます。しかし、それほど単純ではなく、いくつかの問題を解決する必要があります。

VADコードのSTTコードへの統合

useSpeechToTextフックの以前のバージョンでは、サービスにデータを送るべきかどうかをチェックするために、このmutedRefリアクトリフを使った以下のコードがありました。関数onAudioProcessは、AzureのSpeechRecognizerインスタンスをインスタンス化するのと同じuseEffectフックの中で定義されているので、Azureインスタンスの再生成を避けるためにrefを使用して、mutedパラメータを直接チェックする代わりに、このrefを使用しました。

VADを統合するために、 streamingFlagRefという新しいフラグを使います。このフラグは、ミュート状態(パラメータ muted)と、 useAudioActiveフックで定義したVADの状態(ここではspeakerActive変数で使用)に基づいています。また running.current を使用して、Azure インスタンスが初期化される前にストリームが発生しないようにしています。

これらの変数はすべて、shouldStream変数のANDブール式で保持しており、その変化はstreamingFlagRef refの値を更新するuseEffectのトリガーとなります。

サービスにデータを送信するためにpushStream.write(block.bytes);を呼び出す際に、if (!mutedRef.current)をチェックするのではなく、if (streamingFlagRef.current)をチェックするようになりました。以上がコードの変更点です。

下のビデオで確認できるように、会話を止めるとデータの送信が停止しますが、新たな問題が発生しました。

上のビデオにあるように、私たちが話すのを止めると、ストリーミングが早く中断されたように見えます。

  • 部分的な結果に甘んじている(レッド・ステートにて
  • 間を置いて再び話すと、a新しい文を始めるのではなく、間がなかったかのように前の文に付加されていきます

これは、前の文の終了と次の文の開始を識別するために、サービスが音声に一時停止を必要とするために起こります。もし、無音の音声を少しでもストリーミングしないと、サーバーは2つの文を一時停止のない1つの連続した文として認識してしまいます(右図の線のように)。

 

連続ストリーミング(VADなし) vs 中断ストリーミング(VADあり

この問題を簡単に解決するには、ストリーミングを停止する前にタイムアウトを発生させる必要がありますが、以下のハイライト行を含めて試してみましょう。

もう一度テストすると、このような結果になります。

見ての通り、ストリーミングを停止する前のタイムアウトは機能しており、各センテンスはうまく終了し、次のセンテンスは沈黙による休止の後、新しいセンテンスとして識別されますが、早口で話すと、センテンスの最初の部分が失われます。これは、私たちが話し始めてから、VADライブラリが何らかの音を検出して再びストリームの送信を開始するまでに、遅延が生じるために起こります。

これは、各文の最初に空のVAD検出遅延があることに注目してください。

 

連続ストリーミング vs 中断ストリーミング(VADのみ) vs 中断ストリーミング(VAD+タイムアウト)の比較

ストリームの送信を開始すると、最初の単語の始まりに関連するオーディオの最初の部分がこの遅延のためにすでに失われており、この部分を送信しませんでした。これは文末の問題と似ていますが、ここではタイムアウトを追加することはできません。過去に実行するためのタイムアウトを設定しなければならないので、不可能なのです。

また、この問題は、VADライブラリが非常に敏感であるという事実によって悪化しています。どんな小さな一時停止でも、hark_stoppedのトリガーとなり、ある種のバウンシング効果によって、ある文の中でストリームが何度も始まったり止まったりすることになります。

ファイナルソリューション

この2つの問題は関連しており、カットオフの問題を抜きにしてバウンシングの問題を示すことは難しいので、両方を同時に処理します。

カットオフの問題を解決するためには、スピーチの最初にタイムアウトを追加しても、スピーチの最後に使用したのと同じ方法では意味がないので、常に最後の2秒間の音声を保持するバッファを追加する必要があります。そのため、harkが音声を検出すると、ライブストリーミングを送信する前に、まずバッファ全体を送信します。遅延時間はそれほど大きくはありませんが、言語によっては、音声の開始前に空白や無音があった方が精度が高くなる場合があるため、2秒としました。

バウンシング効果を修正するために、stop_speaking harkイベントがトリガーされるたびに、2秒間のタイムアウトを開始し、この2秒間が経過した後にのみストリームの送信を停止するようにします。しかし、この間に何らかの音声活動(speaking harkイベント)を検出した場合は、タイムアウトをキャンセルし、音声活動の停止を検出しなかったかのように、ストリームの送信を継続します。

以上が変更点です。

  1. ファイルの先頭にBUFFER_SECONDS定数を追加し、バッファサイズを秒単位で保持しています。
  2. We added bufferBlocks ref at the top of the hook implementation, to hold the buffer array
  3. バッファ配列を保持するために,フック実装の先頭に bufferBlocks ref を追加しました。
  4. onAudioProcessの中で、サービスにオーディオをストリーミングできるかどうかを確認します。

  • 可能であれば、まずバッファを送信し(空ではない場合)、次にライブストリーミングに関連する現在のブロックを送信します。
  • また,最大サイズに達した場合は,バッファの最初の要素を削除して,BUFFER_SECONDS定数で定義したのと同じバッファサイズを維持します。

5. 最後の useEffect クリーンアップ関数内でバッファをクリーンアップし、マイクのミュート/アンミュートに備えて、クリーンなバッファで開始できるようにしています。

その方法を見てみましょう。

音声を検出したときにライブストリームを開始する前に、いつでも最後の数秒間を送信できるようにバッファを使用すると、次のようになります。

 

連続ストリーミング(VADなし) vs 割込みストリーミング(VAD+タイムアウト) vs 割込みストリーミング(VAD+タイムアウト+バッファ

最終的な感想

Chrome開発ツールの「ネットワーク」タブで確認できるように、Azureサービスに送信されるデータは、私たちが話している間だけです。この記事で解決した問題はすべて、VoicePingの開発時に直面した問題と同じです。このコードは、現在運用中のものと全く同じではありませんが、相違点の一つは、createScriptProcessorこれは非推奨のAPIです。) のために AudioWorkletNode

は、Web Workers を使用して、アプリのメインイベントループ内でのオーディオ処理を回避しています。このチュートリアルでは、よりシンプルにするために、スクリプトプロセッサバージョンを表示することにしました。

もう一つのアーキテクチャ上の変更点は、バウンシング効果の処理を streamingFlagRef フラグ値を定義するコードの中に混在させるのではなく、hark の使用をカプセル化した useAudioActive の中に保持することです。私たちのコードでは、 useAudioActiveが複数の場所で使用されていて、他の場所ではバウンシング効果をそのままにしておきたいので、そうしませんでした。

VoicePingのこれらの機能をぜひお試しください。 https://voice-ping.com/

ソースコード

これまでの変更点をすべて適用した最終版です。お読みいただきありがとうございました…

Related Articles