From 258322a4b1f14dee5a3812df46a5578af440d615 Mon Sep 17 00:00:00 2001 From: dcz Date: Thu, 24 Oct 2024 11:51:34 +0000 Subject: [PATCH] io: dmabuf stream implementation --- Cargo.toml | 1 + examples/stream_capture_dmabuf.rs | 67 ++++++++++ src/io/dmabuf/arena.rs | 143 ++++++++++++++++++++ src/io/dmabuf/mod.rs | 4 + src/io/dmabuf/stream.rs | 215 ++++++++++++++++++++++++++++++ src/io/mod.rs | 1 + src/lib.rs | 2 +- 7 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 examples/stream_capture_dmabuf.rs create mode 100644 src/io/dmabuf/arena.rs create mode 100644 src/io/dmabuf/mod.rs create mode 100644 src/io/dmabuf/stream.rs diff --git a/Cargo.toml b/Cargo.toml index 3ebcec3..85dc156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ v4l2-sys = { path = "v4l2-sys", version = "0.3.0", package="v4l2-sys-mit", optio [dev-dependencies] glium = "0.34" jpeg-decoder = "0.3.0" +memmap2 = "0.9" winit = "0.29" [features] diff --git a/examples/stream_capture_dmabuf.rs b/examples/stream_capture_dmabuf.rs new file mode 100644 index 0000000..2237de2 --- /dev/null +++ b/examples/stream_capture_dmabuf.rs @@ -0,0 +1,67 @@ +use std::io; +use std::time::Instant; + +use v4l::buffer::Type; +use v4l::io::traits::CaptureStream; +use v4l::prelude::*; +use v4l::video::Capture; + +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 = dev.format()?; + 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 = DmabufStream::with_buffers(&dev, Type::VideoCapture, 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(); + + println!("Buffer"); + println!(" sequence : {}", meta.sequence); + println!(" timestamp : {}", meta.timestamp); + println!(" flags : {}", meta.flags); + use std::fs::File; + use std::os::fd::{FromRawFd, AsRawFd, IntoRawFd}; + use memmap2; + let outf = unsafe { File::from_raw_fd(buf.as_raw_fd()) }; + let outfmap = unsafe { memmap2::Mmap::map(&outf) }?; + println!(" length : {}", outfmap.len()); + + let cur = outfmap.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; + } + // Prevent File from dropping and closing the fd, because fd is borrowed from texture and not ours to close. + let _ = File::into_raw_fd(outf); + } + + println!(); + println!("FPS: {}", count as f64 / start.elapsed().as_secs_f64()); + println!("MB/s: {}", megabytes_ps); + + Ok(()) +} diff --git a/src/io/dmabuf/arena.rs b/src/io/dmabuf/arena.rs new file mode 100644 index 0000000..8ac5ad6 --- /dev/null +++ b/src/io/dmabuf/arena.rs @@ -0,0 +1,143 @@ +use std::{io, mem, sync::Arc}; +use std::os::fd::{FromRawFd, OwnedFd}; + +use crate::buffer; +use crate::device::Handle; +use crate::memory::Memory; +use crate::v4l2; +use crate::v4l_sys::*; + +/// Manage dmabuf buffers +/// +/// All buffers are released in the Drop impl. +pub struct Arena { + handle: Arc, + pub bufs: Vec, + pub buf_type: buffer::Type, +} + +impl Arena { + /// Returns a new buffer manager instance + /// + /// You usually do not need to use this directly. + /// A UserBufferStream creates its own manager instance by default. + /// + /// # Arguments + /// + /// * `handle` - Device handle to get its file descriptor + /// * `buf_type` - Type of the buffers + pub fn new(handle: Arc, buf_type: buffer::Type) -> Self { + Arena { + handle, + bufs: Vec::new(), + buf_type, + } + } + + fn requestbuffers_desc(&self) -> v4l2_requestbuffers { + v4l2_requestbuffers { + type_: self.buf_type as u32, + ..unsafe { mem::zeroed() } + } + } + + pub fn allocate(&mut self, count: u32) -> io::Result { + // we need to get the maximum buffer size from the format first + let mut v4l2_fmt = v4l2_format { + type_: self.buf_type as u32, + ..unsafe { mem::zeroed() } + }; + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_G_FMT, + &mut v4l2_fmt as *mut _ as *mut std::os::raw::c_void, + )?; + } + + let mut v4l2_reqbufs = v4l2_requestbuffers { + count, + memory: Memory::Mmap as u32, + ..self.requestbuffers_desc() + }; + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_REQBUFS, + &mut v4l2_reqbufs as *mut _ as *mut std::os::raw::c_void, + )?; + } + + for index in 0..v4l2_reqbufs.count { + let mut v4l2_exportbuf = v4l2_exportbuffer { + index, + type_: self.buf_type as u32, + flags: libc::O_RDWR as _, + ..unsafe { mem::zeroed() } + }; + let fd = unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_EXPBUF, + &mut v4l2_exportbuf as *mut _ as *mut std::os::raw::c_void, + )?; + OwnedFd::from_raw_fd(v4l2_exportbuf.fd) + }; + self.bufs.push(fd); + } + + let mut v4l2_reqbufs = v4l2_requestbuffers { + count, + memory: Memory::DmaBuf as u32, + ..self.requestbuffers_desc() + }; + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_REQBUFS, + &mut v4l2_reqbufs as *mut _ as *mut std::os::raw::c_void, + )?; + } + + Ok(v4l2_reqbufs.count) + } + + pub fn release(&mut self) -> io::Result<()> { + // free all buffers by requesting 0 + let mut v4l2_reqbufs = v4l2_requestbuffers { + count: 0, + memory: Memory::DmaBuf as u32, + ..self.requestbuffers_desc() + }; + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_REQBUFS, + &mut v4l2_reqbufs as *mut _ as *mut std::os::raw::c_void, + ) + } + } +} + +impl Drop for Arena { + fn drop(&mut self) { + if self.bufs.is_empty() { + // nothing to do + return; + } + + if let Err(e) = self.release() { + if let Some(code) = e.raw_os_error() { + // ENODEV means the file descriptor wrapped in the handle became invalid, most + // likely because the device was unplugged or the connection (USB, PCI, ..) + // broke down. Handle this case gracefully by ignoring it. + if code == 19 { + /* ignore */ + return; + } + } + + panic!("{:?}", e) + } + } +} diff --git a/src/io/dmabuf/mod.rs b/src/io/dmabuf/mod.rs new file mode 100644 index 0000000..8195783 --- /dev/null +++ b/src/io/dmabuf/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod arena; + +pub mod stream; +pub use stream::Stream; diff --git a/src/io/dmabuf/stream.rs b/src/io/dmabuf/stream.rs new file mode 100644 index 0000000..9c33fba --- /dev/null +++ b/src/io/dmabuf/stream.rs @@ -0,0 +1,215 @@ +use std::convert::TryInto; +use std::os::fd::{AsRawFd, OwnedFd}; +use std::time::Duration; +use std::{io, mem, sync::Arc}; + +use crate::buffer::{Metadata, Type}; +use crate::device::{Device, Handle}; +use crate::io::traits::{CaptureStream, Stream as StreamTrait}; +use crate::io::dmabuf::arena::Arena; +use crate::memory::Memory; +use crate::v4l2; +use crate::v4l_sys::*; + +/// Stream of user buffers +/// +/// An arena instance is used internally for buffer handling. +pub struct Stream { + handle: Arc, + arena: Arena, + arena_index: usize, + buf_type: Type, + buf_meta: Vec, + timeout: Option, + + active: bool, +} + +impl Stream { + /// Returns a stream for frame capturing + /// + /// # Arguments + /// + /// * `dev` - Device ref to get its file descriptor + /// * `buf_type` - Type of the buffers + /// + /// # Example + /// + /// ``` + /// use v4l::buffer::Type; + /// use v4l::device::Device; + /// use v4l::io::userptr::Stream; + /// + /// let dev = Device::new(0); + /// if let Ok(dev) = dev { + /// let stream = Stream::new(&dev, Type::VideoCapture); + /// } + /// ``` + pub fn new(dev: &Device, buf_type: Type) -> io::Result { + Stream::with_buffers(dev, buf_type, 4) + } + + pub fn with_buffers(dev: &Device, buf_type: Type, buf_count: u32) -> io::Result { + let mut arena = Arena::new(dev.handle(), buf_type); + let count = arena.allocate(buf_count)?; + let mut buf_meta = Vec::new(); + buf_meta.resize(count as usize, Metadata::default()); + + Ok(Stream { + handle: dev.handle(), + arena, + arena_index: 0, + buf_type, + buf_meta, + active: false, + timeout: None, + }) + } + + /// Returns the raw device handle + pub fn handle(&self) -> Arc { + self.handle.clone() + } + + /// Sets a timeout of the v4l file handle. + pub fn set_timeout(&mut self, duration: Duration) { + self.timeout = Some(duration.as_millis().try_into().unwrap()); + } + + /// Clears the timeout of the v4l file handle. + pub fn clear_timeout(&mut self) { + self.timeout = None; + } + + fn buffer_desc(&self) -> v4l2_buffer { + v4l2_buffer { + type_: self.buf_type as u32, + memory: Memory::UserPtr as u32, + ..unsafe { mem::zeroed() } + } + } +} + +impl Drop for Stream { + fn drop(&mut self) { + if let Err(e) = self.stop() { + if let Some(code) = e.raw_os_error() { + // ENODEV means the file descriptor wrapped in the handle became invalid, most + // likely because the device was unplugged or the connection (USB, PCI, ..) + // broke down. Handle this case gracefully by ignoring it. + if code == 19 { + /* ignore */ + return; + } + } + + panic!("{:?}", e) + } + } +} + +impl StreamTrait for Stream { + type Item = OwnedFd; + + fn start(&mut self) -> io::Result<()> { + unsafe { + let mut typ = self.buf_type as u32; + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_STREAMON, + &mut typ as *mut _ as *mut std::os::raw::c_void, + )?; + } + + self.active = true; + Ok(()) + } + + fn stop(&mut self) -> io::Result<()> { + unsafe { + let mut typ = self.buf_type as u32; + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_STREAMOFF, + &mut typ as *mut _ as *mut std::os::raw::c_void, + )?; + } + + self.active = false; + Ok(()) + } +} + +impl<'a> CaptureStream<'a> for Stream { + fn queue(&mut self, index: usize) -> io::Result<()> { + let buf = &mut self.arena.bufs[index]; + let mut v4l2_buf = v4l2_buffer { + index: index as u32, + memory: Memory::DmaBuf as u32, + m: v4l2_buffer__bindgen_ty_1 { + fd: buf.as_raw_fd(), + }, + ..self.buffer_desc() + }; + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_QBUF, + &mut v4l2_buf as *mut _ as *mut std::os::raw::c_void, + )?; + } + + Ok(()) + } + + fn dequeue(&mut self) -> io::Result { + let mut v4l2_buf = self.buffer_desc(); + + if self.handle.poll(libc::POLLIN, self.timeout.unwrap_or(-1))? == 0 { + // This condition can only happen if there was a timeout. + // A timeout is only possible if the `timeout` value is non-zero, meaning we should + // propagate it to the caller. + return Err(io::Error::new(io::ErrorKind::TimedOut, "VIDIOC_DQBUF")); + } + + unsafe { + v4l2::ioctl( + self.handle.fd(), + v4l2::vidioc::VIDIOC_DQBUF, + &mut v4l2_buf as *mut _ as *mut std::os::raw::c_void, + )?; + } + self.arena_index = v4l2_buf.index as usize; + + self.buf_meta[self.arena_index] = Metadata { + bytesused: v4l2_buf.bytesused, + flags: v4l2_buf.flags.into(), + field: v4l2_buf.field, + timestamp: v4l2_buf.timestamp.into(), + sequence: v4l2_buf.sequence, + }; + + Ok(self.arena_index) + } + + fn next(&'a mut self) -> io::Result<(&Self::Item, &Metadata)> { + if !self.active { + // Enqueue all buffers once on stream start + for index in 0..self.arena.bufs.len() { + self.queue(index)?; + } + + self.start()?; + } else { + self.queue(self.arena_index)?; + } + + self.arena_index = self.dequeue()?; + + // The index used to access the buffer elements is given to us by v4l2, so we assume it + // will always be valid. + let bytes = &mut self.arena.bufs[self.arena_index]; + let meta = &self.buf_meta[self.arena_index]; + Ok((bytes, meta)) + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs index 983175c..b07c599 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,4 +1,5 @@ pub mod traits; +pub mod dmabuf; pub mod mmap; pub mod userptr; diff --git a/src/lib.rs b/src/lib.rs index fcbe19b..dca4ed3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,5 +105,5 @@ pub use { pub mod prelude { pub use crate::device::Device; - pub use crate::io::{mmap::Stream as MmapStream, userptr::Stream as UserptrStream}; + pub use crate::io::{dmabuf::Stream as DmabufStream, mmap::Stream as MmapStream, userptr::Stream as UserptrStream}; }