現象

録音した音声ファイルを別環境で聞くために、スクリプトで mp3 に変換 していた。

ある時、ふと気づいたら 2 時間あるはずなのに 1 時間 30 分辺りで 終了するものがいくつも出てきた。でも、元ファイルはちゃんと 2 時間 あった。

以前、変換を始めた頃から 2 時間物はあって、その時はちゃんと変換できて いた。

変換方法

変換は以下の様にやっていた

mplayer -really-quiet -ao pcm:file=/dev/stdout infile.flv \
  | lame  /dev/stdin outfile.mp3

入力は mplayer にお任せで wav ファイルをパイプで lame に渡して mp3 を 作成する。lame 側にはビットレートの指定などオプションも入る。

原因

いろいろ疑って調査した結果、悪い(マズイ)のは wav をパイプって 所だという結論になった。

wav ファイルのフォーマット を見ると、最初にヘッダがあってファイルサイズが定義されている。 これが 4 バイトしかない。

4 バイトである。つまり 4G だ(実際には 2G )。

また、パイプってのも問題。何がマズイのか? ファイルの先頭にサイズ情報が あるって事だ。

パイプって事は後戻りのできないストリームって事だ。でもサイズは変換処理が終了 してみないと正確な値は分からない。処理が終わってから先頭に戻って正しい値を 書き込む事がができないんだ。

こんな条件で、どう処理すれば良いのだろう。とりあえず十分に大きな数値を 書き込んでおき、可能なら後で修正する (いや、可能でないから問題化するのだが)しか無いだろう。

では何故、大抵のファイルでは問題にならないか。単に End-Of-File 、つまり 実ファイルの終端が先に来るからだろう。

ストーリー

つまり、起きていることはこんな事なのだろう

  1. mplayer が /dev/stdout に書き出す時、ファイルサイズとして 適当に大きな値でヘッダ出力する
  2. 通常の wav ファイル出力なら、最後に先頭に seek して正しい値を書き込む
    • そもそも mplayer は出力先が標準出力である事を知らない。または、 分かっているからこそ、コマンドラインオプションに標準出力を用意していない
  3. lame は入力の先頭にあるヘッダ情報からサイズを取得する
    • これが正しくない事は上述の通り
  4. lame は入力で得られたサイズ(フレーム)分の処理を行い、正常に終了する

実際のところは

調査の一環としてパイプに出力されるサイズを見てみた

$ mplayer -really-quiet -ao pcm:file=/dev/stdout sample.flv | od -tax1 | head -4
0000000   R   I   F   F   $   p del del   W   A   V   E   f   m   t  sp
         52  49  46  46  24  f0  ff  7f  57  41  56  45  66  6d  74  20
0000020 dle nul nul nul etx nul stx nul nul   ; nul nul nul   \ enq nul
         10  00  00  00  03  00  02  00  80  bb  00  00  00  dc  05  00

24 f0 ff 7f の部分がサイズで 0x7ffff024 、10 進で 2,147,479,588 となる。 負数にされない様に MSB は落としてあるので約 2G となっている。

パイプでなく wav ファイルに出力させたものは

$ ls -l sample.wav
-rwxrwx--- 1 root vboxsf 2768666668  4月 12 01:47 sample.wav
$ od -tax1 sample.wav | head -4
0000000   R   I   F   F   $ nul ack   %   W   A   V   E   f   m   t  sp
         52  49  46  46  24  80  06  a5  57  41  56  45  66  6d  74  20
0000020 dle nul nul nul etx nul stx nul nul   ; nul nul nul   \ enq nul
         10  00  00  00  03  00  02  00  80  bb  00  00  00  dc  05  00

と、なっていた。サイズは 2768666668 バイト。ヘッダ中の 24 80 06 a5 に フォーマット規約の通り +8 して、ちゃんと一致する。

結局、mplayer が 24 80 06 a5 と出力してくれれば良いのだが、 処理の最初にそれを知り得ないから、暫定で 24 f0 ff 7f を出力 しているって事で合ってる様に見える。

ちなみに、もっと大きなファイル、5 時間位のものを wav に変換してみた 所、3 時間位で終わっていた。そのヘッダは以下の様になっていた。

