FFmpeg VA-API ile HEVC Main10 decode ve frame'leri Vulkan'a zero-copy import

Post 57. Post 49 8-bit H.264’ü NV12 plane’leri olarak import etmişti. Çoğu modern IP kamera ve HDR içerik bunun yerine HEVC Main10 sunar — P010 olarak paketlenmiş 10-bit luma/chroma. İyi haber: pipeline aynı — sadece DRM katman formatları ve Vulkan formatları değişir.

example.sh
$ ./app --headless out.ppm
decoded 15 frames, 1280x720 hw=vaapi sw=p010le
layer0 fmt=R16  planes=1: off=0 pitch=2560
layer1 fmt=GR32 planes=1: off=1966080 pitch=2560
surface is P010 (10-bit) -> R16 + R16G16
wrote out.ppm

Example: VulkanExamples/p57_hevc

8-bit’e göre gerçekte ne değişir

VA-API mantıksal plane başına bir DRM katmanı export eder (Mesa/GBM’deki gibi tek bir multi-plane katman değil). Katman fourcc’si bit derinliğini söyler:

sw_formatlayer0 (luma)layer1 (UV)VkImage formatları
nv12 (8-bit)DRM_FORMAT_R8DRM_FORMAT_GR88R8Unorm + R8G8Unorm
p010le (10-bit)DRM_FORMAT_R16DRM_FORMAT_GR1616R16Unorm + R16G16Unorm

P010 her 10-bit örneği 16 bit içinde MSB-hizalı (bit 15..6) saklar. R16Unorm olarak import etmek otomatik [0,1] normalize eder — shader hiç değişmez:

example.txt
float Y = texture(texY, vUV).r;          // R16_UNORM, zaten [0,1]
vec2  UV = texture(texUV, vUV).rg;       // R16G16_UNORM
vec3 yuv = vec3(Y, UV.x, UV.y) - vec3(0.0625, 0.5, 0.5);
vec3 rgb = clamp(mat3( 1.164,  1.164,  1.164,
                       0.000, -0.392,  2.017,
                       1.596, -0.813,  0.000) * yuv, 0.0, 1.0);

Chroma image’ı hâlâ W/2 × H/2 — ama her texel artık 4 bayt (R16G16), bu yüzden aynı piksel boyutlarında bile pitch NV12’ye kıyasla iki katına çıkar.

Descriptor’dan otomatik tespit

main.cpp (format select)
auto* fctx = reinterpret_cast<AVHWFramesContext*>(
    frame->hw_frames_ctx->data);
// fctx->sw_format == AV_PIX_FMT_P010 veya AV_PIX_FMT_NV12

// ya da doğrudan DRM descriptor'dan:
const bool is10 = desc->layers[0].format == DRM_FORMAT_R16;
const vk::Format fmtY  = is10 ? vk::Format::eR16Unorm
                              : vk::Format::eR8Unorm;
const vk::Format fmtUV = is10 ? vk::Format::eR16G16Unorm
                              : vk::Format::eR8G8Unorm;

frame->hw_frames_ctx (bir AVHWFramesContext*) sw_format‘ı — gerçek surface yerleşimini — açığa çıkarır; AVFrame‘in kendisinin taşımadığı FFmpeg sürümlerinde bile.

Test klibi

example.sh
ffmpeg -f lavfi -i "testsrc2=size=1280x720:rate=30" -t 0.5 \
    -c:v libx265 -pix_fmt yuv420p10le -preset medium -crf 22 \
    -x265-params keyint=15 -f hevc test.h265

Post 49’dan hâlâ geçerli tuzaklar

  • mapped->format = AV_PIX_FMT_DRM_PRIME — av_hwframe_map‘ten önce.
  • arrayPitch/depthPitch sıfır değil, plane boyutu olmalı.
  • max(req.size, offset + planeSize) ayırın — UV plane dma-buf içinde sıfır olmayan bir offset’te durur.
  • eDeviceLocal istemeyin — dma-buf belleği host-visible GTT olabilir.
  • Aynı GPU: VA-API render node’u (/dev/dri/renderD12x) Vulkan’ın seçtiği fiziksel cihaza ait olmalı. Vendor’lar arası bir modifier sessizce import olur ve çöp örnekler.
  • Modifier-tiled image’lar: yalnızca eGeneral (veya ePresentSrcKHR) layout.

VA-API yoksa

10-bit yazılım decode yuv420p10le/p010le‘dir — sırasıyla LSB-hizalı vs MSB-hizalı. p010le‘nin bir staging-buffer upload’u R16Unorm/R16G16Unorm image’a aynı shader’la çalışır; sadece kaynak-kopya döngüsü farklıdır. Bu bir kamera duvarının istediği zarif-degradasyon yoludur: bir kez uyar, render’a devam et.


Check out similar posts by category: C++ Vulkan