リアルタイム音声処理を題材に、Kotlin Flowのバッファリングと実行モデルを改めて深掘りする
tkcREALITY, Android Engineer
私はライブ配信アプリのBGM音声処理を、手動のMediaCodecループからKotlin Flowベースのパイプラインに書き換えました。Producer(MediaCodecによるデコード)、Intermediate(フレーム整形・チャンネル変換・再生位置追跡)、Consumer(ローカル再生と配信送信への分配)という3種類のクラスを定義し、Flowオペレータでチェーン状に合成する宣言的な構成です。設計は綺麗にまとまり、コードの見通しも大幅に良くなりました。 しかし、この実装にした結果、不具合が次々と判明します。「BGMが突然倍速で再生される」「送信音声に定期的にノイズが乗る」「特定機種で音声再生にノイズが入る」「特定機種でBGMが途中で止まる」——これらを解決しないとリリースには踏み切れない状況でした。 最初は何が起きているのか見当もつかず、AudioTrackに渡されるデータから処理段階を一つずつ遡って原因を追っていきました。その結果、これらの不具合の中にはAndroidプラットフォーム側に起因するものも含まれていましたが、いくつかの問題は、shareInやChannelの内部バッファリングを「雰囲気で」使っていたために、上流が下流より先に進み、リングバッファのデータが書き換わる、というFlowの実行モデルに直結する問題でした。本セッションでは、後者——Flowの理解不足が招いた問題に焦点を当てます。 ひとつ目は、shareInでConsumerを分岐した途端、BGMが倍速で再生されるという現象です。本セッションでは、shareInが内部に持つバッファの挙動と、Subscriberの処理速度に関係なく上流が進んでしまう条件を解き明かし、私がどう対処したかを解説します。 ふたつ目は、Channelを経由する箇所で発生した音飛びです。特定端末でのみ発生するバグでした。本セッションでは、AudioTrackの内部バッファサイズが端末のAudio HAL実装に依存することで、フレーム処理が想定より速く進む現象、それに伴ってパイプライン全体が高速に回ること、Channelのsend()とtryReceive()のタイミングのギャップなど、複数の要因がどう絡んだかを解き明かし、私がどう対処したかを解説します。 本セッションでは、この調査と修正の過程を題材に、Kotlin Flowのバッファリングと実行モデルを深掘りします。shareInの内部バッファ、Channelのsend / receiveのタイミング、AudioTrackなど下流のバッファサイズが端末依存である事実——これらを「雰囲気で」使うのではなく、データがどこで保持され、いつ参照され、いつ書き換わりうるかを意識して設計するための具体的な判断基準を、一次情報として共有します。Flowを「リアルタイム性が要求される領域」で安全に使うために必要な視点を持ち帰っていただけます。
対象者
・Kotlin Coroutines / Flow を業務コードで日常的に使っている方 ・Coroutines や Flow の挙動を雰囲気で使っている自覚のある方 ・音声 / 映像 / センサーなどリアルタイム処理で Flow を使おうとしている方 ・Android の音声処理スタック(AudioTrack / Audio HAL)に興味がある方 ・特定端末でのみ再現するような、原因特定が難しいバグの調査に興味がある方