mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-13 19:14:24 +08:00
perf(video): 新增 RKMPP DMA 采集编码通路并完善恢复逻辑
支持原生 HDMI 和 UVC 缓冲区导出,增加同步 RKMPP 编码及可选 MJPEG 硬件转码。 校验帧布局和缓冲区租约,在 DMA 不可用或编码失败时回退到复制通路。 保留自定义码率和 GOP 策略,重开采集时同步 HDMI 源帧率,并区分 UVC 超时状态。 验证:88 个视频测试通过(含 4 个新增回归测试);ARM64 cargo check --tests 通过。
This commit is contained in:
201
src/video/capture/dmabuf_layout.rs
Normal file
201
src/video/capture/dmabuf_layout.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
//! Conservative, dependency-free eligibility checks for linear RKMPP input.
|
||||
|
||||
pub struct DmaCaptureLayout<'a> {
|
||||
pub native_hdmi: bool,
|
||||
pub driver: &'a str,
|
||||
pub bus_info: &'a str,
|
||||
pub configurable_usb: bool,
|
||||
pub single_planar: bool,
|
||||
pub fourcc: [u8; 4],
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
}
|
||||
|
||||
impl DmaCaptureLayout<'_> {
|
||||
/// Minimum readable bytes, not the driver's page-aligned allocation size.
|
||||
pub fn minimum_bytes(&self) -> Option<usize> {
|
||||
if self.width == 0
|
||||
|| self.height == 0
|
||||
|| self.width > 8192
|
||||
|| self.height > 8192
|
||||
|| self.width % 2 != 0
|
||||
|| self.height % 2 != 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let bytes_per_row = if self.native_hdmi {
|
||||
match &self.fourcc {
|
||||
b"NV12" => self.width,
|
||||
b"BGR3" => self.width.checked_mul(3)?,
|
||||
_ => return None,
|
||||
}
|
||||
} else if self.configurable_usb
|
||||
&& self.single_planar
|
||||
&& self.driver == "uvcvideo"
|
||||
&& self.bus_info.starts_with("usb-")
|
||||
{
|
||||
match &self.fourcc {
|
||||
// Compressed frames have variable bytesused and no byte stride.
|
||||
b"MJPG" => return Some(4),
|
||||
b"YUYV" if self.stride % 16 == 0 => self.width.checked_mul(2)?,
|
||||
b"NV12" if self.stride % 16 == 0 => self.width,
|
||||
b"RGB3" if self.stride % 16 == 0 => self.width.checked_mul(3)?,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if self.stride < bytes_per_row {
|
||||
return None;
|
||||
}
|
||||
let size = (self.stride as usize).checked_mul(self.height as usize)?;
|
||||
if self.fourcc == *b"NV12" {
|
||||
size.checked_mul(3)?.checked_div(2)
|
||||
} else {
|
||||
Some(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An MJPEG DMA packet has a bounded payload, not stride * height bytes.
|
||||
/// Reserve readable headroom for the MPP bitstream reader without modifying
|
||||
/// capture memory. The decoder receives only `used`, never the allocation size.
|
||||
pub fn valid_payload(
|
||||
compressed: bool,
|
||||
used: usize,
|
||||
capacity: usize,
|
||||
expected: Option<usize>,
|
||||
) -> bool {
|
||||
if compressed {
|
||||
used >= 4 && used.checked_add(64).is_some_and(|end| end <= capacity)
|
||||
} else {
|
||||
used > 0 && Some(used) == expected && used <= capacity
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn usb() -> DmaCaptureLayout<'static> {
|
||||
DmaCaptureLayout {
|
||||
native_hdmi: false,
|
||||
driver: "uvcvideo",
|
||||
bus_info: "usb-fc880000.usb-1.1",
|
||||
configurable_usb: true,
|
||||
single_planar: true,
|
||||
fourcc: *b"YUYV",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
stride: 3840,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usb_yuyv_uses_byte_stride_and_supports_padding() {
|
||||
let mut layout = usb();
|
||||
assert_eq!(layout.minimum_bytes(), Some(4_147_200));
|
||||
layout.width = 640;
|
||||
layout.height = 480;
|
||||
layout.stride = 1280;
|
||||
assert_eq!(layout.minimum_bytes(), Some(614_400));
|
||||
layout.stride = 1296;
|
||||
assert_eq!(layout.minimum_bytes(), Some(622_080));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usb_requires_correct_driver_bus_queue_and_control_mode() {
|
||||
let mut layout = usb();
|
||||
layout.driver = "rkcif";
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
layout = usb();
|
||||
layout.bus_info = "platform:hdmi";
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
layout = usb();
|
||||
layout.single_planar = false;
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
layout = usb();
|
||||
layout.configurable_usb = false;
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unverified_usb_formats_stay_on_copy_path() {
|
||||
for fourcc in [
|
||||
*b"H264", *b"NV21", *b"NV16", *b"NV24", *b"BGR3", *b"YU12", *b"UYVY", *b"YVYU",
|
||||
*b"BAD!",
|
||||
] {
|
||||
let mut layout = usb();
|
||||
layout.fourcc = fourcc;
|
||||
assert_eq!(layout.minimum_bytes(), None, "{fourcc:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usb_nv12_rgb_and_mjpeg_layouts() {
|
||||
let mut layout = usb();
|
||||
layout.fourcc = *b"NV12";
|
||||
layout.stride = 1920;
|
||||
assert_eq!(layout.minimum_bytes(), Some(3_110_400));
|
||||
layout.fourcc = *b"RGB3";
|
||||
layout.stride = 5760;
|
||||
assert_eq!(layout.minimum_bytes(), Some(6_220_800));
|
||||
layout.stride = 1920;
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
layout.fourcc = *b"MJPG";
|
||||
layout.stride = 0;
|
||||
assert_eq!(layout.minimum_bytes(), Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_payload_is_bounded_and_not_allocation_size() {
|
||||
assert!(valid_payload(true, 63163, 4147200, None));
|
||||
for used in [0, 3, 4147200, usize::MAX] {
|
||||
assert!(!valid_payload(true, used, 4147200, None));
|
||||
}
|
||||
assert!(valid_payload(true, 4, 68, None));
|
||||
assert!(!valid_payload(true, 4, 67, None));
|
||||
assert!(valid_payload(false, 614400, 614400, Some(614400)));
|
||||
assert!(!valid_payload(false, 614399, 614400, Some(614400)));
|
||||
assert!(!valid_payload(false, 614400, 614399, Some(614400)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_geometry_or_stride_is_rejected() {
|
||||
for (w, h, stride) in [
|
||||
(0, 1080, 3840),
|
||||
(1920, 0, 3840),
|
||||
(1919, 1080, 3840),
|
||||
(1920, 1079, 3840),
|
||||
(8194, 1080, 16384),
|
||||
(1920, 8194, 3840),
|
||||
(1920, 1080, 0),
|
||||
(1920, 1080, 1920),
|
||||
(1920, 1080, 3841),
|
||||
(u32::MAX, u32::MAX, u32::MAX),
|
||||
] {
|
||||
let mut layout = usb();
|
||||
layout.width = w;
|
||||
layout.height = h;
|
||||
layout.stride = stride;
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_hdmi_formats_are_preserved_but_not_expanded() {
|
||||
let mut layout = usb();
|
||||
layout.native_hdmi = true;
|
||||
layout.single_planar = false;
|
||||
layout.fourcc = *b"BGR3";
|
||||
layout.stride = 5760;
|
||||
assert_eq!(layout.minimum_bytes(), Some(6_220_800));
|
||||
layout.fourcc = *b"NV12";
|
||||
layout.stride = 1920;
|
||||
assert_eq!(layout.minimum_bytes(), Some(3_110_400));
|
||||
layout.fourcc = *b"YUYV";
|
||||
layout.stride = 3840;
|
||||
assert_eq!(layout.minimum_bytes(), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user