diff --git a/lightningbeam-ui/lightningbeam-core/src/video.rs b/lightningbeam-ui/lightningbeam-core/src/video.rs index 31c31ce..8fb8552 100644 --- a/lightningbeam-ui/lightningbeam-core/src/video.rs +++ b/lightningbeam-ui/lightningbeam-core/src/video.rs @@ -460,6 +460,27 @@ impl VideoDecoder { if gpu_out { // Hardware + GPU consumer: import the VAAPI surface as wgpu NV12 textures // (no CPU copy). + // VAAPI hw frames often don't carry the stream's colour tags, so the + // importer (which only sees the frame) would mis-detect transfer/gamut. + // Copy the authoritative values from the codec context (parsed from the + // bitstream) onto the frame when it left them unspecified. + unsafe { + use ffmpeg::ffi::*; + let fp = frame.as_mut_ptr(); + let cp = decoder.as_ptr(); + if (*fp).color_trc == AVColorTransferCharacteristic::AVCOL_TRC_UNSPECIFIED { + (*fp).color_trc = (*cp).color_trc; + } + if (*fp).color_primaries == AVColorPrimaries::AVCOL_PRI_UNSPECIFIED { + (*fp).color_primaries = (*cp).color_primaries; + } + if (*fp).colorspace == AVColorSpace::AVCOL_SPC_UNSPECIFIED { + (*fp).colorspace = (*cp).colorspace; + } + if (*fp).color_range == AVColorRange::AVCOL_RANGE_UNSPECIFIED { + (*fp).color_range = (*cp).color_range; + } + } let importer = self.importer.as_ref().unwrap(); match unsafe { importer.import(frame.as_mut_ptr() as *mut std::ffi::c_void) } { Some(gpu) => { diff --git a/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs b/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs index f3226d6..2f349f9 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/hw_video.rs @@ -17,6 +17,8 @@ use std::sync::{Arc, Mutex}; struct SharedHwImporter { device: wgpu::Device, adapter: wgpu::Adapter, + /// Log the detected colour info once (under LB_VIDEO_DEBUG) rather than per frame. + logged: std::sync::atomic::AtomicBool, } impl HwVideoImporter for SharedHwImporter { @@ -99,6 +101,16 @@ impl HwVideoImporter for SharedHwImporter { }; let coeffs = ycbcr_coeffs(kr, kb); + if std::env::var("LB_VIDEO_DEBUG").is_ok() + && !self.logged.swap(true, std::sync::atomic::Ordering::Relaxed) + { + eprintln!( + "[hw_video] {}x{} ten_bit={} full_range={} colorspace={:?} primaries={:?} trc={:?}", + width, height, ten_bit, full_range, + (*frame).colorspace, (*frame).color_primaries, (*frame).color_trc, + ); + } + // Transfer characteristic → which EOTF the compositor applies to reach scene-linear. let transfer = match (*frame).color_trc { ff::AVColorTransferCharacteristic::AVCOL_TRC_SMPTE2084 => VideoTransfer::Pq, @@ -143,6 +155,7 @@ pub fn install(vm: &Arc>, device: &wgpu::Device, adapter: &w let importer = Arc::new(SharedHwImporter { device: device.clone(), adapter: adapter.clone(), + logged: std::sync::atomic::AtomicBool::new(false), }); if let Ok(mut vm) = vm.lock() { vm.set_hardware_decode(