$ od -tax1 sample2.wav | head -4
0000000   R   I   F   F   $   p del del   W   A   V   E   f   m   t  sp
         52  49  46  46  24  f0  ff  ff  57  41  56  45  66  6d  74  20
0000020 dle nul nul nul etx nul stx nul nul   ; nul nul nul   \ enq nul
         10  00  00  00  03  00  02  00  80  bb  00  00  00  dc  05  00

24 f0 ff ff になっている。上位バイトが 0xFF 0xFF 。ここが上限なのだろう。

解決に向けて

目的は mp3 に正しく変換する事だ。

とりあえず apt-cache search してみたら pacpl というパッケージがあった。 使ってみたら 5 時間ものも含めて正常な長さの mp3 に変換できた。

ただ、これ、中間ファイルを作るんだよなぁ。wav の。つまり、めちゃくちゃ 大きい中間ファイルを作る。今までの手法の良い所は中間ファイルを作らないって 事だったのだが。

で、考えた。どうせ wav ファイルを作成するなら自前で良いかな。 つまり、パイプでやってたところを中間ファイル(.wav)を使う様に する。

pacpl もラッパーで内部的に lame を呼び出してるのでまぁ同じ。 それに pacpl から呼び出された lame に細かい引数を指定するのは 面倒だけど、自前でやれば細かく指定できる。

要望

どうなれば良いのか。幾つか考えてみた。

wav 以外のフォーマットの採用

サイズの問題は認識されていて、64 ビットの wav フォーマットも提案されている らしい。ただ、普及していない。

まぁ、RAW データのフォーマットとして別の何か、ファイルサイズの もっと大きいものが普及してくれれば良いだけなのだが。

そうすれば mplayer も lame も対応してくれるだろう。

パイプ処理に適したフォーマットの提案

後戻りできないストリームの先頭にサイズ情報を置くのが問題なんだから、 そこにサイズ情報を置かなければ良い。

例えば画像なら 1 ピクセルのデータの並びでデータを保持する事も行われる。 1 ピクセルのデータサイズとか画像の横幅とかの情報が別途必要だが。

同じ様に、サイズ以外の情報、サンプリングレートとかのみをヘッダに含め、 データ量は終端まで読んで確定する。そんな処理を考慮したヘッダ形式。

wav フォーマットの拡張

単に、ヘッダ情報のサイズを無視するフラグをヘッダの拡張域に持つとかで どうだろう。

或いは、正しいサイズを拡張域に持つ様にするとか。

この程度なら現行へのインパクトも小さそうだなぁ。対応していない アプリは拡張ヘッダを無視すれば良いだけだし。

参考

以前はできていた。この時はどうだったのか調べてみた。

環境は Debian squeeze i386 。最近終了したが LTS が提供されていた。

同じ様にパイプの先頭を見ると

$ mplayer -really-quiet -ao pcm:file=/dev/stdout sample.flv | od -tax1 | head -4
0000000   R   I   F   F   $   p del del   W   A   V   E   f   m   t  sp
         52  49  46  46  24  f0  ff  7f  57  41  56  45  66  6d  74  20
0000020 dle nul nul nul soh nul stx nul nul   ; nul nul nul   n stx nul
         10  00  00  00  01  00  02  00  80  bb  00  00  00  ee  02  00

と、なっている。サイズ情報は変わらない。

次にファイル出力させたものをプレイヤーのプロパティで見てみたら、

Audio: PCM 48000Hz stereo 1536kbps [A: pcm_s16le, 48000 Hz, 2 channels, s16, 1536 kb/s]

となっていた。今の環境で変換したものだと

Audio: IEEE Float 48000Hz stereo 3072kbps [A: pcm_f32le, 48000 Hz, 2 channels, fp32, 3072 kb/s]

となっている。ビットレートが倍になっている。どうやら品質が違うようだ。

ファイルサイズで見ても 1,384,325,164 と 2,768,666,668 であり、 倍になっている。

つまり、mplayer がデフォルトで出力する wav の品質が上がった為に、 出力されるバイト数が増えて 2G を超えたため、今回の現象になったものらしい。

その後の展開

新しい展開があった