5秒の素材が、気づけば9秒になるまで
音声と映像の同期は、派手さはないのに壊れるとすぐ分かる。蝶にリンゴが当たる場面で、音だけが少し遅れて聞こえる。人間は映像より音が遅れるぶんには、だいたい125ミリ秒までは気づきにくいとされるが、変換を何度も重ねると、その「少し」が積み上がって無視できなくなる。
Mediabunny の検証では、同じ素材を 64 回、入力の出力を次の入力に回す形で変換し続けると、音声は 2 秒以上遅れた。しかも総尺も延びた。最初は約 5 秒の素材が、最後には約 9 秒近くになっていたという。単発の変換では見過ごせるずれが、繰り返し処理で顕在化したわけだ。
ここで厄介なのは、変換パイプラインのどこかで「明らかなバグ」を踏んだようには見えないことだ。読み込み、デコード、再エンコード、再パッケージングという流れ自体は素直で、タイムスタンプも勝手にはいじっていない。それでも同期が崩れるなら、原因はもっと深い層、つまりコーデックが内部で抱える前提にあるはずだ。
音声コーデックは、映像のように「このフレームから見ればいい」という単純さがない。圧縮の都合で、入力の先頭に少し余分な無音を足したり、終端に端数を抱えたりする。普段は気づかないが、編集や再生のたびにその前提が剥き出しになる。AAC では、この問題が特に分かりやすい形で現れる。
先頭に足される無音は、ただの無駄ではない
Apple の古い技術ノートでは、AAC のエンコード遅延は実装依存だとしつつ、当時の一般的な値として 2112 サンプルが挙げられている。AAC は通常 1024 サンプル単位で扱われるので、2112 は 1024 + 1024 + 64 という内訳になる。最初の 2112 サンプルは、実際の音声ではなく、圧縮の準備に必要な「先頭の余白」だ。
この余白は、単に無駄を入れているわけではない。AAC は MDCT という変換を使い、隣り合うブロック同士を重ね合わせて正しい波形を再構成する。そのため、最初のブロックだけ見ても完全な音は出せない。先頭の数ブロックは、いわばウォームアップ期間になる。だから、エンコーダが入力の先頭に無音を足すのは自然だ。
問題は、その無音を足したことそのものではない。足した事実を、後続の処理系が「知らない」まま扱うことだ。Apple のノートにも、再生側は 2112 サンプルを捨てて同期を保つ必要があるとある。逆に言えば、そこを捨て忘れると音は映像より 2112 サンプルぶん後ろにずれる。48 kHz なら、ちょうど 44 ミリ秒になる。
W3C の WebCodecs まわりでも、この点は長く問題視されてきた。issue #626 では、AAC の priming samples と padding を、エンコーダが返さないためにコンテナへ正しく載せにくいと指摘されている。音楽用途では、ループが 1 サンプルずれるだけでも違和感になる。映像の見た目より、音の境目のほうがシビアな場面は多い。
WebCodecs が黙っていると、何が起きるのか
Mediabunny のブログが面白いのは、WebCodecs を「高品質なブラウザ API だから安心」とは見なしていない点だ。むしろ、ブラウザ実装が完全であるという前提こそ危うい、と示している。AAC のエンコードで見えたのは、出力が少し長くなり、しかもその分だけ先頭側に遅れが入ることだった。長さだけでなく、時刻もずれる。
実験では、1 秒の単純なテスト信号を AAC で通すと、最後のパケットが元信号より少し先まで伸びた。さらに復号した波形を比べると、元信号に対して 44 ミリ秒の遅れが最もよく一致した。48 kHz 換算で 2112 サンプル。ここで重要なのは、数字がたまたまそれっぽいのではなく、Apple の古い資料が示していた値と一致したことだ。
ただし、実装の世界はきれいには揃わない。Mediabunny の観測では、環境によっては 1024 サンプルの場合もあり、Safari では別の事情が重なって 3136 サンプル相当になるケースまであるという。つまり「AAC なら 2112」と決め打ちしたい気持ちは分かるが、決め打ちでは外れる。どこかの時点で、実際のエンコーダが何をしているかを測る必要が出てくる。
W3C の会議でも、同じ問題意識は見えている。2026 年 9 月の Media WG では、「最初の可聴サンプルを時刻 0 にするべきだ」という方向性が語られている一方で、AAC では仕様の表面からだけでは足りず、コーデック内部を見ないと分からない、という認識が共有されていた。標準があるだけでは、現場のずれは消えない。
「2112」を知っているだけでは足りない理由
ここまで読むと、2112 サンプルを引けばいいだけに見える。実際、Apple の文書はそうした固定値の発想を支えてきたし、W3C issue #626 でも AAC は 2112 フレームが頻出すると書かれている。だが、固定値は便利な反面、実装差分の前ではあっさり崩れる。ブラウザごと、OS ごと、時には同じブラウザでも条件次第で変わる。
Mediabunny が採った方向も、その固定化ではない。ブログでは、インパルス信号を 4096 サンプル目に置いてエンコーダを往復させ、ピークがどこに現れるかを測る方法が紹介されている。戻ってきた信号のピークが 6208 番目に出たなら、4096 を引いた差分が遅延量になる。ここで初めて、環境固有の遅れが「見える化」される。
このやり方の肝は、AAC がどう理屈を説明しているかではなく、実際にその場で何サンプルずれるかを確かめる点にある。理論値が正しいことと、実装がその通り動くことは別だ。WebCodecs の AAC でも、仕様上は扱えるはずの情報が表に出てこない。だから、外から見えるタイムスタンプだけでは足りず、後から補正する仕組みが要る。
W3C の issue #626 では、EncodedAudioChunk 側に遅延や padding の情報を持たせる案も議論されている。もしそれが綺麗に揃えば、音声と映像の比較はずっと簡単になる。だが現状は、実装がそれを教えてくれない。だからこそ、Mediabunny はまず測る。その先で、負のタイムスタンプをどう扱うか、どこで切るかが本題になる。
それが分かれば、64 回変換しても 2 秒遅れにはならないはずだ。だが、その「分かれば」の中身は、まだ仕様にも実装にも埋まっていない。では、どうやって毎回同じ補正値を得るのか。実際にどこで 2112 を見つけ、どう同期を戻すのか。そこから先が、まだ開いている。
👉 続き(核心はここから)は PaPoo で読めます: https://papoo.work/doc/311fe273b298c1bc
