FFmpeg 7+ には第一級の Vulkan パスがある:-hwaccel vulkan は
VK_KHR_video_decode_queue 経由で H.264/HEVC/AV1 をデコードし、
フレームは FFmpeg 自身の AVHWDeviceContext 内で VkImage
として存在する。VA-API なし、DMA-Buf エクスポートの手間なし。
アプリがすでに VkDevice を所有しているなら、それを共有する
(または2つの間でフレームをマップする)ことができる。
コマンドラインでの証明
cli.sh
# 完全に GPU 上でデコード——出力はデバイスに留まる:
ffmpeg -init_hw_device vulkan=vk0 -hwaccel vulkan \
-hwaccel_output_format vulkan \
-i input.mp4 -f null -
# デコード → vulkan フィルタ → h264_vulkan に再エンコード:
ffmpeg -init_hw_device vulkan=vk0 -hwaccel vulkan -i in.mp4 \
-vf "scale_vulkan=w=1280:h=720" \
-c:v h264_vulkan -y out.mp4scale_vulkan、avgblur_vulkan、chromakey_vulkan、hflip_vulkan、
overlay_vulkan は実際の vkCmdDispatch/vkCmdDraw として動作
する。ホストコピーなし。エンコーダ h264_vulkan/hevc_vulkan
(FFmpeg 7.1+/8)は VK_KHR_video_encode_queue を使う。
API 側
device.cpp
// デバイスを作成(または av_hwdevice_ctx_create_derived で自分のを再利用):
AVBufferRef* hwDev = nullptr;
av_hwdevice_ctx_create(&hwDev, AV_HWDEVICE_TYPE_VULKAN,
/*device*/ nullptr, /*opts*/ nullptr, 0);
// → FFmpeg がデフォルトアダプタを選ぶ。固定するには "vulkan=0" /device アドレスを渡す
// デコードでフレームは AV_PIX_FMT_VULKAN として届く:
AVFrame* f = av_frame_alloc();
avcodec_receive_frame(dec, f);
if (f->format == AV_PIX_FMT_VULKAN) {
AVVkFrame* vkF = (AVVkFrame*)f->data[0];
VkImage img = vkF->img[0]; // プレーンごとの VkImage
VkSemaphore sem = vkF->sem[0]; // デコード完了時
VkSemaphore sem2= vkF->sem[1]; // アプリが待つべき時
VkImageLayout layout = vkF->layout[0];
// Y/U/V 用プレーン 0..2 — 同じ VkImage かプレーンごとに分離
}AVVkFrame が鍵となる型:VkImage、レイアウト、2つのセマフォを
所有する。sem[0] は「デコード完了」をシグナルし、sem[1] は
アプリの読み取りが終わったときにあなたがシグナルし、FFmpeg が
フレームスロットを再利用できるようにする。
レンダラーとの共有
interop.txt
FFmpeg vk デバイス ─┐
├─ 同じ VkDevice(派生コンテキスト)→ イメージ直接使用
自分の VkDevice ──┘ ↓ eShaderReadOnlyOptimal へのレイアウト遷移
または av_hwframe_map → AV_PIX_FMT_DRM_PRIME → dmabuf
→ 自分のレンダラーで vkImportMemoryFdKHR(VA-API パス)2つの統合レベル:
- 派生デバイス——自分の
VkDeviceからav_hwdevice_ctx_create_derived(FFmpeg 7.1+)→ フレームは あなたのデバイスメモリ空間にある。eShaderReadOnlyOptimalに遷移し、バインドしてサンプルする。ゼロコピー。 - マップパス——
av_hwframe_map(VULKAN → DRM_PRIME)が DMA-Buf fd をエクスポート → レンダラーにインポート。 デバイス/プロセス間で動作する。
map.cpp
AVFrame* drm = av_frame_alloc();
av_hwframe_map(drm, vkFrame, AV_HWFRAME_MAP_READ);
// drm->data[0] = AVDRMFrameDescriptor* → VA-API 投稿と同じパス
機能プローブ
probe.cpp
// ドライバーはビデオキューを公開しなければならない:
auto qf = dev.getQueueFamilyProperties();
bool hasDec = std::ranges::any_of(qf, [](auto& q){
return bool(q.queueFlags & vk::QueueFlagBitsKHR::eVideoDecodeKHR);});
// コーデックレベル:vkGetPhysicalDeviceVideoCapabilitiesKHR と
// VkVideoProfileInfoKHR{videoCodecOperation = eDecodeH264/…}
実践的なルール
hwaccel_output_format vulkanが全体の要点だ。それなしでは FFmpeg はhwdownloadにフォールバックし、とにかく CPU コピーの代金を払う。AVVkFrameセマフォはデコーダと共有される。読み取りが 本当に終わったときだけsem[1]をシグナルする。早いシグナル = 破れたフレーム。- レイアウトはデコーダが残したもの(通常
eVideoDecodeDstKHR)。 サンプリング前に遷移する。eGeneralを仮定しない。 h264_vulkan/hevc_vulkanでのエンコードにはVK_KHR_video_encode_queueが必要。サポートを確認する。 デコードより稀だ。- 弱い/モバイルドライバーでは
vulkanhwaccel が VA-API より 遅いことがある。実績のあるデコードパスをゼロコピー末端と 交換する。両方を測定する。
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください