7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

グラフィックス全般Advent Calendar 2024

Day 10

Vulkan Video で MP4 の H.265/HEVC 動画をデコードする際のメモや注意点

Last updated at Posted at 2024-12-09

はじめに

グラフィックス全般 Advent Calendar 2024 10日目の記事になります.

所感

非常に低レイヤ.

MP4 から demux した動画ストリームを Vulkan のコマンドに渡して~ のように想像していると, かなり落胆することになると思われる.

デコードに用いる Vulkan Video のオブジェクトを作ったりコマンドを実行させたりする際に,
驚くほど多くの H.265 固有のパラメータを渡す必要がある.
これらは基本的には自前で H.265 の bitstream からパースして得る必要がある.

H.265 の仕様書1と睨めっこしながらかなり苦労して Rust 用のパーサを用意した.
パーサとして完成とは言えないが, MainMain 10 profile の H.265 の bitstream を Vulkan Video でデコードするためには十分なものができたので, いちおう, 公開しておく:

Vulkan Video そのものの仕様で難解と言えるのは DPB(Decoded Picture Buffer) やその状態の管理周りくらいで,
あとは Vulkan を知っていればそれほど苦労することはないと感じた.

難しい部分は, H.265 など該当のコーデックそのものの仕様が複雑で膨大なことに起因していて,
Vulkan の仕様書とデコード/エンコードするコーデックそのものの仕様書を交互にたくさん読むことになる.

Vulkan Video: 1フレームのデコードの流れ

大まかな流れ


// video coding scope を開始.
vkCmdBeginVideoCodingKHR(...);

// 開始時やランダムアクセス(シーク)後ならば VkVideoSessionKHR をリセットする
if (is_first_picture) {
    // VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR
    vkCmdControlVideoCodingKHR(...);
}

// フレームのデコード
vkCmdDecodeVideoKHR(...);

// video coding scope を終了.
vkCmdEndVideoCodingKHR(...);

vkCmdBeginVideoCodingKHR 時

video coding scope を開始するコマンド.

video coding scope に bound reference picture たちを設定

video coding scope の bound reference picture とは, scope 中で使える reconstructed picture または reference picture として使用できる picture を表す.
(reconstructed picture は要するに今回のデコードの出力となる picture のこと)

VkVideoReferenceSlotInfoKHR として, VkVideoBeginCodingInfoKHR::pReferenceSlots に設定する.
その際, それぞれの picture に最初に割り当てられる DPB slot も指定する.

vkCmdDecodeVideoKHR 時

H.265/HEVC 固有のパラメータ

VkVideoDecodeH265PictureInfoKHRVkVideoDecodeInfoKHR の pNext-chain に含めることで,
H.265 固有の picture のパラメータを設定する.

後述の VkVideoDecodeH265PictureInfoKHR::pSliceSegmentOffsets もここ.

reference picture setup request

将来のデコードで reference picture となり得る picture の場合,
(これは NAL unit type で判断できる)
VideoDecodeInfoKHR::pSetupReferenceSlot を使って reference picture setup をリクエストする必要がある.

active reference の設定

このデコードがフレーム間予測に用いる reference picture を, VideoDecodeInfoKHR::pReferenceSlots で設定する必要がある.

Vulkan Video の概念 メモ

DPB slot を active にする操作:

  • reference picture setup request つきで reconstructed picture として使用する
    • 👉 DPB slot は その reconstructed picture (への picture reference) に associate される

DPB slot を inactive にする操作/状況:

  • reconstructed picture として使用で coding operation が失敗する
  • reference picture setup request なし で reconstructed picture として使用する
  • frame に associate された DPB slot を reconstructed picture として使用する
  • vkCmdBeginVideoCodingKHRVkVideoReferenceSlotInfoKHR::pPictureResourcenullptr

(DPB slot の内容は invalid picture reference になる)

H.265/HEVC の概念 メモ

picture の種類

IRAP(Intra Random Access Point) picture

総称: I-frame であって, ランダムアクセスポイントとなる picture の種類.

以下が IRAP に該当する:

  • IDR(Instantaneous Decoding Refresh) picture
  • CRA(Clean Random Access) picture
  • BLA(Broken Link Access) picture

IDR(Instantaneous Decoding Refresh) picture

NAL unit type でいうと IDR_W_RADL (== 19) と IDR_N_LP (== 20).

IRAP なので, I-frame.

bitstream の最初の picture は必ず IDR になる.

GOP 内で後続の (i.e. この picture を associated IRAP picture として持つ) P-frame や B-frame は この picture よりも復号順で先行する picture を参照しないことが保証される.
👉 よって, ランダムアクセスの対象としては最も都合がいい.

よって, IDR picture をデコードする際には DPB をクリアする.
また, POC(Pic Order Count) が 0 にリセットされる.

IDR_W_RADL は, RADL を associated picture として持ちうる (RASL は持たない).
IDR_N_LP は, LP(Leading Picture) を associated picture として持たないことが保証される.

CRA(Clean Random Access) picture

NAL unit type でいうと CRA_NUT (== 21).

IRAP なので, I-frame.

空でない RPS (Reference Picture Set) を指定することがある.
(unused for reference としてマークする)

CVS 内に RASL(Random Access Skipped Leading) picture を持つ場合がある.
これら RASL picture は CRA に複号順で先行する picture を参照するため,
CRA からデコードを開始した際にはデコードすることができない.

BLA(Broken Link Access) picture

⚠️ 筆者はこれを扱ったことがない.

NAL unit type でいうと BLA_W_LP (== 16), BLA_W_RADL (== 17), BLA_N_LP (== 18)

IRAP なので, I-frame.

Trailing picture

NAL unit type でいうと TRAIL_N (== 0), TRAIL_R (== 1)

RADL(Random Access Decodable Leading) picture

NAL unit type でいうと RADL_N (== 6), RADL_R (== 7).

associated IRAP の leading picture (i.e. 出力順で先行する).

RASL(Random Access Skipped Leading) picture

NAL unit type でいうと RASL_N (== 8), RASL_R (== 9).

associated CRA/BLA の leading picture (i.e. 出力順で先行する).

CRA/BLA からデコードを開始する場合,
それより前にあるためデコードしていない or bistream 中にない picture を参照することになるため,
これら RASL picture はデコードできない.
よって CRA/BLA からデコードを開始する場合は, RASL picture はスキップする.

VPS, SPS, PPS

PPS (Picture Parameter Set)

1つ以上の coded picture の coded slice segment NAL unit から参照されるパラメータを持つ.

SPS (Sequence Parameter Set)

1つ以上の PPS or active parameter sets SEI message を含む1つ以上の SEI NAL unit から参照されるパラメータを持つ.

VPS (Video Parameter Set)

1つ以上の SPS or active parameter sets SEI message を含む1つ以上の SEI NAL unit から参照されるパラメータを持つ.

H.264 にはなく, H.265/HEVC で追加された概念.

VPS, SPS, PPS の在処

hvc1

hvc1 の場合, これらの NAL unit は out-of-band in the sample entry である (i.e. MP4 ではコンテナに格納される).
この場合, これらの NAL unit は trak -> mdia -> minf -> stbl -> stsd -> hvc1 -> hvcC に格納される HEVCDecoderConfigurationRecord 中 の array に含まれている.

この構造体そのものは, アクセス有料の ISO/IEC 14496-152 で定義されている3が,
例えば chromium のコードで構造を参考にすることもできる.

hev1

hev1 の場合, これらの NAL unit は out-of-band in the sample entry である and/or in-band in the samples である(i.e. bitstream 内に含まれる), らしい.

つまり hvc1 と同様に MP4 コンテナに格納されている場合もあれば, bitstream 内に含まれる場合もある4.

MP4 コンテナに格納されている場合, これらの NAL unit は trak -> mdia -> minf -> stbl -> stsd -> hev1 -> hvcC に格納される HEVCDecoderConfigurationRecord 中 の array に含まれている.

bitstream 内に含まれる場合,
access unit の構造が (AUD), VPS, SPS, PPS, (P-SEI), [slice segment, ...] のようになっているはず.

Disclaimer

筆者は「bitstream 内に含まれる場合」の MP4 を扱っていない.

VPS, SPS, PPS の関係

SPS は VPS の ID を, PPS は SPS の ID を参照する.

GOP(Group Of Pictures)

正確には H.265/HEVC の仕様に存在する概念ではない(H.264 に存在する).

I-picture と, その次の I-picture の前までの, 複号順で後続する picture たちを指す.
先頭の I-picture が IRAP picture であることは求められていない.

CVS(Coded Video Sequence)

IRAP picture と, その次の IRAP picture の前までの, 複号順で後続する (i.e. 前述の IRAP picture を associated IRAP picture として持つ) picture たちを指す.

ここでいう IRAP picture は, NoRaslOutputFlag == 1 であるもの.

つまり, P-predition や B-predition によって関係する picture たちに閉じたグループ.
associated IRAP picture を先頭とするため, 複号順で先頭に先行する picture を P-prediction や B-prediction で参照しない保証がある.

POC(Picture Order Count)

CVS(Coded Video Sequence) 内で一意的な値であり, IDR-frame ではその値は 0 になる.
(またその際 DPB はクリアされ, 存在していた picture たちは unused for reference としてマークされる)

「動画ファイル全体で一意な値」とは一般には言えない5.

GOP の先頭は I-frame だが, それは必ずしも IRAP picture ではない; CVS は1つまたは複数の GOP から構成されうる.

じっさい, 下記のように GoPro HERO11 の吐く MP4 の H.265 bitstream では,
30 フレームごとに IDR があり CVS をなしている.

ランダムアクセス(シーク) について

正常にデコードすることを考えると,
ランダムアクセスしたい位置から, 復号順の逆方向で最も近い IRAP picture を見つけてそこからデコードを開始する必要がある.
IRAP には IDR と CRA と BLA picture があるが, ここでは簡単のため IDR と CRA のみを考える.

IDR が見つかれば簡単

デコーダのステートをフラッシュ/リセットすればいい:

  • DPB の全ての slot を inactive にする
  • POC(Pic Order Count) を 0 に戻す.

ただし...

IDR が一番最初のフレームにしかない動画ファイルも実際には多い.
よってランダムアクセスの対象には CRA も考慮されなければならない.

GoPro HERO11 で撮影した動画の MP4 の H.265 ストリームでは, 30フレームに1回必ず IDR がある!

CRA(Clean Random Access) picture へのランダムアクセスの扱い

RPS (Reference Picture Set)

CRA は I-frame であるとされているが, 実際には, 空でない RPS (Reference Picture Set) を指定するような syntax element を持つことがある.

ランダムアクセスの結果として CRA からの再生になる場合, その CRA は NoRaslOutputFlag == 1 を持っていることになる:

8.1.3 Decoding process for a coded picture with nuh_layer_id equal to 01:

If the current picture is an IDR picture, a BLA picture, the first picture in the bitstream in decoding order, or the first
picture that follows an end of sequence NAL unit in decoding order, the variable NoRaslOutputFlag is set equal to 1.

NoRaslOutputFlag == 1 であるような IRAP picture の場合には, reference picture たちは "unused for reference" としてマークすることになる:

8.3.2 Decoding process for reference picture set1:

When the current picture is an IRAP picture with NoRaslOutputFlag equal to 1, all reference pictures with nuh_layer_id
equal to currPicLayerId currently in the DPB (if any) are marked as "unused for reference".

その際, 実装により unavailable reference pictures というものが生成される模様:

8.1.3 Decoding process for a coded picture with nuh_layer_id equal to 01:

When the current picture is a BLA picture or is a CRA picture with NoRaslOutputFlag equal to 1, the
decoding process for generating unavailable reference pictures specified in clause 8.3.3 is invoked, which
needs to be invoked only for the first slice segment of a picture.

RASL(Random Access Skipped Leading) picture たち

CRA へのランダムアクセスから再生を開始した際, 次の CVS に達するまで,
現在の CVS に存在する RASL(Random Access Skipped Leading) picture はデコードできない.

RASL picture は associated IRAP picture である当該 CRA picture に復号順で先行する picture を参照する可能性があるため.

その名の通り, このケースではこれら RASL picture のデコードをスキップする.

Vulkan Video における注意点

Disclaimer

筆者は NVIDIA のドライバにおける実装でしか動作を確認していない.

VK_VIDEO_DECODE_CAPABILITY_DPB_AND_OUTPUT_COINCIDE_BIT_KHR と I-frame

まずはいちばん最初のフレームのデコードが動作するようにしたいところだが.

最初のフレームは必ず IDR でありつまり I-frame (i.e. reference picture を用いない) だが,
VK_VIDEO_DECODE_CAPABILITY_DPB_AND_OUTPUT_COINCIDE_BIT_KHR の場合は,
I-frame のデコードの際も, DPB slot をちゃんと(出力先と同じものを)指定する必要がある (VkVideoDecodeInfoKHR::pSetupReferenceSlot).
当然だがその際の image layout は VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR になる.

NVIDIA の実装においては, I-frame においても,
これを行わないとデコードが失敗することを確認している.

つまり, result status query の結果が VK_QUERY_RESULT_STATUS_ERROR_KHR になり,
デコード出力先の VkImage には何も書き込まれない.

入力 bitstream

