blog

FFmpegのhevc codec_tagの互換性の問題

問題の説明\n\n// ストリーム\nストリーム #0:0[0x101]: video: hevc , yuv420p, , 250 tbr....

Dec 2, 2020 · 6 min. read
シェア

問題の説明

最近、同僚の同じグループは、問題が発生しました: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
Read next

AndroidのLowMemoryKillerメカニズム

Androidのメカニズムは、現在のシステムメモリのしきい値とプロセスの優先度に基づいて、どのプロセスを強制終了するかを決定するメモリ再利用メカニズムです。定期的に現在のシステムの利用可能なメモリをチェックし、システムの残りの利用可能なメモリが少なくなると、プロセスを殺す戦略を起動します。異なるメモリしきい値に基づいて、どのプロセスを強制終了するかを決定します。

Dec 2, 2020 · 11 min read