From f6f72cd1b9103f0cfc8e8d6fd2b17250174965d1 Mon Sep 17 00:00:00 2001 From: msmmbl Date: Mon, 15 Jul 2024 11:48:28 +0800 Subject: [PATCH] support mplane capture and mmap --- .gitignore | 1 + examples/stream_capture_mplane_mmap.rs | 60 ++++++++++ src/format/mod.rs | 147 +++++++++++++++++++++++++ src/format/quantization.rs | 28 +++-- src/format/transfer.rs | 39 ++++--- src/io/mmap/arena.rs | 59 +++++++--- src/io/mmap/stream.rs | 21 +++- src/video/capture/mod.rs | 95 +++++++++------- src/video/macros.rs | 15 +-- src/video/mod.rs | 2 +- src/video/output/mod.rs | 4 +- src/video/traits.rs | 85 +++++++------- 12 files changed, 428 insertions(+), 128 deletions(-) create mode 100644 examples/stream_capture_mplane_mmap.rs diff --git a/.gitignore b/.gitignore index 972b0c4..547f366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **/target **/Cargo.lock +/.idea/ diff --git a/examples/stream_capture_mplane_mmap.rs b/examples/stream_capture_mplane_mmap.rs new file mode 100644 index 0000000..a8466e5 --- /dev/null +++ b/examples/stream_capture_mplane_mmap.rs @@ -0,0 +1,60 @@ +use std::io; +use std::time::Instant; + +use v4l::buffer::Type; +use v4l::io::traits::CaptureStream; +use v4l::prelude::*; +use v4l::video::{Capture, CaptureMplane}; + +fn main() -> io::Result<()> { + let path = "/dev/video0"; + println!("Using device: {}\n", path); + + // Capture 4 frames by default + let count = 4; + + // Allocate 4 buffers by default + let buffer_count = 4; + + let dev = Device::with_path(path)?; + let format = CaptureMplane::format(&dev)?; + let params = dev.params()?; + println!("Active format:\n{}", format); + println!("Active parameters:\n{}", params); + + // Setup a buffer stream and grab a frame, then print its data + let mut stream = MmapStream::with_buffers(&dev, Type::VideoCaptureMplane, buffer_count)?; + + // warmup + stream.next()?; + + let start = Instant::now(); + let mut megabytes_ps: f64 = 0.0; + for i in 0..count { + let t0 = Instant::now(); + let (buf, meta) = stream.next()?; + let duration_us = t0.elapsed().as_micros(); + + let cur = buf.len() as f64 / 1_048_576.0 * 1_000_000.0 / duration_us as f64; + if i == 0 { + megabytes_ps = cur; + } else { + // ignore the first measurement + let prev = megabytes_ps * (i as f64 / (i + 1) as f64); + let now = cur * (1.0 / (i + 1) as f64); + megabytes_ps = prev + now; + } + + println!("Buffer"); + println!(" sequence : {}", meta.sequence); + println!(" timestamp : {}", meta.timestamp); + println!(" flags : {}", meta.flags); + println!(" length : {}", buf.len()); + } + + println!(); + println!("FPS: {}", count as f64 / start.elapsed().as_secs_f64()); + println!("MB/s: {}", megabytes_ps); + + Ok(()) +} diff --git a/src/format/mod.rs b/src/format/mod.rs index 27d782d..38b83b5 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -33,12 +33,22 @@ impl From for Flags { } } +impl From for Flags { + fn from(flags: u8) -> Self { + Self::from_bits_retain(flags as u32) + } +} + impl From for u32 { fn from(flags: Flags) -> Self { flags.bits() } } +impl From for u8 { + fn from(flags: Flags) -> Self { flags.bits() as u8 } +} + impl fmt::Display for Flags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) @@ -153,3 +163,140 @@ impl From for v4l2_pix_format { } } } + +/// streaming format (multi-planar) +#[derive(Debug, Copy, Clone)] +pub struct FormatMplane { + /// width in pixels + pub width: u32, + /// height in pixels + pub height: u32, + /// pixelformat code + pub fourcc: FourCC, + /// field order for interlacing + pub field_order: FieldOrder, + + pub plane_fmt: [FormatPlanePixItem; 8usize], + + pub num_planes: u8, + + /// flags set by the application or driver + pub flags: Flags, + + /// supplements the pixelformat (fourcc) information + pub colorspace: Colorspace, + /// the way colors are mapped + pub quantization: Quantization, + /// the transfer function for the colorspace + pub transfer: TransferFunction, +} + +impl FormatMplane { + pub const fn new(width: u32, height: u32, fourcc: FourCC) -> Self { + FormatMplane { + width, + height, + fourcc, + field_order: FieldOrder::Any, + plane_fmt: [FormatPlanePixItem { stride: 0, size: 0 }; 8usize], + num_planes: 0, + flags: Flags::empty(), + colorspace: Colorspace::Default, + quantization: Quantization::Default, + transfer: TransferFunction::Default, + } + } +} + +impl fmt::Display for FormatMplane { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "width : {}", self.width)?; + writeln!(f, "height : {}", self.height)?; + writeln!(f, "fourcc : {}", self.fourcc)?; + writeln!(f, "field_order : {}", self.field_order)?; + for i in 0..self.num_planes as usize { + writeln!(f, "plane_fmt[{}]", i)?; + write!(f, "{}", self.plane_fmt[i])?; + } + writeln!(f, "flags : {}", self.flags)?; + writeln!(f, "colorspace : {}", self.colorspace)?; + writeln!(f, "quantization : {}", self.quantization)?; + writeln!(f, "transfer : {}", self.transfer)?; + Ok(()) + } +} + +impl From for FormatMplane { + fn from(fmt: v4l2_pix_format_mplane) -> Self { + let mut plane_fmt = [FormatPlanePixItem { + stride: fmt.plane_fmt[0].bytesperline, + size: fmt.plane_fmt[0].sizeimage, + }; 8usize]; + for i in 1..fmt.num_planes as usize { + plane_fmt[i] = FormatPlanePixItem { + stride: fmt.plane_fmt[i].bytesperline, + size: fmt.plane_fmt[i].sizeimage, + }; + } + + FormatMplane { + width: fmt.width, + height: fmt.height, + fourcc: FourCC::from(fmt.pixelformat), + field_order: FieldOrder::try_from(fmt.field).expect("Invalid field order"), + flags: Flags::from(fmt.flags), + colorspace: Colorspace::try_from(fmt.colorspace).expect("Invalid colorspace"), + plane_fmt, + num_planes: fmt.num_planes, + quantization: Quantization::try_from(fmt.quantization).expect("Invalid quantization"), + transfer: TransferFunction::try_from(fmt.xfer_func).expect("Invalid transfer function"), + } + } +} +impl From for v4l2_pix_format_mplane { + fn from(format: FormatMplane) -> Self { + let mut plane_fmt = [v4l2_plane_pix_format { + bytesperline: format.plane_fmt[0].stride, + sizeimage: format.plane_fmt[0].size, + reserved: [0; 6usize], + }; 8usize]; + for i in 1..format.num_planes as usize { + plane_fmt[i] = v4l2_plane_pix_format { + bytesperline: format.plane_fmt[i].stride, + sizeimage: format.plane_fmt[i].size, + reserved: [0; 6usize], + }; + } + + v4l2_pix_format_mplane { + width: format.width, + height: format.height, + pixelformat: format.fourcc.into(), + field: format.field_order as u32, + colorspace: format.colorspace as u32, + plane_fmt, + num_planes: format.plane_fmt.len() as u8, + flags: format.flags.into(), + quantization: format.quantization as u8, + xfer_func: format.transfer as u8, + ..unsafe { std::mem::zeroed() } + } + } +} + + +#[derive(Debug, Copy, Clone)] +pub struct FormatPlanePixItem { + /// bytes per line + pub stride: u32, + /// maximum number of bytes required to store an image + pub size: u32, +} + +impl fmt::Display for FormatPlanePixItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, " stride : {}", self.stride)?; + writeln!(f, " size : {}", self.size)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/format/quantization.rs b/src/format/quantization.rs index 5aaa743..dfad8e6 100644 --- a/src/format/quantization.rs +++ b/src/format/quantization.rs @@ -26,15 +26,23 @@ impl fmt::Display for Quantization { } } -impl TryFrom for Quantization { - type Error = (); +macro_rules! imp_try_from { + ($($t:ty),*) => { + $( + impl TryFrom<$t> for Quantization { + type Error = (); - fn try_from(code: u32) -> Result { - match code { - 0 => Ok(Self::Default), - 1 => Ok(Self::FullRange), - 2 => Ok(Self::LimitedRange), - _ => Err(()), - } - } + fn try_from(code: $t) -> Result { + match code { + 0 => Ok(Self::Default), + 1 => Ok(Self::FullRange), + 2 => Ok(Self::LimitedRange), + _ => Err(()), + } + } + } + )* + }; } + +imp_try_from!(u32, u8); \ No newline at end of file diff --git a/src/format/transfer.rs b/src/format/transfer.rs index 4b3106d..48ae27d 100644 --- a/src/format/transfer.rs +++ b/src/format/transfer.rs @@ -39,20 +39,29 @@ impl fmt::Display for TransferFunction { } } -impl TryFrom for TransferFunction { - type Error = (); +macro_rules! impl_try_from_transfer_function { + ($($t:ty),*) => { + $( + impl TryFrom<$t> for TransferFunction { + type Error = (); - fn try_from(colorspace_code: u32) -> Result { - match colorspace_code { - 0 => Ok(Self::Default), - 1 => Ok(Self::Rec709), - 2 => Ok(Self::SRGB), - 3 => Ok(Self::OPRGB), - 4 => Ok(Self::SMPTE240M), - 5 => Ok(Self::None), - 6 => Ok(Self::DCIP3), - 7 => Ok(Self::SMPTE2084), - _ => Err(()), - } - } + fn try_from(colorspace_code: $t) -> Result { + match colorspace_code { + 0 => Ok(Self::Default), + 1 => Ok(Self::Rec709), + 2 => Ok(Self::SRGB), + 3 => Ok(Self::OPRGB), + 4 => Ok(Self::SMPTE240M), + 5 => Ok(Self::None), + 6 => Ok(Self::DCIP3), + 7 => Ok(Self::SMPTE2084), + _ => Err(()), + } + } + } + )* + }; } + +impl_try_from_transfer_function!(u8, u32); + diff --git a/src/io/mmap/arena.rs b/src/io/mmap/arena.rs index 9fae806..627a5bb 100644 --- a/src/io/mmap/arena.rs +++ b/src/io/mmap/arena.rs @@ -1,6 +1,7 @@ use std::{io, mem, ptr, slice, sync::Arc}; use crate::buffer; +use crate::buffer::Type; use crate::device::Handle; use crate::memory::Memory; use crate::v4l2; @@ -35,11 +36,22 @@ impl<'a> Arena<'a> { } fn buffer_desc(&self) -> v4l2_buffer { - v4l2_buffer { + let mut planes = v4l2_plane { + .. unsafe { mem::zeroed() } + }; + + let mut desc = v4l2_buffer { type_: self.buf_type as u32, memory: Memory::Mmap as u32, ..unsafe { mem::zeroed() } + }; + + if self.buf_type as u32 == Type::VideoCaptureMplane as u32 { + desc.length = 1; + desc.m.planes = &mut planes; } + + return desc } fn requestbuffers_desc(&self) -> v4l2_requestbuffers { @@ -64,10 +76,9 @@ impl<'a> Arena<'a> { } for index in 0..v4l2_reqbufs.count { - let mut v4l2_buf = v4l2_buffer { - index, - ..self.buffer_desc() - }; + let mut v4l2_buf = self.buffer_desc(); + + v4l2_buf.index = index; unsafe { v4l2::ioctl( self.handle.fd(), @@ -75,18 +86,34 @@ impl<'a> Arena<'a> { &mut v4l2_buf as *mut _ as *mut std::os::raw::c_void, )?; - let ptr = v4l2::mmap( - ptr::null_mut(), - v4l2_buf.length as usize, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED, - self.handle.fd(), - v4l2_buf.m.offset as libc::off_t, - )?; + if self.buf_type as u32 == Type::VideoCaptureMplane as u32 { + let ptr = v4l2::mmap( + ptr::null_mut(), + (*v4l2_buf.m.planes).length as usize, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + self.handle.fd(), + (*v4l2_buf.m.planes).m.mem_offset as libc::off_t, + )?; + let slice = + slice::from_raw_parts_mut::(ptr as *mut u8, v4l2_buf.length as usize); + self.bufs.push(slice); + } else { + let ptr = v4l2::mmap( + ptr::null_mut(), + v4l2_buf.length as usize, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED, + self.handle.fd(), + v4l2_buf.m.offset as libc::off_t, + )?; + let slice = + slice::from_raw_parts_mut::(ptr as *mut u8, v4l2_buf.length as usize); + self.bufs.push(slice); + } + + - let slice = - slice::from_raw_parts_mut::(ptr as *mut u8, v4l2_buf.length as usize); - self.bufs.push(slice); } } diff --git a/src/io/mmap/stream.rs b/src/io/mmap/stream.rs index cd666bd..d7abab7 100644 --- a/src/io/mmap/stream.rs +++ b/src/io/mmap/stream.rs @@ -81,11 +81,22 @@ impl<'a> Stream<'a> { } fn buffer_desc(&self) -> v4l2_buffer { - v4l2_buffer { + let mut planes = v4l2_plane { + .. unsafe { mem::zeroed() } + }; + + let mut desc = v4l2_buffer { type_: self.buf_type as u32, memory: Memory::Mmap as u32, ..unsafe { mem::zeroed() } + }; + + if self.buf_type as u32 == Type::VideoCaptureMplane as u32 { + desc.length = 1; + desc.m.planes = &mut planes; } + + return desc } } @@ -255,8 +266,14 @@ impl<'a, 'b> OutputStream<'b> for Stream<'a> { } self.arena_index = v4l2_buf.index as usize; + let bytesused = if self.buf_type as u32 == Type::VideoCaptureMplane as u32 { + unsafe { (*v4l2_buf.m.planes).bytesused } + } else { + v4l2_buf.bytesused + }; + self.buf_meta[self.arena_index] = Metadata { - bytesused: v4l2_buf.bytesused, + bytesused, flags: v4l2_buf.flags.into(), field: v4l2_buf.field, timestamp: v4l2_buf.timestamp.into(), diff --git a/src/video/capture/mod.rs b/src/video/capture/mod.rs index 949502c..794bb36 100644 --- a/src/video/capture/mod.rs +++ b/src/video/capture/mod.rs @@ -6,52 +6,73 @@ use std::{io, mem}; use crate::buffer::Type; use crate::device::Device; -use crate::format::FourCC; +use crate::format::{FormatMplane, FourCC}; use crate::format::{Description as FormatDescription, Format}; use crate::frameinterval::FrameInterval; use crate::framesize::FrameSize; use crate::v4l2; use crate::v4l_sys::*; -use crate::video::traits::Capture; +use crate::video::traits::{Capture, CaptureMplane}; -impl Capture for Device { - impl_enum_frameintervals!(); - impl_enum_framesizes!(); - impl_enum_formats!(Type::VideoCapture); - impl_format!(Type::VideoCapture); - impl_set_format!(Type::VideoCapture); - - fn params(&self) -> io::Result { - unsafe { - let mut v4l2_params = v4l2_streamparm { - type_: Type::VideoCapture as u32, - ..mem::zeroed() - }; - v4l2::ioctl( - self.handle().fd(), - v4l2::vidioc::VIDIOC_G_PARM, - &mut v4l2_params as *mut _ as *mut std::os::raw::c_void, - )?; - - Ok(Parameters::from(v4l2_params.parm.capture)) + +macro_rules! impl_params { + ($typ:expr) => { + fn params(&self) -> io::Result { + unsafe { + let mut v4l2_params = v4l2_streamparm { + type_: $typ as u32, + ..mem::zeroed() + }; + v4l2::ioctl( + self.handle().fd(), + v4l2::vidioc::VIDIOC_G_PARM, + &mut v4l2_params as *mut _ as *mut std::os::raw::c_void, + )?; + + Ok(Parameters::from(v4l2_params.parm.capture)) + } } } +} - fn set_params(&self, params: &Parameters) -> io::Result { - unsafe { - let mut v4l2_params = v4l2_streamparm { - type_: Type::VideoCapture as u32, - parm: v4l2_streamparm__bindgen_ty_1 { - capture: (*params).into(), - }, - }; - v4l2::ioctl( - self.handle().fd(), - v4l2::vidioc::VIDIOC_S_PARM, - &mut v4l2_params as *mut _ as *mut std::os::raw::c_void, - )?; - } +macro_rules! impl_set_params { + ($typ:expr, $device:ident) => { + fn set_params(&self, params: &Parameters) -> io::Result { + unsafe { + let mut v4l2_params = v4l2_streamparm { + type_: $typ as u32, + parm: v4l2_streamparm__bindgen_ty_1 { + capture: (*params).into(), + }, + }; + v4l2::ioctl( + self.handle().fd(), + v4l2::vidioc::VIDIOC_S_PARM, + &mut v4l2_params as *mut _ as *mut std::os::raw::c_void, + )?; + } - self.params() + $device::params(self) + } } } + +impl Capture for Device { + impl_enum_frameintervals!(); + impl_enum_framesizes!(); + impl_enum_formats!(Type::VideoCapture); + impl_format!(Type::VideoCapture, pix, Format); + impl_set_format!(Type::VideoCapture, pix, Format, Capture); + impl_params!(Type::VideoCapture); + impl_set_params!(Type::VideoCapture, Capture); +} + +impl CaptureMplane for Device { + impl_enum_frameintervals!(); + impl_enum_framesizes!(); + impl_enum_formats!(Type::VideoCaptureMplane); + impl_format!(Type::VideoCaptureMplane, pix_mp, FormatMplane); + impl_set_format!(Type::VideoCaptureMplane, pix_mp, FormatMplane, CaptureMplane); + impl_params!(Type::VideoCaptureMplane); + impl_set_params!(Type::VideoCaptureMplane, CaptureMplane); +} diff --git a/src/video/macros.rs b/src/video/macros.rs index 3f35829..e9fd39d 100644 --- a/src/video/macros.rs +++ b/src/video/macros.rs @@ -128,8 +128,8 @@ macro_rules! impl_enum_formats { } macro_rules! impl_format { - ($typ:expr) => { - fn format(&self) -> io::Result { + ($typ:expr, $pix_key:ident, $fmt_type:ident) => { + fn format(&self) -> io::Result<$fmt_type> { unsafe { let mut v4l2_fmt = v4l2_format { type_: $typ as u32, @@ -141,19 +141,19 @@ macro_rules! impl_format { &mut v4l2_fmt as *mut _ as *mut std::os::raw::c_void, )?; - Ok(Format::from(v4l2_fmt.fmt.pix)) + Ok($fmt_type::from(v4l2_fmt.fmt.$pix_key)) } } }; } macro_rules! impl_set_format { - ($typ:expr) => { - fn set_format(&self, fmt: &Format) -> io::Result { + ($typ:expr, $pix_key:ident, $fmt_type:ident, $device:ident) => { + fn set_format(&self, fmt: &$fmt_type) -> io::Result<$fmt_type> { unsafe { let mut v4l2_fmt = v4l2_format { type_: $typ as u32, - fmt: v4l2_format__bindgen_ty_1 { pix: (*fmt).into() }, + fmt: v4l2_format__bindgen_ty_1 { $pix_key: (*fmt).into() }, }; v4l2::ioctl( self.handle().fd(), @@ -162,7 +162,8 @@ macro_rules! impl_set_format { )?; } - self.format() + $device::format(self) } }; } + diff --git a/src/video/mod.rs b/src/video/mod.rs index 5ca8ade..7546ac4 100644 --- a/src/video/mod.rs +++ b/src/video/mod.rs @@ -6,4 +6,4 @@ pub mod traits; pub mod capture; pub mod output; -pub use traits::{Capture, Output}; +pub use traits::{Capture, CaptureMplane, Output}; diff --git a/src/video/output/mod.rs b/src/video/output/mod.rs index f7f82c3..75028a6 100644 --- a/src/video/output/mod.rs +++ b/src/video/output/mod.rs @@ -18,8 +18,8 @@ impl Output for Device { impl_enum_frameintervals!(); impl_enum_framesizes!(); impl_enum_formats!(Type::VideoOutput); - impl_format!(Type::VideoOutput); - impl_set_format!(Type::VideoOutput); + impl_format!(Type::VideoOutput, pix, Format); + impl_set_format!(Type::VideoOutput, pix, Format, Output); fn params(&self) -> io::Result { unsafe { diff --git a/src/video/traits.rs b/src/video/traits.rs index e7444e1..3d91868 100644 --- a/src/video/traits.rs +++ b/src/video/traits.rs @@ -6,53 +6,62 @@ use crate::{ format::Description as FormatDescription, format::Format, format::FourCC, frameinterval::FrameInterval, framesize::FrameSize, }; +use crate::format::FormatMplane; -/// Capture device protocol -pub trait Capture { - /// Returns a vector of all frame intervals that the device supports for the given pixel format - /// and frame size - fn enum_frameintervals( - &self, - fourcc: FourCC, - width: u32, - height: u32, - ) -> io::Result>; +macro_rules! define_capture { + ($name:ident, $fmt_type:ident) => { + /// Capture device protocol + pub trait $name { + /// Returns a vector of all frame intervals that the device supports for the given pixel format + /// and frame size + fn enum_frameintervals( + &self, + fourcc: FourCC, + width: u32, + height: u32, + ) -> io::Result>; - /// Returns a vector of valid framesizes that the device supports for the given pixel format - fn enum_framesizes(&self, fourcc: FourCC) -> io::Result>; + /// Returns a vector of valid framesizes that the device supports for the given pixel format + fn enum_framesizes(&self, fourcc: FourCC) -> io::Result>; - /// Returns a vector of valid formats for this device - /// - /// The "emulated" field describes formats filled in by libv4lconvert. - /// There may be a conversion related performance penalty when using them. - fn enum_formats(&self) -> io::Result>; + /// Returns a vector of valid formats for this device + /// + /// The "emulated" field describes formats filled in by libv4lconvert. + /// There may be a conversion related performance penalty when using them. + fn enum_formats(&self) -> io::Result>; - /// Returns the format currently in use - fn format(&self) -> io::Result; + /// Returns the format currently in use + fn format(&self) -> io::Result<$fmt_type>; - /// Modifies the capture format and returns the actual format - /// - /// The driver tries to match the format parameters on a best effort basis. - /// Thus, if the combination of format properties cannot be achieved, the closest possible - /// settings are used and reported back. - /// - /// - /// # Arguments - /// - /// * `fmt` - Desired format - fn set_format(&self, fmt: &Format) -> io::Result; + /// Modifies the capture format and returns the actual format + /// + /// The driver tries to match the format parameters on a best effort basis. + /// Thus, if the combination of format properties cannot be achieved, the closest possible + /// settings are used and reported back. + /// + /// + /// # Arguments + /// + /// * `fmt` - Desired format + fn set_format(&self, fmt: &$fmt_type) -> io::Result<$fmt_type>; - /// Returns the parameters currently in use - fn params(&self) -> io::Result; + /// Returns the parameters currently in use + fn params(&self) -> io::Result; - /// Modifies the capture parameters and returns the actual parameters - /// - /// # Arguments - /// - /// * `params` - Desired parameters - fn set_params(&self, params: &CaptureParameters) -> io::Result; + /// Modifies the capture parameters and returns the actual parameters + /// + /// # Arguments + /// + /// * `params` - Desired parameters + fn set_params(&self, params: &CaptureParameters) -> io::Result; + } + }; } + +define_capture!(Capture, Format); +define_capture!(CaptureMplane, FormatMplane); + /// Output device protocol pub trait Output { /// Returns a vector of all frame intervals that the device supports for the given pixel format