VK_KHR_video_decode_av1 で GPU 上の AV1 をデコードする方法

H.264 と HEVC のデコード(VK_KHR_video_decode_h264/h265)は レガシーコーデックをカバーする。AV1(VK_KHR_video_decode_av1)は モダンなものだ。YouTube/Netflix が配信しているコーデックで、その 8Kストリームはソフトウェアデコーダーを窒息させる。Vulkan 経路は H.264 と同じパターンだ。ビデオセッション、DPB、 vkCmdDecodeVideoKHR。AV1 固有のパラメータは VkVideoDecodeAV1* 構造体を通して配管される。

セッションセットアップ

session.cpp
// コーデック固有のプロファイル + ケーパビリティ:
vk::VideoDecodeAV1ProfileInfoKHR av1prof{};
av1prof.stdProfile = STD_VIDEO_AV1_PROFILE_MAIN;   // または _HIGH/_PROFESSIONAL
av1prof.filmGrainSupport = VK_FALSE;              // フィルムグレイン合成を
                                                  // GPU で?高コスト。

vk::VideoProfileInfoKHR prof{};
prof.pNext = &av1prof;
prof.videoCodecOperation =
    vk::VideoCodecOperationFlagBitsKHR::eDecodeAV1;

auto caps = phys.getVideoCapabilitiesKHR(prof);
// caps.maxDpbSlots, caps.maxActiveReferencePictures,
//   caps.maxCodedExtent, …

vk::VideoSessionCreateInfoKHR vs{};
vs.queueFamilyIndex = decQueue;   // eVideoDecodeKHR を持つキュー
vs.pVideoProfile    = &prof;
vs.pictureFormat    = vk::Format::eG8B8R82Plane420Unorm;
vs.referencePictureFormat = vs.pictureFormat;
vs.maxDpbSlots      = caps.maxDpbSlots;
vs.maxActiveReferencePictures = caps.maxActiveReferencePictures;
auto sess = dev.createVideoSessionKHR(vs);

フレームのデコード

decode.cpp
// AV1 のフレームパラメータは STD ヘッダーから来る
// (vulkan_video_codec_av1std.h — StdVideoAV1SequenceHeader、…):
vk::VideoDecodeAV1PictureInfoKHR pic{};
pic.pStdPictureInfo = &stdPic;    // パース済みフレームヘッダー
pic.pNext           = nullptr;
pic.pReferenceNameSlotIndices…  // どの DPB スロットが参照か

vk::VideoDecodeInfoKHR dec{};
dec.pNext = &pic;
dec.srcBuffer          = bitstreamBuf;   // OBU フレームデータ
dec.srcBufferOffset    = off;
dec.srcBufferRange     = len;
dec.dstPictureResource = {imgView, eVideoDecodeDstKHR};
dec.referenceSlotCount = n;
dec.pReferenceSlots    = refs;           // VkVideoReferenceSlotInfoKHR

cmd.beginVideoCodingKHR(begin);
cmd.decodeVideoKHR(dec);                 // 本命の呼び出し
cmd.endVideoCodingKHR({});

H.264 との違い

diffs.txt
セグメンテーションマップ — AV1 にはセグメントごとのフィーチャーフラグ
                          がある。デコーダーがそれを出力する。
                          caps.needsExtraSourceImage?/セグメント
                          マップを確認
フィルムグレイン        — AV1 の代名詞的機能:デコード後に適用される
                          合成グレイン。filmGrainSupport=false =
                          デコーダーはグレインをオフのまま。
                          true = GPU がやる(追加パス)
タイル/ストリップデコード — AV1 のタイルモデルはデコードコマンドの
                          スライス/セグメント分割へのマップが異なる
フレームヘッダーパース  — OBU ヘッダーは依然として CPU 側でパースし、
                          StdVideoAV1SequenceHeader/FrameHeader を渡す

実用的な道 — FFmpeg に任せる

ffmpeg.sh
# FFmpeg ≥7 は hwcontext_vulkan デコードで AV1 を公開:
ffmpeg -init_hw_device vulkan=vk0 -hwaccel vulkan \
       -hwaccel_output_format vulkan -i in.av1.webm -f null -
#   → 完全に GPU 上。フレームは AV_PIX_FMT_VULKAN の VkImage として届く

AVVkFrame が VkImage を直接渡す仕組みは hwcontext_vulkan の 記事を参照。同じ配管でコーデック非依存だ。

実践的なルール

  • AV1 にはデコード対応キューが必要だ。 QueueFlagBitsKHR::eVideoDecodeKHR と eDecodeAV1 を指定した vkGetPhysicalDeviceVideoCapabilitiesKHR を確認する。一部の ドライバーではサポートが遅れて入った。
  • フィルムグレイン(filmGrainSupport)が高コストな部分だ。 デコード後のソフトウェアグレインパスの方がドライバーに頼むより 安いことが多い。
  • AV1 の参照モデル(8つの参照フレーム、show_existing_frame の技)は、 AV1 の maxActiveReferencePictures が H.264 より大きいことを 意味する。2をハードコードしない。
  • セッションパラメータ(VkVideoDecodeAV1SessionParametersCreateInfoKHR) はシーケンスヘッダーを運ぶ。OBU を自分でパースするか FFmpeg の av1 パーサーを使う。
  • 実際の使用には hwcontext_vulkan と組み合わせる。AV1 デコード配管を 手で実装するのは数日の作業だ。FFmpeg の hwaccel がすでに解決している。

同じカテゴリの類似記事をチェック: Vulkan C++