問題の説明
最近、同僚の同じグループは、問題が発生しました:FFmpegスプライシングtsファイルをmp4を生成するために、Androidプラットフォームで再生することができますが、MacとiOSで再生することはできません。FFmpegのバージョンは3.3で、tsストリームのオーディオトラックはlc aacで、ビデオトラックはhevcです:
//
Stream #0:0[0x101]: Video: hevc (Main) ([36][0][0][0] / 0x0024), yuv420p(tv), 720x1280, 250 tbr, 90k tbn, 90k tbc
//
Stream #0:1[0x102]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 100 kb/s
MacのQuickTime Playerで再生すると、フォーマットの非互換性が表示されますが、QQ VideoとVLCで再生できます。また、AndroidではMediaPlayerと独自のプレーヤーで再生できます。
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'leon.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf0
Duration: 00:02:04.42, start: 99.935011, bitrate: 173 kb/s
Stream #0:0(und): Video: hevc (Main) (hev1 / 0x31766568), yuv420p(tv), 720x1280, 159 kb/s, SAR 1:1 DAR 9:16, 1.93 fps, 250 tbr, 90k tbn, 90k tbc (default)
Metadata:
handler_name : VideoHandler
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 12 kb/s (default)
Metadata:
handler_name : SoundHandler
問題の初期解答
問題は何ですか?私たちは、世界最大のゲイの出会い系サイトでヒントを見つけましたEncode H265 to hvc1 codecします。 つまり:hev1またはhvc1は、mp4コンテナ内のhevcストリームをパッケージ化する異なる方法を表す2つのコーデックタグです。hev1タグを持つmp4は、Quicktime PlayerとiOSでサポートされなくなりました。hev1またはhvc1は、mp4でhevcをパッケージ化する2つの異なる方法に過ぎないので、エンコード形式自体を書き換えることなく、2つのタグを切り替えることができます。hvc1はhevcをmp4にパッケージする2つの異なる方法にすぎず、コーデックフォーマット自体はhevcなので、再エンコードすることなくこの2つのタグを切り替えることができます。
そこで、以下のコマンドでmp4のコーデックタグを変換してみました:
ffmpeg -i leon.mp4 -c:v copy -tag:v hvc1 -c:a copy leon-hvc1.mp4
FFmpeg 3.3以前では、ストレートにエラーになることがわかりました:
<mux.c init_muxer 376> Tag hvc1 incompatible with output codec id '174' ([35][0][0][0])
また、3.4以降では問題なく動作します。
leon.mp4はhev1タグモードですが、-tag:v hvc1でhvc1タグモードに変換できます。
同様にFFmpeg 3.4以降では、以下のコマンドでtsをmp4にカプセル化し、-tag:vでmp4のコーデックタグモードを制御できます。
// tsをMP4に変換、デフォルトはleon.mp4コーデックタグがhev1の場合、Quicktime Playerでは再生できない。
ffmpeg -i leon.ts -c:v copy -c:a copy leon.mp4
// tsをMP4に変換する際、強制的にhvc1を指定するので、leon-hvc1とする。.mp4コーデックタグがhvc1なら、Quicktime Playerで再生できる。
ffmpeg -i leon.ts -c:v copy -tag:v hvc1 -c:a copy leon-hvc1.mp4
テストした結果、-tag:v hev1も機能しないことがわかりました:
<mux.c init_muxer 376> Tag hev1 incompatible with output codec id '174' ([35][0][0][0])
つまり、FFmpeg 3.3以前は-tag:v hvc1と-tag:v hev1をサポートしていませんが、FFmpeg 3.4以降はサポートしています。
問題の根本原因
FFmpegのバージョン間の違いは、ソースコードを見ることでしか解決できません。良い点は、エラーメッセージにファイル名、関数名、行番号が指定されていることです: libavformat/mux.cファイル、init_muxer関数、376行目。
コア・コード・セグメントを以下に示します:
// parはhevcストリームのエンコーディングパラメーターである >codecpar
// stはhevcストリーム: AVStream
// sは AVFormatContext である。
// ofカプセル化されたフォーマットを示す。>oformat;
if (par->codec_tag) { // AVStream>codecpar->codec_tag
// AVStream-をチェックする。>codecpar->codec_tagラッパー・コンテナがサポートするコーデックがラップされているかどうか。_tagすなわち、AVOutputFormat- のリストである。>codec_tag
if (!validate_codec_tag(s, st)) {
// AVOutputFormat- より>codec_tagリストのコードIDに対応するコーデックを見つける_tag
const uint32_t otag = av_codec_get_tag(s->oformat->codec_tag, par->codec_id);
// コーデックを設定する_tagコーデックでのチェックサムの失敗_id対応コーデック_tagはotagである
av_log(s, AV_LOG_ERROR, "Tag %s incompatible with output codec id '%d' (%s)
", av_fourcc2str(par->codec_tag), par->codec_id, av_fourcc2str(otag));
ret = AVERROR_INVALIDDATA;
goto fail;
}
} else {
// AVストリーム>codecpar->codec_tagが0の場合、FFmpegはコーデックIDに従って、カプセル化されたフォーマットからコーデックを取得する。_tagリストでコーデックを探す_tag。
par->codec_tag = av_codec_get_tag(of->codec_tag, par->codec_id);
}
avformat_write_header
もし AVStream->codecpar->codec_tag に値があれば、AVStream->codecpar->codec_tag がカプセル化形式がサポートする codec_tag のリストにあるかどうかをチェックし、なければエラーメッセージを表示します。AVStream->codecpar->codec_tag が 0 の場合は、AVCodecID に従って、カプセル化形式がサポートする codec_tags のリストから一致する codec_tag を探します。
ここまでは、FFmpeg 3.3以降のバージョンでロジックが一貫しているので、上記のhvc1タグの相違点の原因は、FFmpegのバージョンによってmp4のサポートするcodec_tagsのリストが異なるためと思われます。
FFmpeg 3.3のエラーメッセージをもう一度見てください:
// hevcストリームに現在設定されているhvc1コーデックを示す。_tagサポートされていない。174はhevcのAVCodecIDで、hevcに対応するコーデックを示す。_tag10進数35のはずである。
<mux.c init_muxer 376> Tag hvc1 incompatible with output codec id '174' ([35][0][0][0])
FFmpegバージョン3.3では、hevcに設定されたhvc1タグはmp4のカプセル化フォーマットではサポートされていません。
libavformat/movenc.c
は mp4 と mov 用の Muxer クラスで、AVOutputFormat->codec_tag はあるカプセル化されたフォーマットがサポートする codec_tags のリストを指定します。mp4 がサポートする codec_tags のリストは libavformat/isom.c の ff_mp4_obj_type 構造体にあります。よく使われるものをいくつか貼り付けてください:
{ AV_CODEC_ID_H264 , 0x21 },
{ AV_CODEC_ID_HEVC , 0x23 },
{ AV_CODEC_ID_AAC , 0x40 },
hevcに対応するcodec_tagは0x23、つまり10進数で35ですが、これは上記のエラーメッセージと同じです。つまり、mp4のhevcエンコーディングフォーマットでサポートされているcodec_tagは、hev1である0x23のみであり、明示的にhvc1を指定しても失敗します。
FFmpegの最新のトランクバージョンをもう一度見てみると、mp4でサポートされているcodec_tagsのリストがあり、やはりよく使われるものがいくつか掲載されているだけでした:
{ AV_CODEC_ID_H264 , MKTAG('a', 'v', 'c', '1') },
{ AV_CODEC_ID_H264 , MKTAG('a', 'v', 'c', '3') },
{ AV_CODEC_ID_HEVC , MKTAG('h', 'e', 'v', '1') },
{ AV_CODEC_ID_HEVC , MKTAG('h', 'v', 'c', '1') },
{ AV_CODEC_ID_AAC , MKTAG('m', 'p', '4', 'a') },
hevcに対応するcodec_tagはhev1とhvc1で、hev1がリストの先頭にあるので、デフォルトでhev1が使われます。つまり、-tag:vを積極的に指定しないと、hevcに対応するcodec_tagはhev1で、Quicktime Playerで再生できないので、tag:vでcodec_tagを指定する必要があります。hvc1で強制的にcodec_tagを指定することで、すべてのプラットフォームと互換性を持たせることができます。
FFmpeg3.3はhev1タグをサポートしているのに、なぜtag:v hev1を指定するとエラーになるのですか?それは、FFmpeg3.3と最新バージョンのcodec_tagの表し方が変わったからで、どちらもhev1ですが、2つのバージョン間の変更値は互換性がないので、-tag:v 35を指定すれば、FFmpeg3.3でも間違いにはなりません。
ここまでで、すべての疑問は解決しました:
- FFmpeg 3.3 以前では、mp4 コンテナは hevc の hev1 タグのみをサポートし、値 0x23 を取ります。
- FFmpeg 3.4 以降では、mp4 コンテナは hevc の hev1 タグと hvc1 タグの両方をサポートしており、デフォルトで hev1 タグを使用します。
FFmpeg3.3以前のバージョンでhevcのhvc1タグを使いたい場合、どうすればいいのでしょうか?FFmpegの異なるバージョンにおけるmovパッケージフォーマット、すべてのtがhevcのhev1とhvc1タグの両方をサポートしていること、hev1タグのデフォルト使用。
avformat_write_header
上記の分析は、FFmpegのコマンドラインに基づいており、次に、コード内のcodec_tagを設定する方法も比較的簡単で、hevcストリームのcodec_tagを設定します:
AVStream->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1');
Mp4の2つの異なるタグの違いは何ですか?異なるタグは異なるBoxタイプに対応しています。
- hev1 tagタグ: stsd -> hvc1 -> hvc1 -> hvcC> hev1 -> hvcC
- hvc1 tagタグ: stsd -> hvc1 -> hvc1 -> hvcC> hvc1 -> hvcC