はじめに
グラフィックス全般 Advent Calendar 2024 10日目の記事になります.
所感
非常に低レイヤ.
MP4 から demux した動画ストリームを Vulkan のコマンドに渡して~ のように想像していると, かなり落胆することになると思われる.
デコードに用いる Vulkan Video のオブジェクトを作ったりコマンドを実行させたりする際に,
驚くほど多くの H.265 固有のパラメータを渡す必要がある.
これらは基本的には自前で H.265 の bitstream からパースして得る必要がある.
H.265 の仕様書1と睨めっこしながらかなり苦労して Rust 用のパーサを用意した.
パーサとして完成とは言えないが, Main や Main 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 固有のパラメータ
VkVideoDecodeH265PictureInfoKHR
を VkVideoDecodeInfoKHR
の 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 として使用する
-
vkCmdBeginVideoCodingKHR
でVkVideoReferenceSlotInfoKHR::pPictureResource
をnullptr
(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 の仕様書1 の Annex 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 Reference は StdVideoDecodeH265PictureInfoFlags::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_headers
で NAL unit たちをダンプすることができる:
ffmpeg -i video.mp4 -c copy -bsf:v trace_headers -f null - 2> 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 などオーディオストリームにも対応している.
hevcesbrowser
bitstream 内の NAL unit たちの情報を見ることができる.
Virinext Bitstream Analyzer と違い, コンテナファイルは開けないようなので注意.
ffmpeg などで動画コンテナから bitstream を抽出する必要がある.
GitlHEVCAnalyzer
CTU(Coding Tree Unit) たちなどの可視化ができるとのことだが,
こちらの H.265/HEVC ではクラッシュしてしまい動作を確認できず.
参考資料
Vulkan Video の使用例
どれも希少なもの.
てっくま氏 によるコードとスライド
- H.264/AVC のデコードのサンプル:
-
コンパクトなVulkan入門書籍/ Vulkan Videoへのチャレンジ - Vulkan Meetup Japan 2024
Vulkan Video の概要, その難しさの特徴について.
記事
-
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 の情報
-
Vulkan Video Core API Introduction
VPS, SPS, PPS の関係もわかりやすく示されている (Page 7).
動画の符号化の基本
-
Video compression basics – RasterGrid
Vulkan Video の仕様の策定にも関わっている RasterGrid による, 動画圧縮の基礎についての解説.
H.265 の bitstream, CVS, GOP, access unit の構造
- 【HEVC学習】2. HEVCのビットストリーム構成 | Kirakuni Tech Diary
- 情報源符号化部 H.265 | MPEG-H HEVC 規格の概要 (社団法人 電波産業会)
- HEVC(MPEG-H/ITU-T H.265)技術解説 - ビットストリームの構成とその機能 - 映像情報メディア学会誌 Vol. 67, No. 7
-
Overview of the High Efficiency Video Coding (HEVC) Standard | IEEE Journals & Magazine | IEEE Xplore
論文なのでかなりフォーマルだけど.
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 について
-
フレームレートとタイムコード、タイムスタンプ #Video - Qiita
正しい時間でフレームを見せるにはこの辺りを気にする必要がある.
その他
-
H.265/HEVC特許暗黒時代 #コーデック - Qiita
H.265 の特許周りのドロドロした状況について.
-
ITU-T. H.265 High Efficiency Video Coding (September, 2023) ↩ ↩2 ↩3 ↩4 ↩5
-
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 ↩
-
What's the difference between 'hvc1' and 'hev1' HEVC codec tags for fMP4? - Bitmovin Community ↩
-
Motion compensation in HEVC. he HEVC standard implements all of the… | by Elecard Company | Medium ↩ ↩2
-
42.13.1. H.265 Decode Bitstream Data Access, Vulkan® 1.3.285 - A Specification (with all registered extensions) ↩
-
H.264 について(まとめ) 【H.264/Annex B/NAL file format/AVC/rtmp】 - モノトーンの伝説日記 ↩