From f6e5277cbb0c6ae93fc733d6101b46f937d1e48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E8=B6=85=E8=B6=85=E8=B6=85=E8=B6=85=E7=BA=A7?= =?UTF-8?q?=E5=96=9C=E6=AC=A2=E4=BD=A0=E7=9A=84=E8=BE=BE=E5=A6=AE=E5=A8=85?= <176143450+My-Denia@users.noreply.github.com> Date: Wed, 11 Nov 2026 17:02:15 +0800 Subject: [PATCH] fix(compositor): decode 10-bit and non-4:2:0 H.264 in software on Windows D3D11VA was chosen per codec, but FFmpeg offers no D3D11 surface for H.264 High 10 or 4:2:2/4:4:4, so get_hw_format rejected every format and send_packet failed with Invalid data; preview and export could not read the file. Choose D3D11VA per stream: only 8-bit 4:2:0 H.264 (or an unknown format) stays on hardware; the rest takes the existing software CpuFrames path. --- crates/compositor/src/cpu_frames_windows.rs | 6 ++-- crates/compositor/src/pipeline_windows.rs | 38 ++++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/crates/compositor/src/cpu_frames_windows.rs b/crates/compositor/src/cpu_frames_windows.rs index ea0e921fbe..b5a240bbec 100644 --- a/crates/compositor/src/cpu_frames_windows.rs +++ b/crates/compositor/src/cpu_frames_windows.rs @@ -24,9 +24,9 @@ use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_NV12, DXGI_SAMPLE_DESC} /// Le flag d'algorithme de swscale. Bindgen ne génère pas les `SWS_*` d'algorithme (des /// macros), et leurs valeurs sont figées par l'ABI de libswscale. `POINT` (plus proche -/// voisin) est le choix honnête : la conversion se fait à dimensions ÉGALES, donc aucun -/// rééchantillonnage n'a lieu — seul le convertisseur de format travaille, et le filtre -/// choisi n'a aucun effet sur la sortie. +/// voisin) est le choix honnête : la conversion se fait à dimensions ÉGALES, donc la luma +/// n'est jamais rééchantillonnée. Seule exception : une source 4:2:2 ou 4:4:4 (H.264 +/// importé) perd sa chroma pleine en passant en NV12, et POINT la décime par voisin. const SWS_POINT: i32 = 0x10; /// Source de frames du backend CPU, attachée à un `Decoder` quand `Backend::Cpu`. diff --git a/crates/compositor/src/pipeline_windows.rs b/crates/compositor/src/pipeline_windows.rs index ea6e658b35..4c59cf73c4 100644 --- a/crates/compositor/src/pipeline_windows.rs +++ b/crates/compositor/src/pipeline_windows.rs @@ -380,6 +380,21 @@ fn d3d11va_for_codec(codec_id: AVCodecID::Type) -> bool { codec_id == AVCodecID::AV_CODEC_ID_H264 } +/// Per-stream refinement of `d3d11va_for_codec`: FFmpeg only offers a D3D11 +/// NV12 surface for 8-bit 4:2:0 H.264. High 10 (yuv420p10le) and 4:2:2 / 4:4:4 +/// get none, `get_hw_format` rejects everything and `send_packet` fails with +/// AVERROR_INVALIDDATA, so those streams take the software `CpuFrames` path. +/// An unknown pixel format (`AV_PIX_FMT_NONE`) keeps the hardware path. +fn d3d11va_for_stream(codec_id: AVCodecID::Type, format: AVPixelFormat::Type) -> bool { + d3d11va_for_codec(codec_id) + && matches!( + format, + AVPixelFormat::AV_PIX_FMT_NONE + | AVPixelFormat::AV_PIX_FMT_YUV420P + | AVPixelFormat::AV_PIX_FMT_YUVJ420P + ) +} + unsafe fn require_decoder_id( codec_id: AVCodecID::Type, ) -> Result<(*const AVCodec, *mut AVCodecContext)> { @@ -847,10 +862,12 @@ impl Decoder { )?; allow_d3d11va_h264_baseline(resources.dctx); - // Hardware D3D11VA is the H.264 capture path. WARP has no video decoder - // (`tests/warp_device_cannot_decode.rs`). AV1/VP9 (legacy WebMs, #554) - // take the same software CpuFrames axis as Backend::Cpu. - let want_hw = gpu.backend != Backend::Cpu && d3d11va_for_codec(codec_id); + // Hardware D3D11VA is the H.264 8-bit 4:2:0 capture path. WARP has no video + // decoder (`tests/warp_device_cannot_decode.rs`). AV1/VP9 (legacy WebMs, #554) + // and H.264 High 10 / 4:2:2 / 4:4:4 (no D3D11 surface exists for them) take + // the same software CpuFrames axis as Backend::Cpu. + let want_hw = gpu.backend != Backend::Cpu + && d3d11va_for_stream(codec_id, (*codecpar).format as AVPixelFormat::Type); let cpu = if want_hw { None } else { @@ -2168,6 +2185,19 @@ mod tests { assert!(!d3d11va_for_codec(AVCodecID::AV_CODEC_ID_VP9)); } + #[test] + fn d3d11va_is_8bit_420_h264_only() { + use AVPixelFormat::*; + let h264 = AVCodecID::AV_CODEC_ID_H264; + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_YUV420P)); + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_YUVJ420P)); + assert!(d3d11va_for_stream(h264, AV_PIX_FMT_NONE)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV420P10LE)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV422P)); + assert!(!d3d11va_for_stream(h264, AV_PIX_FMT_YUV444P)); + assert!(!d3d11va_for_stream(AVCodecID::AV_CODEC_ID_AV1, AV_PIX_FMT_YUV420P)); + } + #[test] fn require_decoder_rejects_none() { let err = unsafe { require_decoder_id(AVCodecID::AV_CODEC_ID_NONE) }