#![allow(dead_code)]
use std::os::fd::BorrowedFd;
use rustix::io::Errno;
use rustix::ioctl::{self, Ioctl, IoctlOutput, Opcode};
pub const fn fourcc(code: &[u8; 4]) -> u32 {
u32::from_le_bytes(*code)
}
pub const PIX_FMT_H264: u32 = fourcc(b"H264");
pub const PIX_FMT_HEVC: u32 = fourcc(b"HEVC");
pub const PIX_FMT_NV12: u32 = fourcc(b"NV12");
pub const PIX_FMT_YUV420: u32 = fourcc(b"YU12");
pub const BUF_TYPE_CAPTURE_MPLANE: u32 = 9;
pub const BUF_TYPE_OUTPUT_MPLANE: u32 = 10;
pub const MEMORY_MMAP: u32 = 1;
pub const CAP_VIDEO_M2M_MPLANE: u32 = 0x0000_4000;
pub const CAP_STREAMING: u32 = 0x0400_0000;
pub const FMT_FLAG_COMPRESSED: u32 = 0x0001;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Timeval {
pub tv_sec: isize,
pub tv_usec: isize,
}
#[repr(C)]
pub struct Capability {
pub driver: [u8; 16],
pub card: [u8; 32],
pub bus_info: [u8; 32],
pub version: u32,
pub capabilities: u32,
pub device_caps: u32,
pub reserved: [u32; 3],
}
impl Capability {
pub const fn zeroed() -> Self {
Self {
driver: [0; 16],
card: [0; 32],
bus_info: [0; 32],
version: 0,
capabilities: 0,
device_caps: 0,
reserved: [0; 3],
}
}
pub fn caps(&self) -> u32 {
if self.device_caps != 0 {
self.device_caps
} else {
self.capabilities
}
}
pub fn driver_name(&self) -> &str {
let end = self.driver.iter().position(|&b| b == 0).unwrap_or(16);
core::str::from_utf8(&self.driver[..end]).unwrap_or("?")
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct FmtDesc {
pub index: u32,
pub buf_type: u32,
pub flags: u32,
pub description: [u8; 32],
pub pixelformat: u32,
pub mbus_code: u32,
pub reserved: [u32; 3],
}
impl FmtDesc {
pub const fn at(index: u32, buf_type: u32) -> Self {
Self {
index,
buf_type,
flags: 0,
description: [0; 32],
pixelformat: 0,
mbus_code: 0,
reserved: [0; 3],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct PlanePixFormat {
pub sizeimage: u32,
pub bytesperline: u32,
pub reserved: [u16; 6],
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct PixFormatMplane {
pub width: u32,
pub height: u32,
pub pixelformat: u32,
pub field: u32,
pub colorspace: u32,
pub plane_fmt: [PlanePixFormat; 8],
pub num_planes: u8,
pub flags: u8,
pub ycbcr_enc: u8,
pub quantization: u8,
pub xfer_func: u8,
pub reserved: [u8; 7],
}
impl PixFormatMplane {
pub const fn zeroed() -> Self {
Self {
width: 0,
height: 0,
pixelformat: 0,
field: 0,
colorspace: 0,
plane_fmt: [PlanePixFormat {
sizeimage: 0,
bytesperline: 0,
reserved: [0; 6],
}; 8],
num_planes: 0,
flags: 0,
ycbcr_enc: 0,
quantization: 0,
xfer_func: 0,
reserved: [0; 7],
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union FormatUnion {
pub pix_mp: PixFormatMplane,
pub raw: [u8; 200],
align: [usize; 1],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Format {
pub buf_type: u32,
pub fmt: FormatUnion,
}
impl Format {
pub const fn zeroed(buf_type: u32) -> Self {
Self {
buf_type,
fmt: FormatUnion { raw: [0; 200] },
}
}
pub fn pix_mp(&self) -> &PixFormatMplane {
unsafe { &self.fmt.pix_mp }
}
pub fn pix_mp_mut(&mut self) -> &mut PixFormatMplane {
unsafe { &mut self.fmt.pix_mp }
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct RequestBuffers {
pub count: u32,
pub buf_type: u32,
pub memory: u32,
pub capabilities: u32,
pub flags: u8,
pub reserved: [u8; 3],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ExportBuffer {
pub buf_type: u32,
pub index: u32,
pub plane: u32,
pub flags: u32,
pub fd: i32,
pub reserved: [u32; 11],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union PlaneMemory {
pub mem_offset: u32,
pub userptr: usize,
pub fd: i32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Plane {
pub bytesused: u32,
pub length: u32,
pub m: PlaneMemory,
pub data_offset: u32,
pub reserved: [u32; 11],
}
impl Plane {
pub const fn zeroed() -> Self {
Self {
bytesused: 0,
length: 0,
m: PlaneMemory { userptr: 0 },
data_offset: 0,
reserved: [0; 11],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Timecode {
pub tc_type: u32,
pub flags: u32,
pub frames: u8,
pub seconds: u8,
pub minutes: u8,
pub hours: u8,
pub userbits: [u8; 4],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union BufferMemory {
pub offset: u32,
pub userptr: usize,
pub planes: *mut Plane,
pub fd: i32,
}
#[repr(C)]
pub struct Buffer {
pub index: u32,
pub buf_type: u32,
pub bytesused: u32,
pub flags: u32,
pub field: u32,
pub timestamp: Timeval,
pub timecode: Timecode,
pub sequence: u32,
pub memory: u32,
pub m: BufferMemory,
pub length: u32,
pub reserved2: u32,
pub request_fd: i32,
}
impl Buffer {
pub fn mplane(index: u32, buf_type: u32, planes: &mut [Plane]) -> Self {
Self {
index,
buf_type,
bytesused: 0,
flags: 0,
field: 0,
timestamp: Timeval::default(),
timecode: Timecode::default(),
sequence: 0,
memory: MEMORY_MMAP,
m: BufferMemory {
planes: planes.as_mut_ptr(),
},
length: planes.len() as u32,
reserved2: 0,
request_fd: 0,
}
}
}
#[derive(Clone, Copy)]
enum Dir {
R,
W,
Rw,
}
struct Vidioc<'a, T> {
nr: u8,
dir: Dir,
arg: &'a mut T,
}
unsafe impl<T> Ioctl for Vidioc<'_, T> {
type Output = ();
const IS_MUTATING: bool = true;
fn opcode(&self) -> Opcode {
match self.dir {
Dir::R => ioctl::opcode::read::<T>(b'V', self.nr),
Dir::W => ioctl::opcode::write::<T>(b'V', self.nr),
Dir::Rw => ioctl::opcode::read_write::<T>(b'V', self.nr),
}
}
fn as_ptr(&mut self) -> *mut core::ffi::c_void {
(self.arg as *mut T).cast()
}
unsafe fn output_from_ptr(
_out: IoctlOutput,
_ptr: *mut core::ffi::c_void,
) -> rustix::io::Result<Self::Output> {
Ok(())
}
}
fn vidioc<T>(fd: BorrowedFd<'_>, nr: u8, dir: Dir, arg: &mut T) -> Result<(), Errno> {
unsafe { ioctl::ioctl(fd, Vidioc { nr, dir, arg }) }
}
const NR_QUERYCAP: u8 = 0;
const NR_ENUM_FMT: u8 = 2;
const NR_G_FMT: u8 = 4;
const NR_S_FMT: u8 = 5;
const NR_REQBUFS: u8 = 8;
const NR_QUERYBUF: u8 = 9;
const NR_QBUF: u8 = 15;
const NR_EXPBUF: u8 = 16;
const NR_DQBUF: u8 = 17;
const NR_STREAMON: u8 = 18;
const NR_STREAMOFF: u8 = 19;
pub fn querycap(fd: BorrowedFd<'_>) -> Result<Capability, Errno> {
let mut cap = Capability::zeroed();
vidioc(fd, NR_QUERYCAP, Dir::R, &mut cap)?;
Ok(cap)
}
pub fn enum_fmt(fd: BorrowedFd<'_>, buf_type: u32, index: u32) -> Result<Option<FmtDesc>, Errno> {
let mut desc = FmtDesc::at(index, buf_type);
match vidioc(fd, NR_ENUM_FMT, Dir::Rw, &mut desc) {
Ok(()) => Ok(Some(desc)),
Err(Errno::INVAL) => Ok(None),
Err(e) => Err(e),
}
}
pub fn g_fmt(fd: BorrowedFd<'_>, buf_type: u32) -> Result<Format, Errno> {
let mut format = Format::zeroed(buf_type);
vidioc(fd, NR_G_FMT, Dir::Rw, &mut format)?;
Ok(format)
}
pub fn s_fmt(fd: BorrowedFd<'_>, format: &mut Format) -> Result<(), Errno> {
vidioc(fd, NR_S_FMT, Dir::Rw, format)
}
pub fn reqbufs(fd: BorrowedFd<'_>, buf_type: u32, count: u32) -> Result<u32, Errno> {
let mut req = RequestBuffers {
count,
buf_type,
memory: MEMORY_MMAP,
..RequestBuffers::default()
};
vidioc(fd, NR_REQBUFS, Dir::Rw, &mut req)?;
Ok(req.count)
}
pub fn querybuf(
fd: BorrowedFd<'_>,
buf_type: u32,
index: u32,
planes: &mut [Plane],
) -> Result<(), Errno> {
let mut buffer = Buffer::mplane(index, buf_type, planes);
vidioc(fd, NR_QUERYBUF, Dir::Rw, &mut buffer)
}
pub fn qbuf(fd: BorrowedFd<'_>, buffer: &mut Buffer) -> Result<(), Errno> {
vidioc(fd, NR_QBUF, Dir::Rw, buffer)
}
pub fn dqbuf(fd: BorrowedFd<'_>, buffer: &mut Buffer) -> Result<Option<()>, Errno> {
match vidioc(fd, NR_DQBUF, Dir::Rw, buffer) {
Ok(()) => Ok(Some(())),
Err(Errno::AGAIN) => Ok(None),
Err(e) => Err(e),
}
}
pub fn streamon(fd: BorrowedFd<'_>, buf_type: u32) -> Result<(), Errno> {
let mut arg = buf_type;
vidioc(fd, NR_STREAMON, Dir::W, &mut arg)
}
pub fn streamoff(fd: BorrowedFd<'_>, buf_type: u32) -> Result<(), Errno> {
let mut arg = buf_type;
vidioc(fd, NR_STREAMOFF, Dir::W, &mut arg)
}
pub fn expbuf(fd: BorrowedFd<'_>, buf_type: u32, index: u32, plane: u32) -> Result<i32, Errno> {
let mut exp = ExportBuffer {
buf_type,
index,
plane,
flags: 0o2000000 | 0o2,
..ExportBuffer::default()
};
vidioc(fd, NR_EXPBUF, Dir::Rw, &mut exp)?;
Ok(exp.fd)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_struct_sizes_match_the_kernel_abi() {
use core::mem::size_of;
assert_eq!(size_of::<Capability>(), 104);
assert_eq!(size_of::<FmtDesc>(), 64);
assert_eq!(size_of::<PixFormatMplane>(), 192);
assert_eq!(size_of::<Format>(), 204 + size_of::<usize>() - 4);
assert_eq!(size_of::<RequestBuffers>(), 20);
assert_eq!(size_of::<ExportBuffer>(), 64);
assert_eq!(size_of::<Timecode>(), 16);
#[cfg(target_pointer_width = "64")]
{
assert_eq!(size_of::<Plane>(), 64);
assert_eq!(size_of::<Buffer>(), 88);
}
#[cfg(target_pointer_width = "32")]
{
assert_eq!(size_of::<Plane>(), 60);
assert_eq!(size_of::<Buffer>(), 68);
}
}
#[test]
fn fourccs_spell_correctly() {
assert_eq!(PIX_FMT_H264, 0x3436_3248); assert_eq!(PIX_FMT_NV12, 0x3231_564E);
}
}
pub const EVENT_EOS: u32 = 2;
pub const EVENT_SOURCE_CHANGE: u32 = 5;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct EventSubscription {
pub event_type: u32,
pub id: u32,
pub flags: u32,
pub reserved: [u32; 5],
}
#[repr(C)]
pub struct Event {
pub event_type: u32,
pub u: [u64; 8],
pub pending: u32,
pub sequence: u32,
pub timestamp: [isize; 2],
pub id: u32,
pub reserved: [u32; 8],
}
impl Event {
pub const fn zeroed() -> Self {
Self {
event_type: 0,
u: [0; 8],
pending: 0,
sequence: 0,
timestamp: [0; 2],
id: 0,
reserved: [0; 8],
}
}
}
const NR_DQEVENT: u8 = 89;
const NR_SUBSCRIBE_EVENT: u8 = 90;
pub fn subscribe_event(fd: BorrowedFd<'_>, event_type: u32) -> Result<(), Errno> {
let mut sub = EventSubscription {
event_type,
..EventSubscription::default()
};
vidioc(fd, NR_SUBSCRIBE_EVENT, Dir::W, &mut sub)
}
pub fn dqevent(fd: BorrowedFd<'_>) -> Result<Option<Event>, Errno> {
let mut event = Event::zeroed();
match vidioc(fd, NR_DQEVENT, Dir::R, &mut event) {
Ok(()) => Ok(Some(event)),
Err(Errno::NOENT) => Ok(None),
Err(e) => Err(e),
}
}