現状の仕様6はこの点についてかなり underspecified だと感じられるため,
NVIDIA の実装でしか動作を確認していないことをここで繰り返しておく.

⚠️ Annex B で渡す

H.265/HEVC では (H.264/AVC でも),
NAL unit たちを格納する byte stream のフォーマットは2種類存在する37:

  • Length-prefixed byte stream
  • Annex B byte stream format
    H.265 の仕様書1Annex B で定義される.

VkVideoDecodeInfoKHR::srcBuffer には, Annex B byte stream,
つまり start code prefix (3バイトの 0x000001) と NAL unit を交互に配置したバッファを指定する.

NAL unit は start code prefix がエスケープされた EBSP(Encapsulated Byte Sequence Payload) のまま用いる.

つまり MP4 のストリームのサンプルから読んだような length-prefixed byte stream をデコードしたい場合は,
Annex B に変換する必要がある.

VkVideoDecodeH265PictureInfoKHR::pSliceSegmentOffsets には srcBuffer 中の start code prefix たちのオフセットを設定する.

⚠️AUD (Access Unit Delimiter) NAL unit はスキップする

AUD NAL unit は access unit に含まれていることもないこともあるが, これは入力として渡さない.

渡してしまうと, NVIDIA のドライバは result status query が仕様にない 1000331003 を出力する形で失敗する.
(この値は他のケースでも出力されることがある)

H.264/AVC の場合で SPS, PPS の NAL unit は渡しても問題ないとされている8のとは, 対照的だが,
VkVideoDecodeH265PictureInfoKHR::pSliceSegmentOffsets の名前を考えると,
slice segment ではない NAL unit を渡してはならない, というのは納得はいく.

H.265 の VCL NAL unit と StdVideoDecodeH265PictureInfoFlags

nal_unit_type Name Is IRAP Is IDR Is Reference
0 TRAIL_N
1 TRAIL_R
2 TSA_N
3 TSA_R
4 STSA_N
5 STSA_R
6 RADL_N
7 RADL_R
8 RASL_N
9 RASL_R
10 RSV_VCL_N10
11 RSV_VCL_R11
12 RSV_VCL_N12
13 RSV_VCL_R13
14 RSV_VCL_N14
15 RSV_VCL_R15
16 BLA_W_LP
17 BLA_W_RADL
18 BLA_N_LP
19 IDR_W_RADL
20 IDR_N_LP
21 CRA_NUT
22 RSV_IRAP_VCL22
23 RSV_IRAP_VCL22

Is ReferenceStdVideoDecodeH265PictureInfoFlags::IsReference に設定され,
これは H.265 における reference picture setup を制御し, ひいては DPB slot の activation を制御する.

fn is_reference(nal_unit_type: u8) -> bool {
  if nal_unit_type < 16 {
    nal_unit_type & 1 == 1
  } else {
    true
  }
}

動画の回転について

たとえば GoPro HERO11 を 180° 回転して設置して撮影した動画では, bitstream をデコードして得られる画像が 180° 回転した状態になる.

正しい向きで動画を再生するためには, この回転を打ち消す必要がある.

表示するために必要な変換を表す行列が, 以下に含まれている:

  • MP4 の moov -> mvhd
  • MP4 の 該当 track の trak -> tkhd

前者は単位行列になっていることが多い模様.

ツールたち

NVIDIA Nsight

Vulkan Video に対応しており, vkCmdDecodeVideoKHR の出力先の状態を表示することができる.

ffmpeg

trace_headersNAL unit たちをダンプすることができる:

ffmpeg -i video.mp4 -c copy -bsf:v trace_headers -f null - 2> NALUs.txt
NALUs.txt
...

