1. 【概要】
ffmpeg で変換結果を標準出力に出力するためには、-f mp4 -
のように-f
オプションで出力フォーマットを明示的に指定した上で、出力先として -
を指定しなければなりません。
でも、そのような指定をしても変換中にエラーになることがあります。
推測混じりですが原因がつかめたので、せっかくなので投稿することにしました。
2. 【発生する問題について】
まず、以下のようなコマンドを実行してみます。
ffmpeg -i sample.mp4 -c:v copy -c:a copy -map 0 -f matroska - | ffplay -i -
このコマンドでは、mp4形式の動画ファイルを mkv形式に変換して ffplay で再生しています。
「え?普通に ffplay -i sample.mp4
でいいんでないの?」 というツッコミはごもっともですが、わかりやすい例として挙げただけなのでご容赦ください。
これを実行すると、普通に ffplay が起動して動画の再生が始まります。まぁ、普通に当たり前です。
次に、出力フォーマットを mp4 にしてみます。
ffmpeg -i sample.mp4 -c:v copy -c:a copy -map 0 -f mp4 - | ffplay -i -
すると、ffmpeg から以下のようなエラーメッセージが表示され、変換ができません。
[mp4 @ 0000016b2121a200] muxer does not support non seekable output
[out#0/mp4 @ 0000016b21214840] Could not write header (incorrect codec parameters ?): Invalid argument
[aost#0:1/copy @ 0000016b212622c0] Error initializing output stream:
S
また、出力フォーマットは mkv のままで、映像を libaom-av1
でエンコードしてみます。
ffmpeg -i sample.mp4 -c:v libaom-av1 -crf 23 -c:a copy -map 0 -f matroska - | ffplay -i -
すると、ffmpeg から以下のようなエラーメッセージが表示されて、同じく変換に失敗します。
av_interleaved_write_frame(): Invalid data found when processing input
3.【原因について】
エラーメッセージからピンときた方もおられると思いますが、原因は出力先がランダムアクセス不可であったことでした。つまり、出力先を標準出力にしたことによる問題です。
この問題は必ず発生するわけではなく、例えば出力フォーマットに mkv を指定した場合 (つまり -f matroska
を指定した場合) は問題なく動作しましたし、映像のエンコーダに copy
/ libx264
/ libx265
を指定した場合も問題ありませんでした。
4. 【これってバグ?】
ここから先は私の推測になります。
結論から言うと、バグではなく仕様 と言えると思います。何故かというと ffmpeg や 各コーデックのプログラム側ではおそらく対処のしようがないからです。
※この部分はかなり込み入った話になります。読み飛ばしても結構です。
話は変わりますが、以前に zip ファイルのフォーマットを調べたことがあるのですが、その中には以下のようなデータ構造がよく使われています。
<----ファイルの先頭方向 ファイルの末尾方向---->
block の長さ (2または4バイト整数) |
block (圧縮されたデータのバイト列)・・・・・・・・・・・・・・・・・・・・・ |
---|
こういったデータ構造をデコードする場合には、まず zip ファイルから「blockの長さ」を読み込んで、次にその長さの分だけ「block」部分を読み込みます。
逆にエンコードの場合には、まず「blockの長さ」を zip ファイルに書き込んで、次に block 部分を書き込み・・・たくなります。ところがそうすることが難しいことがよくあります。その代表的なケースは「block の部分が圧縮されたデータであり、実際に圧縮をしてみないと圧縮後の長さがわからない」というものです。
もちろん、実際に圧縮をしてみてその結果の長さを先に書き込んで、圧縮されたデータをその後に書き込む、ということも可能ではあります。しかし、その場合は、圧縮した結果をどこかに保存しておいて、その長さを調べて先に書き込み、その後に圧縮結果を書き込む必要があります。その場合の問題は、圧縮後のデータを格納するためのメモリ(大抵はヒープメモリ)が余分に必要なことです。また、ヒープ領域の管理にはそれなりにCPUやディスクI/Oコストもかかります。
そういった問題を 大抵の zip エンコーダの開発者は以下のように解決しています。
- まず、「block の長さ」部分をすべて 0 で書き込む。
- 次に、入力されたデータを逐次圧縮して「block」部分に書き込む。
- 圧縮されたデータの長さが判明しているので、「blockの長さ」部分まで戻って 改めて圧縮されたデータの長さを書き込む。
これなら余分なメモリを消費せず、メモリ管理の手間も減ります。
要するに、消費メモリの削減や圧縮速度を低下させないためには 出力先はランダムアクセス可能でなければならない わけです。1
話は ffmpeg 関連に戻りますが、おそらく ffmpeg のいくつかのエンコーダは前述の例と同様の理由でコーデックのファイルフォーマットの都合により出力先がランダムアクセス可能でなくてはならないのではないでしょうか。
5. 【まとめ】
- ffmpeg での変換結果を標準出力やパイプへ出力しようとすると、出力フォーマットや変換コーデックの指定によってはエラーとなることがある。
- おそらくはファイルフォーマットに由来する仕様であり、回避方法としては出力先をファイルにするしかないと思われる。
-
余談ですが、実際の zip ファイルフォーマットの規定には、出力先がランダムアクセス不可である場合の救済のためのとある特殊なフォーマットが存在します。しかし、そのフォーマットでのエンコードに対応していない zip エンコーダは多く存在します。 ↩