Steven
Steven1 分で読める

イヤホンのバグ: 相手が文字起こしから消えたとき

デュアルチャンネルの文字起こしは、ノートPCのスピーカーでも、オーバーイヤーヘッドホンでも完璧に動いていました。ところが誰かがイヤホンを挿した途端、話している相手が文字起こしからただ消えてしまったのです — 相手が話し出した瞬間、Chromiumが私たちの音声を黙って無音化していたからでした。十一の修正が失敗しました。十二番目がアーキテクチャそのものを変えました。

エンジニアリング
オーディオ
文字起こし
GeekByeリリース
イヤホンのバグ: 相手が文字起こしから消えたとき

GeekByeは会話の二つの側を文字起こしします: 一方のチャンネルはあなたのマイク、もう一方は相手の音声(あなたのコンピューターが再生している音)です。ノートPCのスピーカーでは完璧に動きました。オーバーイヤーヘッドホンでも動きました。ところがあるユーザーがイヤホンを挿すと、話している相手が文字起こしから消えたのです。文字化けしたのではありません。誤って帰属されたのでもありません。消えたのです。

これはGeekBye v1.6.12の物語、十一の修正が失敗したバグの、そして私たちがブラウザと協力する代わりにブラウザと戦っていたのだと気づいた瞬間の物語です。

生きていて、空っぽだったトラック

その症状は不気味でした。会議を始めると、すべて問題なさそうに見え、あなたが一言しゃべると — その瞬間から、相手の音声チャンネルが純粋なデジタルの無音に落ちるのです。私たちは計測しました: システム音声の信号は正常なエネルギーレベル(RMSでおよそ 0.03–0.06)にあり、マイクがあなたの声を拾った瞬間に 0.0000 へ落ち、セッションの残りの間ずっとそのままでした。

ここが私たちに何日も費やさせた部分です。あらゆるブラウザAPIが、音声トラックは完全に健全だと言い張りました。readyState: liveenabled: truemuted: false。プラットフォームが公開するあらゆるステータスフラグからすれば、ストリームは正常でした。死んでいたのはサンプルのほうだったのです — ゼロしか運んでいない生きたパイプ。公式の話ではトラックは動いていると言うので、私たちは長いあいだ自分たちのエネルギーメーターを信じられずにいました。

引き金はイヤホンに特有で、その理由は物理的なものです: イヤホンは、あなたのマイク入力であると同時にあなたの音声出力でもある一つのデバイスです。 ノートPCのスピーカーやオーバーイヤーヘッドホンはそれらを分離します — 出力は一方へ行き、マイクは別の場所にあります。しかし入力と出力が同じデバイスになると、定義上、フィードバック経路ができます。私たちの強い推論(これはブラウザの内奥にあるので、章と節を引用することはできません)はこうです: Chromiumはこのマイク+スピーカーが一つのデバイス上にある形を検出し、これはハウリングを起こそうとしているフィードバックループだと判断して、保護的にキャプチャをミュートする — 静かに、アプリが読めるどのフラグを通してもアプリに知らせずに。

十一の修正、そのいくつかはより悪かった

私たちはまず明白なやり方で攻めていき、あらゆる試みを書き留めました。両方のストリームでエコーキャンセル、ノイズ抑制、オートゲインを無効にする。二つのストリームを同時にではなく、遅延を挟んで順番に開始する。それぞれのストリームに独自のサンプルレートを与える。無音のゲインノードを追加する。すべてを単一の共有オーディオコンテキストに通す。AudioWorkletへ移す。生のWebCodecsトラックプロセッサーまで下りる。

十一のアプローチ。それらは失敗しただけでなく — いくつかは事態を悪化させました。 一つは30秒の遅延を持ち込みました。別の一つは、あなたのマイクの文字起こしを相手の発言として表示させました。私たちは深い茂みの中にいて、症状を一つずつ手当てし、バグは動き続けました。

突破口はとらえ方の転換でした: これまでの修正はどれも二つの別々のストリームを健全に保とうとしていました。しかし二つの別々のストリームこそ、Chromiumのフィードバックのヒューリスティックが見てミュートできるまさにそのものだったのです。もし比較するものが何もなかったら?

修正: ブラウザに二つではなく一つのストリームを渡す

v1.6.12は戦うのをやめ、アーキテクチャを変えました。マイクとシステム音声を二つの独立したストリームとして送る代わりに、何かがアプリを離れる前に、クライアント側でそれらを一つのモノラルストリームにミックスするのです。両方のソースが一つのパイプラインを流れると、トラックは独立して死ねません — ブラウザが検出してミュートできる二デバイスのフィードバックの形がもはや存在しないからです。引き金となる条件そのものが消え去ります。

うれしいおまけがありました。二つのストリームは二つのクライアント〜バックエンド間のWebSocketと二つのバックエンド〜プロバイダー間の接続を意味していました。一つのミックスされたストリームはそれを一つと一つにまとめました。イヤホンのバグを直したことで、副作用として文字起こしのコストが半分になったのです。

しかしモノラルミックスには明白な問題があります: 二人を一つのチャンネルに混ぜてしまったら、どうやって誰が何を言ったかを知り続けるのか? あなたはそれを教えてくれていた分離そのものを捨ててしまったのです。

