音声のエンコーダディレイ (1) - 基本

エンコーダディレイについて
動画の音声として、MP3/AAC などのコーデックを使用する場合、「エンコーダディレイ」の存在について知っておく必要があります。
これを意識しておかないと、再生したときに、映像と音声の位置が少しずれる場合があります。

AAC などでエンコードした場合、エンコーダによって、先頭や終端に余分なデータが追加されるため、これをデコードすると、ソースのオーディオに含まれていない無音部分が追加されてしまうので、ソースよりも少し再生時間が長くなります。

これは、オーディオデータを正しくエンコード・デコードするのに必要なデータなので、エンコード後のデータから取り除くことはできません。
そのため、映像と音声を同期させて再生したい場合は、先頭の余分な部分をスキップする必要があります。
AAC のエンコーダディレイを確認してみる
まずは、実際に AAC をエンコードしてみて、エンコーダディレイの存在を確認してみましょう。
ffmpeg と fdkaac (package: fdkaac) を使います。

AAC エンコード
44100 Hz、2秒の src.wav を作成して、LC-AAC で out.aac に変換してみます。

## ノコギリ波作成
$ ffmpeg -f lavfi -i "aevalsrc=t*440-floor(t*440)|t*440-floor(t*440):s=44100:d=2" src.wav

## LC-AAC 128kbps でエンコード (ADTS)
$ fdkaac -b 128 -f 2 -o out.aac src.wav

-f 2 (ADTS) で、生の AAC ファイルを作成します。デフォルトの場合、MP4 コンテナに格納されます。

AAC→WAV 変換
出力した out.aac を conv.wav に変換して、ffprobe コマンドで時間を確認してみます。
※ffprobe で詳細な時間を取得したい場合は、"-show_entries format=duration" オプションを追加します。

## AAC->WAV 変換
$ ffmpeg -i out.aac conv.wav

## 再生時間を表示
$ ffprobe -show_entries format=duration conv.wav
...
  Duration: 00:00:02.07, bitrate: 1411 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels, s16, 1411 kb/s
[FORMAT]
duration=2.066576
[/FORMAT]

duration=2.066576 が秒数です。ソースが2秒なので、0.066576 秒増えているのがわかります。

>> 波形の比較

Audacity で、src.wav と conv.wav を並べて比較してみると、conv.wav は先頭に無音部分があるのがわかります。
これが、エンコーダディレイとしての余分な部分です。
M4A で出力してみる
先程は生の AAC データで出力しましたが、今度は MP4 コンテナで out.m4a を出力して、同じことをしてみます。

## MP4 コンテナ出力
$ fdkaac -b 128 -o out.m4a src.wav

## AAC->WAV 変換
$ ffmpeg -i out.m4a conv.wav

## 再生時間表示
$ ffprobe -show_entries format=duration conv.wav
...
  Duration: 00:00:02.02, bitrate: 1411 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels, s16, 1411 kb/s
[FORMAT]
duration=2.020136
[/FORMAT]

先程より少ない、duration=2.020136 となっています。

>> 波形の比較

conv.wav では、先頭の無音部分が除外されています。
ただし、終端の無音部分は残っているので、0.020136 秒は終端の余分な部分となります。

なぜこのような違いが起こるのか
out.aac と out.m4a の出力の違いで、なぜこのようなことが起こるかと言うと、fdkaac で m4a 出力を行った場合、エンコーダディレイのサンプル数の情報 (iTunSMPB) が、ファイルの終端の方に格納されているので、ffmpeg が m4a をデコードする時、入力ファイルに iTunSMPB の情報があった場合は、自動で先頭のサンプルを除外してデコードするためです。

※HE-AAC/HE-AAC v2 の m4a の場合は、先頭に中途半端な無音部分が残ります。

生の AAC ファイルの場合は、この情報が含まれていないので (含めることができない)、余分な部分はすべてデコードされているのです。
faad コマンドでデコード
ちなみに、AAC→WAV 変換時に、ffmpeg ではなく faad コマンドを使うと、また違う結果になります。

## out.aac -> WAV 変換
$ faad -o conv.wav out.aac

## 再生時間表示
$ ffprobe -show_entries format=duration conv.wav
...
  Duration: 00:00:02.04, bitrate: 1411 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels, s16, 1411 kb/s
[FORMAT]
duration=2.043356
[/FORMAT]

>> ffmpeg と faad の波形の比較

faad の方は、ffmpeg で変換した場合と比べて、先頭の無音部分が少しだけ削除されているのがわかります。

これは、faad が先頭の余分なサンプル数を、常に 1024 として除外しているためです。
m4a で iTunSMPB の情報があっても、常に 1024 サンプルが削除されます。

この場合、out.aac 内の実際の先頭サンプル数は 2048 なので、faad でデコードすると、先頭の 1024 サンプルが除外されて、残りの 1024 サンプルが残るという、中途半端な結果となっています。

このように、デコーダやファイル形式によって、先頭の無音部分の取り扱いが異なるので、注意してください。
エンコーダディレイのサンプル数
エンコードした AAC には、ソースには存在しない余分な無音部分が存在するということがわかりました。
そこで、AAC エンコード時の余分な部分が、実際にどれだけの長さであるのかを確認してみます。
iTunSMPB
fdkaac で m4a に出力した場合、デフォルトで iTunSMPB のデータが付加されるため、この情報を元に、余分な部分のサンプル数を取得することができます。
※MP4 コンテナにおける情報なので、生の AAC ファイルには付けられません。

## LC-AAC エンコード
$ fdkaac -b 128 -o out.m4a src.wav

out.m4a を出力して、終端のバイナリを確認してみると、以下のような文字があるのが確認できます。

00008608: 69 54 75 6E 53 4D 50 42 00 00 00 84 64 61 74 61 iTunSMPB····data
00008618: 00 00 00 01 00 00 00 00 20 30 30 30 30 30 30 30 ········ 0000000
00008628: 30 20 30 30 30 30 30 38 30 30 20 30 30 30 30 30 0 00000800 00000
00008638: 33 37 38 20 30 30 30 30 30 30 30 30 30 30 30 31 378 000000000001
00008648: 35 38 38 38 20 30 30 30 30 30 30 30 30 20 30 30 5888 00000000 00
00008658: 30 30 30 30 30 30 20 30 30 30 30 30 30 30 30 20 000000 00000000 
00008668: 30 30 30 30 30 30 30 30 20 30 30 30 30 30 30 30 00000000 0000000
00008678: 30 20 30 30 30 30 30 30 30 30 20 30 30 30 30 30 0 00000000 00000
00008688: 30 30 30 20 30 30 30 30 30 30 30 30             000 00000000

"iTunSMPB...data..." の後に、以下のような数字が並んでいる部分があります。

00000000 00000800 00000378 0000000000015888 ...

この文字から、各サンプル数を取得することができます。
数字は16進数なので、10進数に変換すると、以下のようになります。

16進数10進数意味
(2番目) 000008002048先頭のサンプル数
(3番目) 00000378888終端のサンプル数
(4番目) 000000000001588888200ソースのサンプル数

元のソースは 44100 Hz で2秒なので、全体のサンプル数は 44100 x 2 = 88200 です。

サンプル数を秒数に変換したい場合は、「サンプル数 ÷ サンプルレート (Hz)」で計算できます。

AAC エンコード後のサンプル数は、2048 + 88200 + 888 = 91136 です。

これを秒数に変換すると、91136 / 44100 = 2.06657596372 ということで、ffmpeg で out.aac を WAV に変換した時の duration=2.066576 と一致します。
エンコーダによる違い
余分な部分の先頭のサンプル数は、AAC エンコーダや AAC のプロファイルによって、数が固定されています。
※終端のサンプル数は、場合によって変動します。

■ 各エンコーダにおける先頭サンプル数

fdkaac
LC-AAC2048
HE-AAC2048 (実際は 5058)
HE-AAC v23072 (実際は 7106)
qaac (ver 2.64)
LC-AAC2112
HE-AAC2112 (実際は 5186)
ffmpeg
(-acodec aac) LC-AAC1024 (内蔵エンコーダ)
エンコード後のデータの先頭に
"Lavf60.3.100" というようなバージョンの文字がある。
(-acodec libfdk_aac)fdkaac と同じ。
"LavfXX.X.XXX" の文字はない。
(-acodec aac_at)macOS のみ。未確認

ffmpeg の場合は、3種類のエンコーダを使うことができます。

※HE-AAC/HE-AAC v2 の場合、iTunSMPB で指定されたサンプル数と、実際のサンプル数は一致しません。
生の AAC データで確認するには?
生の AAC データの中には、iTunSMPB のような情報は含まれていないので、正確なサンプル数は判断できません。
また、動画の音声として格納されている場合も、生の AAC だけが格納されている状態なので、同様です。

生の AAC データの場合は、余分な部分の正確なサンプル数は取得できないので、エンコーダを特定してサンプル数を推測するか、映像と音声の差分時間から計算して推測するしかありません。
HE-AAC/HE-AAC v2
LC-AAC の場合は、iTunSMPB で指定されたサンプル数を使えば問題ありませんが、HE-AAC/HE-AAC v2 の場合は、iTunSMPB で指定されたサンプル数と実際の波形を比べると、明らかに指定サンプル数よりも無音部分の方が長くなっています。

確認のため、HE/HEv2 でエンコードして、WAV 変換してみます。
※m4a で出力すると、ffmpeg が iTunSMPB のサンプル数分を除外するので、ADTS で出力してください。

## HE-AAC
$ fdkaac -p 5 -b 64 -f 2 -o out_he.aac src.wav

## HE-AAC v2
$ fdkaac -p 29 -b 32 -f 2 -o out_he2.aac src.wav

## AAC->WAV 変換
$ ffmpeg -i out_he.aac conv_he.wav
$ ffmpeg -i out_he2.aac conv_he2.wav

>> HE-AAC の波形
>> HE-AAC v2 の波形

fdkaac の HE-AAC の先頭サンプル数は 2048 となっています。
しかし、これを秒数に変換すると、2048 / 44100 = 0.0464399092971 秒 となり、波形の開始位置と一致しません。
実際のサンプル数の求め方
以下で書かれている内容にヒントがありそうです。

>> https://hydrogenaud.io/index.php/topic,107952.0.html

  • HE/HEv2 は、SBR のディレイが 481 サンプルあるので、この値を追加する。
  • HE/HEv2 は、SBR によって、サンプルレートがソースの半分の値になっているため、最終的にサンプル数を2倍にする。

つまり、「(サンプル数 + SBR ディレイサンプル) × 2」で、実際のサンプル数を計算します。

fdkaac の場合は、--include-sbr-delay オプションを付けると、iTunSMPB のサンプル数に、SBR のディレイ値が追加されます。

このオプションを付けて HE-AAC でエンコードした場合、iTunSMPB の値は 0x9E1 (2529) になりました。
ここから元のサンプル数 2048 を引くと 481 になるので、この値が SBR のディレイサンプル数となります。
実際に計算してみる (fdkaac の場合)
HE-AAC : (2048 + 481) * 2 / 44100 = 0.114693877551 秒
HE-AAC v2 : (3072 + 481) * 2 / 44100 = 0.161133786848 秒

このような計算で求めると、ちょうど波形の開始位置あたりと一致します。
これにより、fdkaac でエンコードした時の HE-AAC の実際の先頭サンプル数は 5058、HE-AAC v2 の場合は 7106 となります。

なお、デコード後の先頭サンプルを除外する時も、映像と音声の結合時にディレイを設定する時も、この値を使うことになります。
Opus の場合
Opus の場合は、Ogg コンテナにデータが入っている形となるので、AAC とは少し扱いが異なります。

Opus の場合も先頭にエンコーダディレイが挿入されます。
opusenc でエンコードした後に表示される Preskip の値が、先頭の余分なサンプル数です。

$ opusenc --bitrate 128 src.wav out.opus

Encoding using libopus 1.4 (audio)
-----------------------------------------------------
   Input: 44.1 kHz, 2 channels
  Output: 2 channels (2 coupled)
          20ms packets, 128 kbit/s VBR
 Preskip: 312

Encoding complete                                
-----------------------------------------------------
       Encoded: 2.02 seconds
       Runtime: 0 seconds
         Wrote: 43100 bytes, 101 packets, 5 pages
       Bitrate: 166.234 kbit/s (without overhead)
 Instant rates: 156.4 to 254.8 kbit/s
                (391 to 637 bytes per packet)
      Overhead: 2.61% (container+metadata)

Opus は MP4 でも使うことができます。
サンプル数の確認
動画に入っている Opus 音声から、この Preskip のサンプル数を取得したい場合は、動画ファイルから音声を抽出した後、バイナリエディタで先頭の "OpusHead" の文字を探します。

00000000: 4F 67 67 53 00 02 00 00 00 00 00 00 00 00 C8 79 OggS···········y
00000010: B6 30 00 00 00 00 06 5B 54 CF 01 13 4F 70 75 73 ·0·····[T···Opus
00000020: 48 65 61 64 01 02 38 01 44 AC 00 00 00 00 00 4F Head··8·D······O
                      == -- ===== -----------

"OpusHead" の後のバイナリデータに各情報がセットされています。
※数値はリトルエンディアン

uint80x01バージョン
uint80x02チャンネル数
uint160x0138 (312)preskip (先頭のスキップするサンプル数)
uint320x0000AC44 (44100)オリジナルのサンプルレート (Hz)

このように、Opus の場合は、エンコード後のデータに、先頭のスキップするサンプル数の情報が入っているので、デコードされる時は、常にこの情報を使って、先頭のサンプルが除外されます。

ffmpeg で Opus→WAV 変換をしても、先頭に無音部分は存在しません。

また、MP4Box で映像と音声を結合する時も、Opus の場合は自動で遅延情報が設定されます。

そのため、Opus を使う場合は、基本的にエンコーダディレイを気にする必要はありません。