diff --git a/lib/examples/vicodec_test/ioctl_api.rs b/lib/examples/vicodec_test/ioctl_api.rs index 4e60a87..8805433 100644 --- a/lib/examples/vicodec_test/ioctl_api.rs +++ b/lib/examples/vicodec_test/ioctl_api.rs @@ -249,7 +249,7 @@ pub fn run( dqbuf::<()>(&fd, output_queue).expect("Failed to dequeue output buffer"); // The CAPTURE buffer, on the other hand, we want to examine more closely. - let cap_dqbuf: DqBuffer = + let cap_dqbuf: V4l2Buffer = dqbuf(&fd, capture_queue).expect("Failed to dequeue capture buffer"); let bytes_used = cap_dqbuf.get_first_plane().bytesused() as usize; diff --git a/lib/src/device/queue.rs b/lib/src/device/queue.rs index 35388d3..26437b9 100644 --- a/lib/src/device/queue.rs +++ b/lib/src/device/queue.rs @@ -483,7 +483,7 @@ impl TryDequeue for Queue type Dequeued = DqBuffer; fn try_dequeue(&self) -> DqBufResult { - let dqbuf: ioctl::DqBuffer = match ioctl::dqbuf(&self.inner, self.inner.type_) { + let dqbuf: ioctl::V4l2Buffer = match ioctl::dqbuf(&self.inner, self.inner.type_) { Ok(dqbuf) => dqbuf, Err(DqBufError::Eos) => return Err(DqBufError::Eos), Err(DqBufError::NotReady) => return Err(DqBufError::NotReady), diff --git a/lib/src/device/queue/dqbuf.rs b/lib/src/device/queue/dqbuf.rs index 8c64858..01bac86 100644 --- a/lib/src/device/queue/dqbuf.rs +++ b/lib/src/device/queue/dqbuf.rs @@ -22,7 +22,7 @@ pub type DropCallback = Box) + Send>; /// return their ownership to the user. pub struct DqBuffer { /// Dequeued buffer information as reported by V4L2. - pub data: ioctl::DqBuffer, + pub data: ioctl::V4l2Buffer, /// The backing memory that has been provided for this buffer. plane_handles: Option

, @@ -47,7 +47,7 @@ impl DqBuffer { queue: &Queue>, buffer: &Arc>, plane_handles: P, - data: ioctl::DqBuffer, + data: ioctl::V4l2Buffer, fuse: BufferStateFuse

, ) -> Self { DqBuffer { diff --git a/lib/src/device/queue/qbuf.rs b/lib/src/device/queue/qbuf.rs index d7653a5..dfd7027 100644 --- a/lib/src/device/queue/qbuf.rs +++ b/lib/src/device/queue/qbuf.rs @@ -124,7 +124,7 @@ impl<'a, D: Direction, P: PrimitiveBufferHandles, Q: BufferHandles + From

> QB self.index, qbuffer, ) { - Ok(_) => (), + Ok(()) => (), Err(error) => { return Err(QueueError { error, diff --git a/lib/src/ioctl.rs b/lib/src/ioctl.rs index 3db9f0b..f07457e 100644 --- a/lib/src/ioctl.rs +++ b/lib/src/ioctl.rs @@ -46,6 +46,8 @@ pub use request::*; pub use streamon::*; pub use subscribe_event::*; +use std::fmt::Debug; + use crate::bindings; use crate::QueueType; use std::{ @@ -102,17 +104,6 @@ mod tests { } } -/// A memory area we can pass to ioctls in order to get/set plane information -/// with the multi-planar API. -type PlaneData = [bindings::v4l2_plane; bindings::VIDEO_MAX_PLANES as usize]; - -/// For simple initialization of `PlaneData`. -impl Default for bindings::v4l2_plane { - fn default() -> Self { - unsafe { mem::zeroed() } - } -} - /// Returns whether the given queue type can handle multi-planar formats. fn is_multi_planar(queue: QueueType) -> bool { matches!( @@ -134,3 +125,137 @@ where self.into() as i32 } } + +/// A memory area we can pass to ioctls in order to get/set plane information +/// with the multi-planar API. +type V4l2BufferPlanes = [bindings::v4l2_plane; bindings::VIDEO_MAX_PLANES as usize]; + +/// For simple initialization of `V4l2BufferPlanes`. +impl Default for bindings::v4l2_plane { + fn default() -> Self { + unsafe { mem::zeroed() } + } +} + +/// Information about a single plane of a V4L2 buffer. +pub struct V4l2BufferPlane<'a> { + plane: &'a bindings::v4l2_plane, +} + +impl<'a> Debug for V4l2BufferPlane<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("V4l2BufferPlane") + .field("length", &self.length()) + .field("bytesused", &self.bytesused()) + .field("data_offset", &self.data_offset()) + .finish() + } +} + +impl<'a> V4l2BufferPlane<'a> { + pub fn length(&self) -> u32 { + self.plane.length + } + + pub fn bytesused(&self) -> u32 { + self.plane.bytesused + } + + pub fn data_offset(&self) -> u32 { + self.plane.data_offset + } +} + +/// A completely owned v4l2_buffer, where the pointer to planes is meaningless and fixed up when +/// needed. +/// +/// If the buffer is single-planar, it is modified to use `planes` anyway for the information of +/// its unique plane. +#[derive(Clone)] +#[repr(C)] +pub struct V4l2Buffer { + buffer: bindings::v4l2_buffer, + planes: V4l2BufferPlanes, +} + +/// V4l2Buffer is safe to send across threads. `v4l2_buffer` is !Send because it +/// contains a pointer, but we are making sure to use it safely here. +unsafe impl Send for V4l2Buffer {} + +impl Debug for V4l2Buffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("V4l2Buffer") + .field("index", &self.index()) + .field("flags", &self.flags()) + .field("sequence", &self.sequence()) + .finish() + } +} + +impl V4l2Buffer { + pub fn index(&self) -> u32 { + self.buffer.index + } + + pub fn queue_type(&self) -> u32 { + self.buffer.type_ + } + + pub fn flags(&self) -> BufferFlags { + BufferFlags::from_bits_truncate(self.buffer.flags) + } + + pub fn is_last(&self) -> bool { + self.flags().contains(BufferFlags::LAST) + } + + pub fn timestamp(&self) -> bindings::timeval { + self.buffer.timestamp + } + + pub fn sequence(&self) -> u32 { + self.buffer.sequence + } + + pub fn is_multi_planar(&self) -> bool { + matches!( + self.buffer.type_, + bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE + | bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE + ) + } + + pub fn num_planes(&self) -> usize { + if self.is_multi_planar() { + self.buffer.length as usize + } else { + 1 + } + } + + /// Returns the first plane of the buffer. This method is guaranteed to + /// succeed because every buffer has at least one plane. + pub fn get_first_plane(&self) -> V4l2BufferPlane { + V4l2BufferPlane { + plane: &self.planes[0], + } + } + + /// Returns plane `index` of the buffer, or `None` if `index` is larger than + /// the number of planes in this buffer. + pub fn get_plane(&self, index: usize) -> Option { + if index < self.num_planes() { + Some(V4l2BufferPlane { + plane: &self.planes[index], + }) + } else { + None + } + } + + /// Returns the raw v4l2_buffer as a pointer. Useful to pass to unsafe + /// non-Rust code. + pub fn as_raw_v4l2_buffer(&self) -> *const bindings::v4l2_buffer { + &self.buffer + } +} diff --git a/lib/src/ioctl/dqbuf.rs b/lib/src/ioctl/dqbuf.rs index 1e3f3f7..63b7275 100644 --- a/lib/src/ioctl/dqbuf.rs +++ b/lib/src/ioctl/dqbuf.rs @@ -1,207 +1,15 @@ -use super::{is_multi_planar, BufferFlags, PlaneData}; use crate::bindings; +use crate::ioctl::is_multi_planar; +use crate::ioctl::QueryBuf; +use crate::ioctl::V4l2BufferPlanes; use crate::QueueType; -use nix::{self, errno::Errno}; +use std::fmt::Debug; use std::mem; use std::os::unix::io::AsRawFd; -use std::{fmt::Debug, pin::Pin}; -use thiserror::Error; - -/// Implementors can receive the result from the `dqbuf` ioctl. -pub trait DqBuf: Sized { - /// Try to retrieve the data from `v4l2_buf`. If `v4l2_planes` is `None`, - /// then the buffer is single-planar. If it has data, the buffer is - /// multi-planar and `v4l2_planes` shall be used to retrieve the plane data. - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, v4l2_planes: Option) -> Self; -} - -impl DqBuf for bindings::v4l2_buffer { - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option) -> Self { - v4l2_buf - } -} - -/// Allows to dequeue a buffer without caring for any of its data. -impl DqBuf for () { - fn from_v4l2_buffer(_v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option) -> Self { - } -} - -/// Useful for the case where we are only interested in the index of a dequeued -/// buffer -impl DqBuf for u32 { - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option) -> Self { - v4l2_buf.index - } -} - -/// Information about a single plane of a dequeued buffer. -pub struct DqBufPlane<'a> { - plane: &'a bindings::v4l2_plane, -} - -impl<'a> Debug for DqBufPlane<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DQBufPlane") - .field("length", &self.length()) - .field("bytesused", &self.bytesused()) - .field("data_offset", &self.data_offset()) - .finish() - } -} -impl<'a> DqBufPlane<'a> { - pub fn length(&self) -> u32 { - self.plane.length - } - - pub fn bytesused(&self) -> u32 { - self.plane.bytesused - } - - pub fn data_offset(&self) -> u32 { - self.plane.data_offset - } -} - -/// Information for a dequeued buffer. Safe variant of `struct v4l2_buffer`. -pub struct DqBuffer { - v4l2_buffer: bindings::v4l2_buffer, - // The `m.planes` pointer of `v4l2_buffer` points here and must stay stable - // as we move this object around. - v4l2_planes: Pin>, -} - -impl Clone for DqBuffer { - fn clone(&self) -> Self { - let mut ret = Self { - v4l2_buffer: self.v4l2_buffer, - v4l2_planes: self.v4l2_planes.clone(), - }; - // Make the planes pointer of the cloned data point to the right copy. - if self.is_multi_planar() { - ret.v4l2_buffer.m.planes = ret.v4l2_planes.as_mut_ptr(); - } - ret - } -} - -/// DQBuffer is safe to send across threads. `v4l2_buffer` is !Send because it -/// contains a pointer, but we are making sure to use it safely here. -unsafe impl Send for DqBuffer {} - -impl Debug for DqBuffer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DQBuffer") - .field("index", &self.index()) - .field("flags", &self.flags()) - .field("sequence", &self.sequence()) - .finish() - } -} - -impl DqBuffer { - pub fn index(&self) -> u32 { - self.v4l2_buffer.index - } - - pub fn flags(&self) -> BufferFlags { - BufferFlags::from_bits_truncate(self.v4l2_buffer.flags) - } - - pub fn is_last(&self) -> bool { - self.flags().contains(BufferFlags::LAST) - } - - pub fn timestamp(&self) -> bindings::timeval { - self.v4l2_buffer.timestamp - } - - pub fn sequence(&self) -> u32 { - self.v4l2_buffer.sequence - } - - pub fn is_multi_planar(&self) -> bool { - matches!( - self.v4l2_buffer.type_, - bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE - | bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE - ) - } - - pub fn num_planes(&self) -> usize { - if self.is_multi_planar() { - self.v4l2_buffer.length as usize - } else { - 1 - } - } - - /// Returns the first plane of the buffer. This method is guaranteed to - /// succeed because every buffer has at least one plane. - pub fn get_first_plane(&self) -> DqBufPlane { - DqBufPlane { - plane: &self.v4l2_planes[0], - } - } - - /// Returns plane `index` of the buffer, or `None` if `index` is larger than - /// the number of planes in this buffer. - pub fn get_plane(&self, index: usize) -> Option { - if index < self.num_planes() { - Some(DqBufPlane { - plane: &self.v4l2_planes[index], - }) - } else { - None - } - } - - /// Returns the raw v4l2_buffer as a pointer. Useful to pass to unsafe - /// non-Rust code. - pub fn as_raw_v4l2_buffer(&self) -> *const bindings::v4l2_buffer { - &self.v4l2_buffer - } -} - -impl DqBuf for DqBuffer { - fn from_v4l2_buffer( - v4l2_buffer: bindings::v4l2_buffer, - v4l2_planes: Option, - ) -> Self { - let mut dqbuf = DqBuffer { - v4l2_buffer, - v4l2_planes: Box::pin(match v4l2_planes { - Some(planes) => planes, - // In single-plane mode, reproduce the buffer information into - // a v4l2_plane in order to present a unified interface. - None => { - let mut pdata: PlaneData = Default::default(); - pdata[0] = bindings::v4l2_plane { - bytesused: v4l2_buffer.bytesused, - length: v4l2_buffer.length, - data_offset: 0, - reserved: Default::default(), - // Safe because both unions have the same members and - // layout in single-plane mode. - m: unsafe { std::mem::transmute(v4l2_buffer.m) }, - }; - - pdata - } - }), - }; - - // Since the planes have moved, update the planes pointer if we are - // using multi-planar. - if dqbuf.is_multi_planar() { - dqbuf.v4l2_buffer.m.planes = dqbuf.v4l2_planes.as_mut_ptr() - } - - dqbuf - } -} +use nix::errno::Errno; +use thiserror::Error; #[doc(hidden)] mod ioctl { @@ -242,14 +50,14 @@ impl From for Errno { pub type DqBufResult = Result; /// Safe wrapper around the `VIDIOC_DQBUF` ioctl. -pub fn dqbuf(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult { +pub fn dqbuf(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult { let mut v4l2_buf = bindings::v4l2_buffer { type_: queue as u32, ..unsafe { mem::zeroed() } }; let dequeued_buffer = if is_multi_planar(queue) { - let mut plane_data: PlaneData = Default::default(); + let mut plane_data: V4l2BufferPlanes = Default::default(); v4l2_buf.m.planes = plane_data.as_mut_ptr(); v4l2_buf.length = plane_data.len() as u32; diff --git a/lib/src/ioctl/qbuf.rs b/lib/src/ioctl/qbuf.rs index 2b36422..683d846 100644 --- a/lib/src/ioctl/qbuf.rs +++ b/lib/src/ioctl/qbuf.rs @@ -1,5 +1,6 @@ //! Safe wrapper for the VIDIOC_(D)QBUF and VIDIOC_QUERYBUF ioctls. -use super::{is_multi_planar, PlaneData}; +use super::{is_multi_planar, V4l2BufferPlanes}; +use crate::ioctl::{QueryBuf, V4l2Buffer}; use crate::memory::{Memory, PlaneHandle}; use crate::{bindings, QueueType}; @@ -66,10 +67,34 @@ pub trait QBuf { fn fill_mplane_v4l2_buffer( self, v4l2_buf: &mut bindings::v4l2_buffer, - v4l2_planes: &mut PlaneData, + v4l2_planes: &mut V4l2BufferPlanes, ) -> Result<(), QBufError>; } +impl QBuf for V4l2Buffer { + fn fill_splane_v4l2_buffer( + self, + v4l2_buf: &mut bindings::v4l2_buffer, + ) -> Result<(), QBufError> { + *v4l2_buf = self.buffer; + + Ok(()) + } + + fn fill_mplane_v4l2_buffer( + self, + v4l2_buf: &mut bindings::v4l2_buffer, + v4l2_planes: &mut V4l2BufferPlanes, + ) -> Result<(), QBufError> { + *v4l2_buf = self.buffer; + for (dest, src) in v4l2_planes.iter_mut().zip(self.planes.iter()) { + *dest = *src; + } + + Ok(()) + } +} + /// Representation of a single plane of a V4L2 buffer. pub struct QBufPlane(pub bindings::v4l2_plane); @@ -167,7 +192,7 @@ impl QBuf for QBuffer { fn fill_mplane_v4l2_buffer( self, v4l2_buf: &mut bindings::v4l2_buffer, - v4l2_planes: &mut PlaneData, + v4l2_planes: &mut V4l2BufferPlanes, ) -> Result<(), QBufError> { if self.planes.is_empty() || self.planes.len() > v4l2_planes.len() { return Err(QBufError::NumPlanesMismatch( @@ -212,12 +237,12 @@ mod ioctl { /// accessed by anyone else, the caller also needs to guarantee that the backing /// memory won't be freed until the corresponding buffer is returned by either /// `dqbuf` or `streamoff`. -pub fn qbuf( +pub fn qbuf( fd: &impl AsRawFd, queue: QueueType, index: usize, - buf_data: T, -) -> Result<(), QBufError> { + buf_data: I, +) -> Result { let mut v4l2_buf = bindings::v4l2_buffer { index: index as u32, type_: queue as u32, @@ -225,15 +250,15 @@ pub fn qbuf( }; if is_multi_planar(queue) { - let mut plane_data: PlaneData = Default::default(); + let mut plane_data: V4l2BufferPlanes = Default::default(); + buf_data.fill_mplane_v4l2_buffer(&mut v4l2_buf, &mut plane_data)?; v4l2_buf.m.planes = plane_data.as_mut_ptr(); - buf_data.fill_mplane_v4l2_buffer(&mut v4l2_buf, &mut plane_data)?; unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), &mut v4l2_buf) }?; - Ok(()) + Ok(O::from_v4l2_buffer(v4l2_buf, Some(plane_data))) } else { buf_data.fill_splane_v4l2_buffer(&mut v4l2_buf)?; unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), &mut v4l2_buf) }?; - Ok(()) + Ok(O::from_v4l2_buffer(v4l2_buf, None)) } } diff --git a/lib/src/ioctl/querybuf.rs b/lib/src/ioctl/querybuf.rs index b02b92f..3b1a25f 100644 --- a/lib/src/ioctl/querybuf.rs +++ b/lib/src/ioctl/querybuf.rs @@ -1,5 +1,6 @@ -use super::{is_multi_planar, BufferFlags, PlaneData}; +use super::{is_multi_planar, BufferFlags, V4l2BufferPlanes}; use crate::bindings; +use crate::ioctl::V4l2Buffer; use crate::QueueType; use nix::errno::Errno; use thiserror::Error; @@ -13,12 +14,44 @@ pub trait QueryBuf: Sized { /// then the buffer is single-planar. If it has data, the buffer is /// multi-planar and the array of `struct v4l2_plane` shall be used to /// retrieve the plane data. - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, v4l2_planes: Option) -> Self; + fn from_v4l2_buffer( + v4l2_buf: bindings::v4l2_buffer, + v4l2_planes: Option, + ) -> Self; } -impl QueryBuf for bindings::v4l2_buffer { - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option) -> Self { - v4l2_buf +/// For cases where we are not interested in the result of `qbuf` +impl QueryBuf for () { + fn from_v4l2_buffer( + _v4l2_buf: bindings::v4l2_buffer, + _v4l2_planes: Option, + ) -> Self { + } +} + +impl QueryBuf for V4l2Buffer { + fn from_v4l2_buffer( + v4l2_buf: bindings::v4l2_buffer, + v4l2_planes: Option, + ) -> Self { + Self { + buffer: v4l2_buf, + planes: v4l2_planes.unwrap_or_else(|| { + let mut pdata: V4l2BufferPlanes = Default::default(); + // Duplicate information for the first plane so our methods can work. + pdata[0] = bindings::v4l2_plane { + bytesused: v4l2_buf.bytesused, + length: v4l2_buf.length, + data_offset: 0, + reserved: Default::default(), + // Safe because both unions have the same members and + // layout in single-plane mode. + m: unsafe { std::mem::transmute(v4l2_buf.m) }, + }; + + pdata + }), + } } } @@ -39,7 +72,10 @@ pub struct QueryBuffer { } impl QueryBuf for QueryBuffer { - fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, v4l2_planes: Option) -> Self { + fn from_v4l2_buffer( + v4l2_buf: bindings::v4l2_buffer, + v4l2_planes: Option, + ) -> Self { let planes = match v4l2_planes { None => vec![QueryBufPlane { mem_offset: unsafe { v4l2_buf.m.offset }, @@ -96,7 +132,7 @@ pub fn querybuf( }; if is_multi_planar(queue) { - let mut plane_data: PlaneData = Default::default(); + let mut plane_data: V4l2BufferPlanes = Default::default(); v4l2_buf.m.planes = plane_data.as_mut_ptr(); v4l2_buf.length = plane_data.len() as u32;