fix: 为 RKCIF/RK628 选择受支持的视频格式 (#292)

RK628 通过 RKCIF 接入时,如果 One-KVM 启动时 HDMI 信号尚未锁定,
SourceFollowing 路径可能回退到默认 MJPEG。由于 RKCIF 不支持 MJPEG,
后续 S_FMT 无法建立有效的采集链路。

解决方案:
在解析 SourceFollowing 配置后,根据设备的 VIDIOC_ENUM_FMT 枚举结果
校验最终格式。如果该格式不受支持且格式列表非空,则选择按优先级排序
的首个可用格式,对于 RKCIF/RK628 通常为 NV12。

该方法只替换 FourCC,保留 HDMI DV timings 提供的分辨率和帧率。
同时覆盖无信号启动和当前格式已失效两种场景。
This commit is contained in:
Carbon
2026-08-26 16:37:57 +08:00
committed by GitHub
parent 1647e70243
commit dcfa3eadaf

View File

@@ -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::<PixelFormat>() {
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 [