ioctl: introduce and use V4l2Buffer structure
Introduce a simple V4l2Buffer (inspired from DqBuffer) that makes it
easy to queue/dequeue/query buffers from a single structure. This allows
further simplifications, like the removal of the DqBuf trait which is
now equivalent to QueryBuf.
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 @@
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 @@
type Dequeued = DqBuffer<D, P>;
fn try_dequeue(&self) -> DqBufResult<Self::Dequeued> {
- 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 @@
/// return their ownership to the user.
pub struct DqBuffer<D: Direction, P: BufferHandles> {
/// 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<P>,
@@ -47,7 +47,7 @@
queue: &Queue<D, BuffersAllocated<P>>,
buffer: &Arc<BufferInfo<P>>,
plane_handles: P,
- data: ioctl::DqBuffer,
+ data: ioctl::V4l2Buffer,
fuse: BufferStateFuse<P>,
) -> 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 @@
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 streamon::*;
pub use subscribe_event::*;
+use std::fmt::Debug;
+
use crate::bindings;
use crate::QueueType;
use std::{
@@ -102,17 +104,6 @@
}
}
-/// 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 @@
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<V4l2BufferPlane> {
+ 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,208 +1,16 @@
-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 nix::errno::Errno;
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<PlaneData>) -> Self;
-}
-
-impl DqBuf for bindings::v4l2_buffer {
- fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option<PlaneData>) -> 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<PlaneData>) -> 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<PlaneData>) -> 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<Box<PlaneData>>,
-}
-
-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<DqBufPlane> {
- 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<PlaneData>,
- ) -> 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
- }
-}
-
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_buffer;
@@ -242,14 +50,14 @@
pub type DqBufResult<T> = Result<T, DqBufError>;
/// Safe wrapper around the `VIDIOC_DQBUF` ioctl.
-pub fn dqbuf<T: DqBuf + Debug>(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult<T> {
+pub fn dqbuf<T: QueryBuf>(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult<T> {
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 @@
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 @@
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 @@
/// 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<T: QBuf>(
+pub fn qbuf<I: QBuf, O: QueryBuf>(
fd: &impl AsRawFd,
queue: QueueType,
index: usize,
- buf_data: T,
-) -> Result<(), QBufError> {
+ buf_data: I,
+) -> Result<O, QBufError> {
let mut v4l2_buf = bindings::v4l2_buffer {
index: index as u32,
type_: queue as u32,
@@ -225,15 +250,15 @@
};
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 @@
/// 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<PlaneData>) -> Self;
+ fn from_v4l2_buffer(
+ v4l2_buf: bindings::v4l2_buffer,
+ v4l2_planes: Option<V4l2BufferPlanes>,
+ ) -> Self;
}
-impl QueryBuf for bindings::v4l2_buffer {
- fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, _v4l2_planes: Option<PlaneData>) -> 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<V4l2BufferPlanes>,
+ ) -> Self {
+ }
+}
+
+impl QueryBuf for V4l2Buffer {
+ fn from_v4l2_buffer(
+ v4l2_buf: bindings::v4l2_buffer,
+ v4l2_planes: Option<V4l2BufferPlanes>,
+ ) -> 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 @@
}
impl QueryBuf for QueryBuffer {
- fn from_v4l2_buffer(v4l2_buf: bindings::v4l2_buffer, v4l2_planes: Option<PlaneData>) -> Self {
+ fn from_v4l2_buffer(
+ v4l2_buf: bindings::v4l2_buffer,
+ v4l2_planes: Option<V4l2BufferPlanes>,
+ ) -> Self {
let planes = match v4l2_planes {
None => vec![QueryBufPlane {
mem_offset: unsafe { v4l2_buf.m.offset },
@@ -96,7 +132,7 @@
};
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;