Skip to main content

Decode & HW Acceleration

vv_media::Decoder decodes one video stream, sequentially (next_frame) or after seek_to_time. Every source, software or hardware, ends up as the same FrameYuv420, so the cache and the compositor never know which path produced a frame.

Choosing the GPU

The Playback setting is resolved once (off the UI thread, in vv-app/src/hw_decode.rs) into an ordered list of devices; every decoder tries them in that order. Only devices that actually open are kept.

SettingDevices tried (Linux)
AutoHybrid laptop (integrated + discrete): Vulkan on the integrated GPU, so the discrete one is not woken. Otherwise NVDEC if an NVIDIA GPU exists, then Vulkan on the first GPU.
NVDECCUDA
VulkanVulkan on the discrete GPU, else the first one
Offnone: software

On macOS the only choice is VideoToolbox (Auto) or Off. A Vulkan device without the VK_KHR_video_decode_queue extension (e.g. Asahi's Honeykrisp today) is rejected up front, so in practice hardware decoding works on x86 GPUs only.

Open and fallback

decode-open.png
  • One device per backend per process, created on first use and shared by all decoders.
  • Surface budget: each HW decoder leases its worst-case surfaces (16-frame DPB + 2 extra + 1) from a global budget, by default ⅛ of physical RAM. HwPriority::Low decoders (window behind the playhead, proxy generation) may use only half of it, so they cannot starve the playback ahead.
  • Silent fallback detection: FFmpeg falls back to software on its own when a hwaccel refuses a stream. The decoder checks each frame's pixel format; on a mismatch or an error the media is added to hw::FAILED, reopened in software and re-seeked to where it was. It never goes back to HW in this process.
  • HW decoders run with one thread; software decoders use FFmpeg frame threading, which makes the decode from keyframe to seek target much faster.

Seeking

seek_to_time guarantees landing on a keyframe at or before the target: the landing frame is decoded immediately, and if it is past the target the seek is retried further back, doubling the step (up to 20 tries). skip_before then lets the caller fast-forward to the wanted frame without converting the frames in between, and non-reference frames there are not even decoded.

Output format

SourceFrameYuv420 chroma
HW frames, NV12/P010interleaved (NV12), 10-bit truncated to 8
formats with alphaplanar + alpha plane (YUVA420P)
everything elseplanar YUV420P

No conversion runs when the frame is already in the target format. Matrix (BT.601/709/2020) and range come from the source; a missing matrix is guessed from the height (≥ 720 rows → BT.709). RGB-to-YUV conversion stays in the GPU shader. VFR streams are conformed to a contiguous CFR index: holes repeat the last frame (shared, not copied), and frames denser than the rate are dropped so the index stays tied to the pts.