エンコーダディレイについて
動画の音声として、MP3/AAC などのコーデックを使用する場合、「エンコーダディレイ」の存在について知っておく必要があります。
これを意識しておかないと、再生したときに、映像と音声の位置が少しずれる場合があります。
AAC などでエンコードした場合、エンコーダによって、先頭や終端に余分なデータが追加されるため、これをデコードすると、ソースのオーディオに含まれていない無音部分が追加されてしまうので、ソースよりも少し再生時間が長くなります。
これは、オーディオデータを正しくエンコード・デコードするのに必要なデータなので、エンコード後のデータから取り除くことはできません。
そのため、映像と音声を同期させて再生したい場合は、先頭の余分な部分をスキップする必要があります。
これを意識しておかないと、再生したときに、映像と音声の位置が少しずれる場合があります。
AAC などでエンコードした場合、エンコーダによって、先頭や終端に余分なデータが追加されるため、これをデコードすると、ソースのオーディオに含まれていない無音部分が追加されてしまうので、ソースよりも少し再生時間が長くなります。
これは、オーディオデータを正しくエンコード・デコードするのに必要なデータなので、エンコード後のデータから取り除くことはできません。
そのため、映像と音声を同期させて再生したい場合は、先頭の余分な部分をスキップする必要があります。
AAC のエンコーダディレイを確認してみる
まずは、実際に AAC をエンコードしてみて、エンコーダディレイの存在を確認してみましょう。
ffmpeg と fdkaac (package: fdkaac) を使います。
※-f 2 (ADTS) で、生の AAC ファイルを作成します。デフォルトの場合、MP4 コンテナに格納されます。
※ffprobe で詳細な時間を取得したい場合は、"-show_entries format=duration" オプションを追加します。
duration=2.066576 が秒数です。ソースが2秒なので、0.066576 秒増えているのがわかります。
>> 波形の比較
Audacity で、src.wav と conv.wav を並べて比較してみると、conv.wav は先頭に無音部分があるのがわかります。
これが、エンコーダディレイとしての余分な部分です。
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 を出力して、同じことをしてみます。
先程より少ない、duration=2.020136 となっています。
>> 波形の比較
conv.wav では、先頭の無音部分が除外されています。
ただし、終端の無音部分は残っているので、0.020136 秒は終端の余分な部分となります。
※HE-AAC/HE-AAC v2 の m4a の場合は、先頭に中途半端な無音部分が残ります。
生の AAC ファイルの場合は、この情報が含まれていないので (含めることができない)、余分な部分はすべてデコードされているのです。
## 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 コマンドを使うと、また違う結果になります。
>> ffmpeg と faad の波形の比較
faad の方は、ffmpeg で変換した場合と比べて、先頭の無音部分が少しだけ削除されているのがわかります。
これは、faad が先頭の余分なサンプル数を、常に 1024 として除外しているためです。
m4a で iTunSMPB の情報があっても、常に 1024 サンプルが削除されます。
この場合、out.aac 内の実際の先頭サンプル数は 2048 なので、faad でデコードすると、先頭の 1024 サンプルが除外されて、残りの 1024 サンプルが残るという、中途半端な結果となっています。
このように、デコーダやファイル形式によって、先頭の無音部分の取り扱いが異なるので、注意してください。
## 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 エンコード時の余分な部分が、実際にどれだけの長さであるのかを確認してみます。
そこで、AAC エンコード時の余分な部分が、実際にどれだけの長さであるのかを確認してみます。
iTunSMPB
fdkaac で m4a に出力した場合、デフォルトで iTunSMPB のデータが付加されるため、この情報を元に、余分な部分のサンプル数を取得することができます。
※MP4 コンテナにおける情報なので、生の AAC ファイルには付けられません。
out.m4a を出力して、終端のバイナリを確認してみると、以下のような文字があるのが確認できます。
"iTunSMPB...data..." の後に、以下のような数字が並んでいる部分があります。
この文字から、各サンプル数を取得することができます。
数字は16進数なので、10進数に変換すると、以下のようになります。
元のソースは 44100 Hz で2秒なので、全体のサンプル数は 44100 x 2 = 88200 です。
サンプル数を秒数に変換したい場合は、「サンプル数 ÷ サンプルレート (Hz)」で計算できます。
AAC エンコード後のサンプル数は、2048 + 88200 + 888 = 91136 です。
これを秒数に変換すると、91136 / 44100 = 2.06657596372 ということで、ffmpeg で out.aac を WAV に変換した時の duration=2.066576 と一致します。
※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番目) 00000800 | 2048 | 先頭のサンプル数 |
(3番目) 00000378 | 888 | 終端のサンプル数 |
(4番目) 0000000000015888 | 88200 | ソースのサンプル数 |
元のソースは 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 のプロファイルによって、数が固定されています。
※終端のサンプル数は、場合によって変動します。
■ 各エンコーダにおける先頭サンプル数
ffmpeg の場合は、3種類のエンコーダを使うことができます。
※HE-AAC/HE-AAC v2 の場合、iTunSMPB で指定されたサンプル数と、実際のサンプル数は一致しません。
※終端のサンプル数は、場合によって変動します。
■ 各エンコーダにおける先頭サンプル数
fdkaac | |
LC-AAC | 2048 |
---|---|
HE-AAC | 2048 (実際は 5058) |
HE-AAC v2 | 3072 (実際は 7106) |
qaac (ver 2.64) | |
LC-AAC | 2112 |
HE-AAC | 2112 (実際は 5186) |
ffmpeg | |
(-acodec aac) LC-AAC | 1024 (内蔵エンコーダ) エンコード後のデータの先頭に "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 データの場合は、余分な部分の正確なサンプル数は取得できないので、エンコーダを特定してサンプル数を推測するか、映像と音声の差分時間から計算して推測するしかありません。
また、動画の音声として格納されている場合も、生の 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 の波形
>> HE-AAC v2 の波形
fdkaac の HE-AAC の先頭サンプル数は 2048 となっています。
しかし、これを秒数に変換すると、2048 / 44100 = 0.0464399092971 秒 となり、波形の開始位置と一致しません。
確認のため、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
つまり、「(サンプル数 + SBR ディレイサンプル) × 2」で、実際のサンプル数を計算します。
fdkaac の場合は、--include-sbr-delay オプションを付けると、iTunSMPB のサンプル数に、SBR のディレイ値が追加されます。
このオプションを付けて HE-AAC でエンコードした場合、iTunSMPB の値は 0x9E1 (2529) になりました。
ここから元のサンプル数 2048 を引くと 481 になるので、この値が SBR のディレイサンプル数となります。
>> 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 となります。
なお、デコード後の先頭サンプルを除外する時も、映像と音声の結合時にディレイを設定する時も、この値を使うことになります。
HE-AAC v2 : (3072 + 481) * 2 / 44100 = 0.161133786848 秒
このような計算で求めると、ちょうど波形の開始位置あたりと一致します。
これにより、fdkaac でエンコードした時の HE-AAC の実際の先頭サンプル数は 5058、HE-AAC v2 の場合は 7106 となります。
なお、デコード後の先頭サンプルを除外する時も、映像と音声の結合時にディレイを設定する時も、この値を使うことになります。
Opus の場合
Opus の場合は、Ogg コンテナにデータが入っている形となるので、AAC とは少し扱いが異なります。
Opus の場合も先頭にエンコーダディレイが挿入されます。
opusenc でエンコードした後に表示される Preskip の値が、先頭の余分なサンプル数です。
Opus は MP4 でも使うことができます。
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" の文字を探します。
"OpusHead" の後のバイナリデータに各情報がセットされています。
※数値はリトルエンディアン
このように、Opus の場合は、エンコード後のデータに、先頭のスキップするサンプル数の情報が入っているので、デコードされる時は、常にこの情報を使って、先頭のサンプルが除外されます。
ffmpeg で Opus→WAV 変換をしても、先頭に無音部分は存在しません。
また、MP4Box で映像と音声を結合する時も、Opus の場合は自動で遅延情報が設定されます。
そのため、Opus を使う場合は、基本的にエンコーダディレイを気にする必要はありません。
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" の後のバイナリデータに各情報がセットされています。
※数値はリトルエンディアン
uint8 | 0x01 | バージョン |
---|---|---|
uint8 | 0x02 | チャンネル数 |
uint16 | 0x0138 (312) | preskip (先頭のスキップするサンプル数) |
uint32 | 0x0000AC44 (44100) | オリジナルのサンプルレート (Hz) |
このように、Opus の場合は、エンコード後のデータに、先頭のスキップするサンプル数の情報が入っているので、デコードされる時は、常にこの情報を使って、先頭のサンプルが除外されます。
ffmpeg で Opus→WAV 変換をしても、先頭に無音部分は存在しません。
また、MP4Box で映像と音声を結合する時も、Opus の場合は自動で遅延情報が設定されます。
そのため、Opus を使う場合は、基本的にエンコーダディレイを気にする必要はありません。