d:id:RobinEgg:20090402:p1 で書いたようなワンセグに限らず、通常のフルセグでも TS ファイルを分割して取得した AAC ファイルを faad/ffmpeg で読み込ませようとするとコアダンプを吐いたりエラーが出て処理ができなかった理由について。
特に、 ffmpeg では
[libfaad @ 0x1538250]faac: frame decoding failed: Bitstream value not allowed by specification [libfaad @ 0x1538250]faac: frame decoding failed: Invalid number of channels Error while decoding stream #0.0 ...
等というエラーが大量にはき出され、全く使い物になっていませんでした。
これは、放送波上の AAC データには、 raw data の前に ADTS ヘッダが付加されていることが多いのですが、この「ヘッダ・データ」の構造が正常に維持されず、ファイルがデータ部の途中から開始していると、ファイルの先頭に ADTS ヘッダが来ず、いきなりデータ部から始まるということになってしまい、 ADTS ヘッダのパースが正常にできないためにエラーが出力される、ということです。こうなると多くの再生・変換ソフトはお手上げで、軒並みエラーを吐いて死んでしまうため*1、ファイルの破損なのか、それともコーデック側の問題なのか、の切り分けが非常に困難なものとなってしまいます。一度 VLC や Winamp を経由して wave ファイルに落とし込んでやれば特に問題ないのですが、如何せん面倒くさいのでどうにか自動的に処理をさせたいところ。
ちなみに、 AAC の ADTS ヘッダの構造は、
なんちゃって記 |FAAD2を使ってAAC再生ソフトを作る(その1)
というようになっているらしい。
手元にあるワンセグのTSファイルから抽出した AAC データの ADTS ヘッダは以下のようになっている:
FF | F8 | 58 | 80 | 1F | E2 | 24 | 0C | AF | A0 |
11111111 | 11111000 | 01011000 | 10000000 | 00011111 | 11100010 | 00100100 | 00001100 | 10101111 | 10100000 |
これを上記の表と比較すると、「MPEG2-LC/保護なし/2ch/24000Hz/オリジナル」であるということが判る。赤で示した部分が AAC のデータ部分。
同様に、フルセグ(2ch)では:
FF | F8 | 4C | 80 | 55 | E1 | 3C | F1 | D5 | 21 |
11111111 | 11111000 | 01001100 | 10000000 | 01010101 | 11100001 | 00111100 | 11110001 | 11010101 | 00100001 |
「MPEG2-LC/保護なし/2ch/48000Hz/オリジナル」となっている。おそらく 5.1ch では
FF F8 4D 80...
となるものと思われます。ワンセグにしろ、フルセグにしろ、
- MPEG2-LC
- Layer 値が一定
- 保護(DRM)はかかっていない
- コピーフラグがない
なので、先頭は
FF F8
もしくは
FF F8 xx 80
で統一できるのではないかと。後者の場合、 aac_frame_length の値次第でビット値が変化しうるけれど、とりあえず手元にあるいくつかのデータでは全て一定値となっていたので、変化した場合はその時に考えるということで。なお、保存した AAC ファイルの先頭がこれらのビット値から始まっていなければ、その AAC ファイルは壊れたものと見なされていると思われます。
参考
519 :名無しさん◎書き込み中[sage]:2008/02/09(土) 16:56:03 ID: OhlfZXWj Mpeg2-AACのデータは現在サンプルをとっている限りでは 2ch「FF F8 4C 80」 5.1ch「FF F8 4D A0」のようになっていて、1ブロックごとに同じヘッダが付いています。 これが、1秒間に約46.88個並んでいます ※新たなヘッダ情報も出てくるかもしれないので、ヘッダ解析プログラムも作っておきます。 擬似WAVE作成はこれを1ブロック毎にデータ内に時間軸に合わせて配置するだけです。 ただし、ext_bsで抽出できる擬似WAVEとは異なるので、抽出プログラムも自分で作る必要があります。 AviUtlで使用するならば、再圧縮無しWAVE出力はまったく化けないので、 少々プログラムが書ける人ならば、一日で書けてしまうくらいの簡単なプログラムで終わります。 MainConcept Mpeg HD PlugIn のPCM出力の場合は、おそらく1バイトおきに1ビットずつ化けています。 これを考慮してプログラムを作るのが、なかなか厄介なのです。【BDAV】BD Rip以降の行程を楽しむスレ Vol.2【AACS】
5.1ch「FF F8 4D A0」というのはコピーフラグが立っているから A0 になっているのか。
とりあえず書いてみた
非常に単純なコード。
上述の、
FF F8 xx 80
を検出し*2、検出点から終端までをそっくりそのままコピーしています。先頭2バイト FF F8 だけのチェックならもう少し簡単で済みますが、一応余白を取るような感じにしています。
AAC ファイルはそこまで大きくならない(100MB超えることは滅多にないと思う)ので、一気に読み出して変数に格納 → 一気に書き出し、をしようかと思ったけど、どうも某スレでは潤沢にメモリを積んだ環境のあるユーザーは少数なような気がするので、逐次 100KB ずつ読み込んで書き出し、を繰り返してます。50MB 位のファイルを指定して、メモリ増分は2,3MB程度かな。
特に理由はないけど Python で。やってることは非常に簡単なことなので、他言語への移植も簡単だと思う。移植はどうぞご自由に。なお、指定されたファイルが本当に AAC なのか、とかのチェックはしていません。その辺は臨機応変に。あまり綺麗なアルゴリズムじゃないのはご容赦。
#!/usr/bin/python # -*- coding:utf-8 -*- # # ADTC ヘッダ開始の合図である FF F8 xx 80... を、壊れた AAC ファイルから探し出し、 # 以後のデータをそっくりそのまま書き出しファイルへコピーする # # 2009 (C) http://d.hatena.ne.jp/RobinEgg/ # # Suppried with MIT License # import os, sys if __name__ == '__main__': arg = sys.argv if (len(arg) != 2): print('Usage: $ python %s AACFile' % sys.argv[0]) exit() # 読み出し AAC ファイル f = os.path.abspath(arg[1]) splitExt = os.path.splitext(f) if (splitExt[1] != '.aac'): print("指定されたファイルは AAC ファイルではないようです。\nMPEG4 等のコンテナに含まれている場合は ffmpeg 等で\nraw AAC に変換してから処理を行ってください。\n念のため処理を中止します。") exit() # 出力ファイル outfile = splitExt[0] + '-truncated.aac' try: fr = open(f, 'rb') except IOError as erMsg: print("読み込みファイルを開けませんでした。\n" + str(erMsg)) exit() # ファイルポインタ fp = -1 # FF が見つかった時点で、配列上の位置情報を代入 findPos = 0 # ループフラッグ loop = True while (loop == True): # chunk に分けて読み出し x = fr.read(16) if (x == b''): break lg = len(x) # 0xFF = 255 # 0xF8 = 248 # 0x80 = 128 for i, c in enumerate(x): if ( c == 0xFF ): if (i < lg - 3): if ( (x[i+1] == 0xF8) and (x[i+3] == 0x80) ): fp = fr.tell() - (lg - i) loop = False break elif ( (i == lg - 3) or (i == lg - 2) ): if (x[i+1] == 0xF8): findPos = i continue elif (i == lg - 1): findPos = i continue if (findPos != 0): if ( (findPos == lg - 3) and (x[0] == 0x80) ): fp = fr.tell() - (2*len(x) - findPos) loop = False break elif ( (findPos == lg - 2) and (x[1] == 0x80) ): fp = fr.tell() - (2*len(x) - findPos) loop = False break elif ( (findPos == lg - 1) and ( (x[0] == 0xF8) and (x[2] == 0x80) ) ): fp = fr.tell() - (2*len(x) - findPos) loop = False break if (fp == -1): fr.close() print("ADTS ヘッダが見つかりませんでした。指定されたファイルは本当に AAC ファイルですか?") elif (fp == 0): fr.close() print("既にヘッダが整えられています。これ以上作業する必要はありません。") else: fr.flush() fr.seek(fp) try: fw = open(outfile, 'wb') except IOError as erMsg: fr.close() print("書き込みファイルを開けませんでした。\n書き込み権限があるか、共有違反が起こっていないかを確認してください。\n" + str(erMsg)) exit() while 1: # chunk に小分けして読み出して書き込み x = fr.read(1024 ^ 3) if (x == b''): break fw.write(x) fw.close() fr.close() print('Done!')
なお、
FF F8
のみのチェックにとどめたい場合は、 for ループ内を以下と入れ替えてください。
for i, c in enumerate(x): if ( c == 0xFF ): if (i < lg - 2): if (x[i+1] == 0xF8): fp = fr.tell() - (lg - i) loop = False break elif (i == lg - 1): findPos = i continue if (findPos != 0): if ( (findPos == lg - 1) and (x[0] == 0xF8) ): fp = fr.tell() - (2*len(x) - findPos) loop = False break