[trace_headers @ 000002b4a4fd34c0] Packet: 61163 bytes, key frame, pts 128000, dts 126464, duration 512.
[trace_headers @ 000002b4a4fd34c0] Slice Segment Header
[trace_headers @ 000002b4a4fd34c0] 0           forbidden_zero_bit                                          0 = 0
[trace_headers @ 000002b4a4fd34c0] 1           nal_unit_type                                          010101 = 21
[trace_headers @ 000002b4a4fd34c0] 7           nuh_layer_id                                           000000 = 0
[trace_headers @ 000002b4a4fd34c0] 13          nuh_temporal_id_plus1                                     001 = 1
[trace_headers @ 000002b4a4fd34c0] 16          first_slice_segment_in_pic_flag                             1 = 1
[trace_headers @ 000002b4a4fd34c0] 17          no_output_of_prior_pics_flag                                0 = 0
[trace_headers @ 000002b4a4fd34c0] 18          slice_pic_parameter_set_id                                  1 = 0
[trace_headers @ 000002b4a4fd34c0] 19          slice_type                                                011 = 2
[trace_headers @ 000002b4a4fd34c0] 22          slice_pic_order_cnt_lsb                              11111010 = 250
[trace_headers @ 000002b4a4fd34c0] 30          short_term_ref_pic_set_sps_flag                             0 = 0
[trace_headers @ 000002b4a4fd34c0] 31          num_negative_pics                                       00101 = 4
[trace_headers @ 000002b4a4fd34c0] 36          num_positive_pics                                           1 = 0
[trace_headers @ 000002b4a4fd34c0] 37          delta_poc_s0_minus1[0]                                    010 = 1
[trace_headers @ 000002b4a4fd34c0] 40          used_by_curr_pic_s0_flag[0]                                 0 = 0
[trace_headers @ 000002b4a4fd34c0] 41          delta_poc_s0_minus1[1]                                    010 = 1
[trace_headers @ 000002b4a4fd34c0] 44          used_by_curr_pic_s0_flag[1]                                 0 = 0
[trace_headers @ 000002b4a4fd34c0] 45          delta_poc_s0_minus1[2]                                    011 = 2
[trace_headers @ 000002b4a4fd34c0] 48          used_by_curr_pic_s0_flag[2]                                 0 = 0
[trace_headers @ 000002b4a4fd34c0] 49          delta_poc_s0_minus1[3]                                    010 = 1
[trace_headers @ 000002b4a4fd34c0] 52          used_by_curr_pic_s0_flag[3]                                 0 = 0

...

Virinext Bitstream Analyzer

プロプライエタリ.

コンテナファイルや elementary stream を入力として, NAL unit たちの情報を閲覧するためのツール.
AAC などオーディオストリームにも対応している.

有料だがデモ版で最初の10フレームまで機能を利用できる:
Virinext Bitstream Analyzer

hevcesbrowser

bitstream 内の NAL unit たちの情報を見ることができる.
Virinext Bitstream Analyzer と違い, コンテナファイルは開けないようなので注意.
ffmpeg などで動画コンテナから bitstream を抽出する必要がある.

GitlHEVCAnalyzer

CTU(Coding Tree Unit) たちなどの可視化ができるとのことだが,
こちらの H.265/HEVC ではクラッシュしてしまい動作を確認できず.

参考資料

Vulkan Video の使用例

どれも希少なもの.

てっくま氏 によるコードとスライド

  • H.264/AVC のデコードのサンプル:

記事

  • Vulkan Video Decoding – Wicked Engine8
    Wicked Engine での H.264 のデコードの実装についての記事.

  • Lynne's compiled musings | Vulkan Video decoding
    ffmpeg の Vulkan Video デコード/エンコード の開発者である Lynne 氏による記事.
    プラットフォームやベンダ独自のデコード/エンコード API などエコシステムの状況についても詳しい.

  • Vulkan Video Decode: First Frames
    H.264 の最初のフレームをデコードできるまでの苦労が綴られている.

Khronos Group からの Vulkan Video の情報

動画の符号化の基本

H.265 の bitstream, CVS, GOP, access unit の構造

H.265 の Motion compensation (動き補償) や reference picture について

  • Motion compensation in HEVC. he HEVC standard implements all of the… | by Elecard Company | Medium5

byte stream について

  • H.264 - redstrange Wiki*
    H.264/AVC の byte stream の解説なので H.265 のそれとは異なるが, 参考になる.

MP4 について

その他

  1. ITU-T. H.265 High Efficiency Video Coding (September, 2023) 2 3 4 5

  2. ISO/IEC 14496-15:2022 - Information technology — Coding of audio-visual objects — Part 15: Carriage of network abstraction layer (NAL) unit structured video in the ISO base media file format

  3. HEVC bitstream formats - Virinext Bitstream Analyzer 2

  4. What's the difference between 'hvc1' and 'hev1' HEVC codec tags for fMP4? - Bitmovin Community

  5. Motion compensation in HEVC. he HEVC standard implements all of the… | by Elecard Company | Medium 2

  6. 42.13.1. H.265 Decode Bitstream Data Access, Vulkan® 1.3.285 - A Specification (with all registered extensions)

  7. H.264 について(まとめ) 【H.264/Annex B/NAL file format/AVC/rtmp】 - モノトーンの伝説日記

  8. Vulkan Video Decoding – Wicked Engine 2

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?