エネルギーから「誰が話したか」を作り直す

答えは、ミックスするに各ソースの音量を計測し、どのソースが優勢だったか — マイク、システム、または無音 — を告げるヒントを音声のチャンクごとに付けることでした。より大きいソースが勝ちます。スピーカーなら十分に単純です。

イヤホンにはもう一層が必要で、ここでエコーが物語に戻ってきます — しかもあなたが予想するのとは逆の方向を指して。イヤホンでは、相手の声がイヤホンから漏れてマイクへ戻ってきます。だから注意しないと、あなたのマイクチャンネルに到達した相手のエコーが、あなたの発言として帰属されてしまいます。修正はエコー対応のしきい値の集まりです: システム音声が小さな下限を超えて再生されているとき、あなたのマイクが「あなたが話している」と数えられるために超えなければならない基準がぐっと跳ね上がります — 通常の 0.015 から 0.1 へ。言い換えれば: 相手が話している間、あなたのマイクは、その言葉をあなたの発言として認める前に、相手のエコーより明らかに、疑いなく大きくなければならないのです。曖昧なフレームは、エコーだと想定して既定で「システム」になります。これは意図的なバイアスで、かつて間違えていたまさにその状況で帰属を正しくします。

その概念実証 — モノラルミックスに加えて、エネルギーベースでエコー対応の音声検出 — が、GeekByeが今日動かしている帰属システムの祖先です。足場は、それが自らを証明したのちに同じリリース内で取り払われ、エネルギーの計算は後にきちんとテストされたモジュールへと固まりました。しかしv1.6.12こそ、そのアイデアが、切迫の中で、一組のイヤホンを持ったユーザーによって生まれた場所なのです。

イヤホンのバグが教えた三つのこと

  1. 健全なハンドルが死んだデータを運びうる。 調査全体は、私たちのエネルギーメーターをプラットフォームのステータスフラグより信じたことにかかっていました — liveenabledunmuted、すべてが嘘をつき、サンプルはゼロでした。APIの自己申告ではなく、ペイロードを監視しなさい。ストリームが正常だと主張するなら、実際に正常かどうかを計測しなさい。
  2. プラットフォームがあなたのアーキテクチャと戦うなら、プラットフォームではなくアーキテクチャを変えよ。 私たちはChromiumのフィードバック保護を論破できず、その周縁で無効にしようとするあらゆる試みは失敗するか裏目に出ました。観測可能な条件 — 二つのストリームが一つになること — を取り除くと、問題全体が消えました。ブラウザとの戦いに勝つより、引き金を取り除くほうが、ほとんど常に安上がりです。
  3. 堅牢さのためにチャンネルを畳むなら、破壊したものを作り直さなければならない。 モノラルへのミックスは「誰が話したか」を運んでいたチャンネル分離を殺しました。その情報は、ミックス前のエネルギーとエコー対応のしきい値から再導出しなければなりませんでした。埋め合わせのロジックは修正の後付けではありません — それが修正の半分そのものなのです。

これは、GeekBye v2になる信頼性物語の第四章です。前の章は、接続が切れただけでアプリ全体が落ちてはいけない(v1.6.8)を。この音声作業が土台にしたパイプラインの書き直しは、音声コードを5,000行削除したら(v1.6.0)を。そして全体の弧については、ソフトウェアを完璧に出荷することの解剖学をご覧ください。

関連記事

音声コードを5,000行削除したら — 文字起こしが二重に出始めた
Steven
Steven1 分で読める

音声コードを5,000行削除したら — 文字起こしが二重に出始めた

GeekBye v1.6は、オンデバイスの二つのSwift文字起こしエンジンを引き剥がし、一つの統一パイプラインに置き換えました — 差し引き5,000行超の削除を、人々がアプリで会議の真っ最中に、です。すると文字起こしが二重に現れ始めました。バグが、まだエンジンは二つあると思い込んでいた唯一の層へ移動したからです。

エンジニアリング
文字起こし
アーキテクチャ
ファイアウォールがWebSocketを塞ぐとき、リアルタイム文字起こしはどうなるか
Steven
Steven1 分で読める

ファイアウォールがWebSocketを塞ぐとき、リアルタイム文字起こしはどうなるか

企業ネットワークはHTTPSは通すくせに、WebSocketのアップグレードをこっそり殺すのが大好きです。それがリアルタイム文字起こしを黙って壊します。GeekBye v2.0.8は自動でピュアHTTPSトランスポートへフォールバックする — そしてそれを出荷する過程で、機能全体を無意味にしかねないバグが見つかりました。

文字起こし
ネットワーク
エンジニアリング
AI文字起こしはなぜ専門用語を聞き間違えるのか(そして私たちはどう直したか)
Steven
Steven1 分で読める

AI文字起こしはなぜ専門用語を聞き間違えるのか(そして私たちはどう直したか)

ライブセッションで「what is the pointer in C++」が「what is the point in life」と聞き取られました。そのトランスクリプトからGeekBye v2.0.11に至るまでの調査記録 — keytermバイアス、接続を切断していたタイミング競合、そして自分たちの修正が裏目に出た日の話です。

文字起こし
信頼性
エンジニアリング