From dcfa3eadaf1f249c1e222aea9fa1d59d6ccfe797 Mon Sep 17 00:00:00 2001 From: Carbon <135588205+carbonfix@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:37:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=B8=BA=20RKCIF/RK628=20=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8F=97=E6=94=AF=E6=8C=81=E7=9A=84=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=20(#292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RK628 通过 RKCIF 接入时,如果 One-KVM 启动时 HDMI 信号尚未锁定, SourceFollowing 路径可能回退到默认 MJPEG。由于 RKCIF 不支持 MJPEG, 后续 S_FMT 无法建立有效的采集链路。 解决方案: 在解析 SourceFollowing 配置后,根据设备的 VIDIOC_ENUM_FMT 枚举结果 校验最终格式。如果该格式不受支持且格式列表非空,则选择按优先级排序 的首个可用格式,对于 RKCIF/RK628 通常为 NV12。 该方法只替换 FourCC,保留 HDMI DV timings 提供的分辨率和帧率。 同时覆盖无信号启动和当前格式已失效两种场景。 --- src/video/device/mod.rs | 82 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/src/video/device/mod.rs b/src/video/device/mod.rs index 10b10fa3..7766ce8b 100644 --- a/src/video/device/mod.rs +++ b/src/video/device/mod.rs @@ -125,6 +125,12 @@ pub fn resolve_video_input_config( requested_resolution: Resolution, requested_fps: u32, ) -> ResolvedVideoInputConfig { + let mut resolved = ResolvedVideoInputConfig { + format: requested_format, + resolution: requested_resolution, + fps: requested_fps, + }; + if device.control_mode == VideoControlMode::SourceFollowing { if let VideoInputStatus { state: VideoInputState::Locked, @@ -135,26 +141,39 @@ pub fn resolve_video_input_config( } = &device.input_status { if let Ok(format) = format.parse::() { - return ResolvedVideoInputConfig { + resolved = ResolvedVideoInputConfig { format, resolution: Resolution::new(*width, *height), fps: fps.round().clamp(1.0, 120.0) as u32, }; } } + + // Source-following devices do not allow One-KVM to choose the HDMI + // resolution or frame rate, but their pixel format still has to be one + // of the formats enumerated by the capture node. In particular, rkcif + // commonly exposes NV12 but One-KVM's default is MJPEG. Passing that + // unsupported default to S_FMT leaves the pipeline in an invalid state. + if !device.formats.is_empty() + && !device + .formats + .iter() + .any(|format| format.format == resolved.format) + { + resolved.format = device.formats[0].format; + } } - ResolvedVideoInputConfig { - format: requested_format, - resolution: requested_resolution, - fps: requested_fps, - } + resolved } #[cfg(test)] mod tests { use super::*; + #[cfg(unix)] + use super::linux::FormatInfo; + #[cfg(unix)] fn device(control_mode: VideoControlMode, input_status: VideoInputStatus) -> VideoDeviceInfo { VideoDeviceInfo { @@ -175,6 +194,15 @@ mod tests { } } + #[cfg(unix)] + fn format(format: PixelFormat) -> FormatInfo { + FormatInfo { + format, + resolutions: Vec::new(), + description: format.to_string(), + } + } + #[test] fn recognizes_vendor_and_upstream_native_hdmirx_names() { assert!(is_rk_hdmirx_driver("rk_hdmirx", "rk_hdmirx")); @@ -246,6 +274,48 @@ mod tests { } } + #[cfg(unix)] + #[test] + fn source_following_replaces_unenumerated_default_format_without_signal() { + let mut device = device( + VideoControlMode::SourceFollowing, + VideoInputStatus::no_signal(), + ); + device.formats = vec![format(PixelFormat::Nv12), format(PixelFormat::Yuyv)]; + + let resolved = resolve_video_input_config( + &device, + PixelFormat::Mjpeg, + Resolution::new(1920, 1080), + 30, + ); + + assert_eq!(resolved.format, PixelFormat::Nv12); + assert_eq!(resolved.resolution, Resolution::new(1920, 1080)); + assert_eq!(resolved.fps, 30); + } + + #[cfg(unix)] + #[test] + fn source_following_replaces_stale_active_format_but_keeps_input_mode() { + let mut device = device( + VideoControlMode::SourceFollowing, + VideoInputStatus::locked(PixelFormat::Mjpeg, 1280, 720, 59.94), + ); + device.formats = vec![format(PixelFormat::Nv12), format(PixelFormat::Yuyv)]; + + let resolved = resolve_video_input_config( + &device, + PixelFormat::Mjpeg, + Resolution::new(1920, 1080), + 30, + ); + + assert_eq!(resolved.format, PixelFormat::Nv12); + assert_eq!(resolved.resolution, Resolution::new(1280, 720)); + assert_eq!(resolved.fps, 60); + } + #[test] fn no_signal_and_unavailable_never_expose_stale_mode_fields() { for status in [