VAAPI でデコードしてその結果をインポートするパスは機能するが、Vulkan
にはネイティブな動画デコード API がある。VK_KHR_video_decode_queue と
VK_KHR_video_decode_h264(および h265、av1)だ。セッション/DPB/
vkCmdDecodeVideoKHR のパターンは AV1 の記事と同じで、H.264 ではコーデック
関連の構造体が AV1* の代わりに VkVideoDecodeH264* になる。これにより
VAAPI レイヤーが完全に不要になる。VA-API コンテキストも DMA-Buf
エクスポートも不要で、デコードされたピクチャはすでに VkImage だ。
なぜ VAAPI+インポートではなくネイティブなのか
compare.txt
VAAPI パス: FFmpeg hwaccel → VASurface → av_hwframe_map → DMA-Buf
→ vkImportMemoryFdKHR → VkImage (API が2つ、デバイス
コンテキストが2つ、modifier/foreign-queue の一連の処理)
Vulkan パス: avcodec がビットストリームを自分のコードに渡す →
vkCmdDecodeVideoKHR に供給 → 出力 VkImage(API は1つ、
フレームはすでに Vulkan 内 — インポートは一切不要)VAAPI パスが存在するのは FFmpeg がすでにそれを実装しているからだ。 デコードループを自分で制御できるなら、ネイティブパスの方がクリーンだ。
セッション
session.cpp
vk::VideoDecodeH264ProfileInfoKHR h264{};
h264.stdProfileIdc = STD_VIDEO_H264_PROFILE_IDC_HIGH;
h264.pictureLayout = vk::VideoDecodeH264PictureLayoutFlagBitsKHR
::eProgressive;
vk::VideoProfileInfoKHR prof{};
prof.pNext = &h264;
prof.videoCodecOperation =
vk::VideoCodecOperationFlagBitsKHR::eDecodeH264;
prof.chromaSubsampling = vk::VideoChromaSubsamplingFlagBitsKHR::e420;
// AV1 の記事と同様:eVideoDecodeKHR 対応のキューファミリで
// vkCreateVideoSessionKHR を呼ぶ
デコードコマンド
decode.cpp
vk::VideoDecodeH264PictureInfoKHR pic{};
pic.pStdPictureInfo = &stdPic; // StdVideoH264PictureInfo
pic.sliceCount = nSlices;
pic.pSliceOffsets = sliceOffsets; // srcBuffer へのバイトオフセット
vk::VideoDecodeInfoKHR dec{};
dec.pNext = &pic;
dec.srcBuffer = bitstreamBuf; // 生の NAL/フレームデータ
dec.dstPictureResource = {dstView, eVideoDecodeDstKHR};
dec.referenceSlotCount = nRefs;
dec.pReferenceSlots = refs;
cmd.beginVideoCodingKHR(begin);
cmd.decodeVideoKHR(dec);
cmd.endVideoCodingKHR({});自分でパースする必要があるもの
parse.txt
CPU 側で自分で用意する必要があるもの:
StdVideoH264SequenceParameterSet (SPS)
StdVideoH264PictureParameterSet (PPS)
StdVideoH264PictureInfo (フレームごとのヘッダー)
srcBuffer へのスライスごとのオフセット
FFmpeg/libavcodec の h264 パーサーはこれらをすべて生成する。
実践的なパターンは「FFmpeg に demux とパースを任せ、パース済みの
ヘッダーと NAL を Vulkan デコーダーに渡す」というものだ。AV1 との DPB の違い
- H.264 の参照モデルはスライディングウィンドウ / MMCO — 参照
スロットはフレームごとに変わり、AV1 の 8 スロットモデルと同様に
VkVideoReferenceSlotInfoKHRで管理する。 field_pic_flag/インターレースの扱い — H.264 にはインターレース モードがある(eFieldInterlacedレイアウト)。AV1 はプログレッシブのみだ。- H.264 の
maxDpbSlotsは典型的には 16 — AV1 の 17+8 より小さい。
実践的なルール
- まずキューのケイパビリティを確認する。
QueueFlagBitsKHR::eVideoDecodeKHRとeDecodeH264を指定したvkGetPhysicalDeviceVideoCapabilitiesKHRだ。 すべての GPU がデコードキューを公開しているわけではない (VAAPI フォールバックでカバーできる)。 - デコードセッションには SPS/PPS を保持する
VkVideoSessionParametersKHRが必要になる。ストリームのパラメータセットが変わったら更新する。 - 出力は自分で確保した
VkImageに書き込まれる。pictureFormat(例:eG8B8R82Plane420Unorm)は DPB フォーマットと一致させ、そのイメージをdstPictureResourceとして直接バインドする。デコード済みフレームは そのままテクスチャになっている。 - インターレース H.264 が厄介な点だ。
pictureLayoutフラグはストリームと 一致させる必要がある。プログレッシブのみのストリームなら簡単だ。 - VAAPI との比較:ネイティブ Vulkan デコードでは DMA-Buf エクスポートと foreign-queue 転送が不要になる。トレードオフとして、FFmpeg が処理して いた DPB とパラメータのパースを自分で引き受けることになる。
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください