use crate::ioctl;
use crate::util;
use crate::vp8::{self, CtrlVp8Frame};
use crate::Request;
use bitflags::bitflags;
use core::ffi::CStr;
use core::fmt;
use nix::errno::Errno;
use std::fs::OpenOptions;
use std::io;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
use std::os::raw::{c_char, c_long, c_void};
use std::path::Path;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default)]
pub struct CapsFlags: u32 {
const VIDEO_CAPTURE = 0x00000001;
const VIDEO_OUTPUT = 0x00000002;
const VIDEO_OVERLAY = 0x00000004;
const VBI_CAPTURE = 0x00000010;
const VBI_OUTPUT = 0x00000020;
const SLICED_VBI_CAPTURE = 0x00000040;
const SLICED_VBI_OUTPUT = 0x00000080;
const RDS_CAPTURE = 0x00000100;
const VIDEO_OUTPUT_OVERLAY = 0x00000200;
const HW_FREQ_SEEK = 0x00000400;
const RDS_OUTPUT = 0x00000800;
const VIDEO_CAPTURE_MPLANE = 0x00001000;
const VIDEO_OUTPUT_MPLANE = 0x00002000;
const VIDEO_M2M_MPLANE = 0x00004000;
const VIDEO_M2M = 0x00008000;
const TUNER = 0x00010000;
const AUDIO = 0x00020000;
const RADIO = 0x00040000;
const MODULATOR = 0x00080000;
const SDR_CAPTURE = 0x00100000;
const EXT_PIX_FORMAT = 0x00200000;
const SDR_OUTPUT = 0x00400000;
const META_CAPTURE = 0x00800000;
const READWRITE = 0x01000000;
const ASYNCIO = 0x02000000;
const STREAMING = 0x04000000;
const META_OUTPUT = 0x08000000;
const TOUCH = 0x10000000;
const IO_MC = 0x20000000;
const DEVICE_CAPS = 0x80000000;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default)]
struct BufFlag: u32 {
const MAPPED = 0x00000001;
const QUEUED = 0x00000002;
const DONE = 0x00000004;
const KEYFRAME = 0x00000008;
const PFRAME = 0x00000010;
const BFRAME = 0x00000020;
const ERROR = 0x00000040;
const IN_REQUEST = 0x00000080;
const TIMECODE = 0x00000100;
const M2M_HOLD_CAPTURE_BUF = 0x00000200;
const PREPARED = 0x00000400;
const NO_CACHE_INVALIDATE = 0x00000800;
const NO_CACHE_CLEAN = 0x00001000;
const TIMESTAMP_MASK = 0x0000e000;
const TIMESTAMP_UNKNOWN = 0x00000000;
const TIMESTAMP_MONOTONIC = 0x00002000;
const TIMESTAMP_COPY = 0x00004000;
const TSTAMP_SRC_MASK = 0x00070000;
const TSTAMP_SRC_EOF = 0x00000000;
const TSTAMP_SRC_SOE = 0x00010000;
const LAST = 0x00100000;
const REQUEST_FD = 0x00800000;
}
}
#[derive(Default, Clone)]
#[repr(C)]
pub struct Capability {
driver: [c_char; 16],
card: [c_char; 32],
bus_info: [c_char; 32],
version: u32,
capabilities: CapsFlags,
device_caps: CapsFlags,
reserved: [u32; 3],
}
impl fmt::Debug for Capability {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Capability")
.field("driver", &self.driver())
.field("card", &self.card())
.field("bus_info", &self.bus_info())
.field("version", &self.version())
.field("capabilities", &self.capabilities)
.field("device_caps", &self.device_caps)
.finish()
}
}
impl Capability {
pub fn driver(&self) -> &str {
unsafe {
let c_str = CStr::from_ptr(self.driver.as_ptr());
std::str::from_utf8_unchecked(c_str.to_bytes())
}
}
pub fn card(&self) -> &str {
unsafe {
let c_str = CStr::from_ptr(self.card.as_ptr());
std::str::from_utf8_unchecked(c_str.to_bytes())
}
}
pub fn bus_info(&self) -> &str {
unsafe {
let c_str = CStr::from_ptr(self.bus_info.as_ptr());
std::str::from_utf8_unchecked(c_str.to_bytes())
}
}
pub fn version(&self) -> String {
util::format_kernel_version(self.version)
}
pub fn capabilities(&self) -> CapsFlags {
self.capabilities
}
pub fn device_caps(&self) -> CapsFlags {
self.device_caps
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default)]
pub struct FmtFlag: u32 {
const COMPRESSED = 0x0001;
const EMULATED = 0x0002;
const CONTINUOUS_BYTESTREAM = 0x0004;
const DYN_RESOLUTION = 0x0008;
const ENC_CAP_FRAME_INTERVAL = 0x0010;
const CSC_COLORSPACE = 0x0020;
const CSC_XFER_FUNC = 0x0040;
const CSC_YCBCR_ENC = 0x0080;
const CSC_HSV_ENC = FmtFlag::CSC_YCBCR_ENC.bits();
const CSC_QUANTIZATION = 0x0100;
}
}
#[derive(Default, Clone)]
#[repr(C)]
pub struct FmtDesc {
index: u32,
type_: BufType,
flags: FmtFlag,
description: [c_char; 32],
pixelformat: u32,
mbus_code: u32,
reserved: [u32; 3],
}
impl fmt::Debug for FmtDesc {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("FmtDesc")
.field("index", &self.index)
.field("type", &self.type_)
.field("flags", &self.flags)
.field("pixelformat", &self.pixelformat_to_string())
.field("mbus_code", &self.mbus_code)
.field("description", &self.description())
.finish()
}
}
impl FmtDesc {
pub fn description(&self) -> &str {
unsafe {
let c_str = CStr::from_ptr(self.description.as_ptr());
std::str::from_utf8_unchecked(c_str.to_bytes())
}
}
pub fn pixelformat_to_string(&self) -> String {
let bytes = self.pixelformat.to_ne_bytes();
unsafe { String::from_utf8_unchecked(bytes.to_vec()) }
}
pub fn pixelformat(&self) -> u32 {
self.pixelformat
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum BufType {
VideoCapture = 1,
VideoOutput = 2,
VideoCaptureMplane = 9,
VideoOutputMplane = 10,
}
impl Default for BufType {
fn default() -> BufType {
BufType::VideoCapture
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u32)]
pub enum Memory {
Mmap = 1,
UserPtr = 2,
Overlay = 3,
Dmabuf = 4,
}
impl Default for Memory {
fn default() -> Memory {
Memory::Mmap
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default)]
struct BufCap: u32 {
const SUPPORTS_MMAP = 0x01;
const SUPPORTS_USERPTR = 0x02;
const SUPPORTS_DMABUF = 0x04;
const SUPPORTS_REQUESTS = 0x08;
const SUPPORTS_ORPHANED_BUFS = 0x10;
const SUPPORTS_M2M_HOLD_CAPTURE_BUF = 0x20;
const SUPPORTS_MMAP_CACHE_HINTS = 0x40;
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct RequestBuffers {
count: u32,
type_: BufType,
memory: Memory,
capabilities: BufCap,
flags: u8,
reserved: [u8; 3],
}
#[derive(Default, Clone, Debug)]
#[repr(C)]
struct Timeval {
tv_sec: u64,
tv_nsec: u64,
}
#[derive(Default, Clone, Debug)]
#[repr(C)]
struct Timespec {
tv_sec: c_long,
tv_nsec: c_long,
}
#[derive(Default, Clone, Debug)]
#[repr(C)]
struct Timecode {
type_: u32,
flags: u32,
frames: u8,
seconds: u8,
minutes: u8,
hours: u8,
userbits: [u8; 4],
}
union M {
offset: u32,
userptr: usize,
planes: *mut c_void,
fd: RawFd,
}
impl Default for M {
fn default() -> M {
M { offset: 0 }
}
}
impl fmt::Debug for M {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("M")
.field("offset", &unsafe { self.offset })
.finish()
}
}
#[derive(Default, Debug)]
#[repr(C)]
pub struct Buffer {
index: u32,
type_: BufType,
bytesused: u32,
flags: BufFlag,
field: u32,
timestamp: Timeval,
timecode: Timecode,
sequence: u32,
memory: Memory,
m: M,
length: u32,
reserved2: u32,
request_fd: RawFd,
}
impl Buffer {
pub fn new(type_: BufType) -> Buffer {
let mut buf = Buffer::default();
buf.type_ = type_;
buf.memory = Memory::Mmap;
buf
}
pub fn set_request(&mut self, request: &Request) {
self.flags |= BufFlag::REQUEST_FD;
self.request_fd = request.as_raw_fd();
}
pub fn set_bytesused(&mut self, bytesused: usize) {
self.bytesused = bytesused as u32;
}
pub fn bytesused(&mut self) -> u32 {
self.bytesused
}
pub fn length(&self) -> u32 {
self.length
}
pub fn set_offset(&mut self, offset: u32) {
self.m.offset = offset;
}
pub fn is_error(&self) -> bool {
self.flags.contains(BufFlag::ERROR)
}
pub fn offset(&self) -> u32 {
assert_eq!(self.memory, Memory::Mmap);
unsafe { self.m.offset }
}
pub fn dmabuf(&self) -> RawFd {
assert_eq!(self.memory, Memory::Dmabuf);
unsafe { self.m.fd }
}
}
#[derive(Debug)]
#[repr(C)]
pub struct ExportBuffer {
type_: BufType,
index: u32,
plane: u32,
flags: u32,
fd: Option<OwnedFd>,
reserved: [u32; 11],
}
impl ExportBuffer {
pub fn new(type_: BufType, index: u32, flags: u32) -> ExportBuffer {
ExportBuffer {
type_,
index,
plane: 0,
flags,
fd: None,
reserved: Default::default(),
}
}
pub fn into_fd(self) -> OwnedFd {
self.fd.unwrap()
}
}
#[derive(Default, Clone, Copy, Debug)]
#[repr(C)]
struct PixFormat {
width: u32,
height: u32,
pixelformat: u32,
field: u32,
bytesperline: u32,
sizeimage: u32,
colorspace: u32,
priv_: u32,
flags: u32,
enc: u32,
quantization: u32,
xfer_func: u32,
}
#[cfg_attr(target_pointer_width = "32", repr(C))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
union Fmt {
pix: PixFormat,
raw_data: [u8; 200],
}
#[repr(C)]
pub struct Format {
type_: BufType,
fmt: Fmt,
}
impl fmt::Debug for Format {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Format")
.field("type", &self.type_)
.field(
"pix",
&match self.type_ {
BufType::VideoCapture | BufType::VideoOutput => unsafe { self.fmt.pix },
type_ => todo!("Add multiplanar API for {type_:?}"),
},
)
.finish()
}
}
impl Format {
pub fn new(
type_: BufType,
width: u32,
height: u32,
pixelformat: u32,
sizeimage: u32,
) -> Format {
match type_ {
BufType::VideoCapture => {
let fmt = Fmt {
pix: PixFormat {
width,
height,
pixelformat,
sizeimage,
..Default::default()
},
};
Format { type_, fmt }
}
BufType::VideoOutput => {
let fmt = Fmt {
pix: PixFormat {
width,
height,
pixelformat,
sizeimage,
..Default::default()
},
};
Format { type_, fmt }
}
type_ => todo!("type: {:?}", type_),
}
}
pub fn width(&self) -> u32 {
unsafe { self.fmt.pix }.width
}
pub fn height(&self) -> u32 {
unsafe { self.fmt.pix }.height
}
pub fn bytesperline(&self) -> u32 {
unsafe { self.fmt.pix }.bytesperline
}
}
#[derive(Clone, Debug)]
#[repr(C, packed)]
pub struct ExtControl {
id: u32,
size: u32,
reserved: u32,
vp8_frame: *mut CtrlVp8Frame,
}
impl ExtControl {
pub fn new(id: u32, vp8_frame: *mut CtrlVp8Frame) -> ExtControl {
let size = std::mem::size_of::<CtrlVp8Frame>() as u32;
ExtControl {
id,
size,
reserved: 0,
vp8_frame,
}
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct ExtControls {
which: u32,
count: u32,
error_idx: u32,
request_fd: RawFd,
reserved: u32,
controls: *mut ExtControl,
}
impl ExtControls {
pub fn new(request: Option<&Request>, count: u32, controls: *mut ExtControl) -> ExtControls {
let (which, request_fd) = match request {
Some(request) => (vp8::uapi::V4L2_CTRL_WHICH_REQUEST_VAL, request.as_raw_fd()),
None => (vp8::uapi::V4L2_CTRL_WHICH_CUR_VAL, 0),
};
ExtControls {
which,
count,
error_idx: 0,
request_fd,
reserved: 0,
controls,
}
}
}
pub struct Video {
fd: OwnedFd,
}
impl AsFd for Video {
fn as_fd(&self) -> BorrowedFd {
self.fd.as_fd()
}
}
impl Video {
pub fn open<P: AsRef<Path>>(filename: P) -> io::Result<Video> {
let file = OpenOptions::new().read(true).write(true).open(filename)?;
let fd = file.into();
Ok(Video { fd })
}
pub fn querycap(&self) -> Result<Capability, Errno> {
let mut capability = Capability::default();
unsafe { ioctl::querycap(self.fd.as_raw_fd(), &mut capability) }?;
Ok(capability)
}
pub fn enum_fmts(&self, type_: BufType) -> Result<Vec<FmtDesc>, Errno> {
let mut vec = Vec::new();
let mut fmt = FmtDesc::default();
fmt.type_ = type_;
loop {
match unsafe { ioctl::enum_fmt(self.fd.as_raw_fd(), &mut fmt) } {
Ok(_) => vec.push(fmt.clone()),
Err(errno) if errno == Errno::EINVAL => break,
Err(errno) => return Err(errno),
}
fmt.index += 1;
}
Ok(vec)
}
pub fn s_fmt(&self, format: &mut Format) -> Result<(), Errno> {
unsafe { ioctl::s_fmt(self.fd.as_raw_fd(), format) }?;
Ok(())
}
pub fn reqbufs(&self, memory: Memory, type_: BufType, count: usize) -> Result<(), Errno> {
let count = count as u32;
let mut buffers = RequestBuffers {
memory,
type_,
count,
capabilities: BufCap::empty(),
flags: 0,
reserved: Default::default(),
};
unsafe { ioctl::reqbufs(self.fd.as_raw_fd(), &mut buffers) }?;
Ok(())
}
pub fn querybuf(&self, buf: &mut Buffer) -> Result<(), Errno> {
unsafe { ioctl::querybuf(self.fd.as_raw_fd(), buf) }?;
Ok(())
}
pub fn qbuf(&self, buf: &mut Buffer) -> Result<(), Errno> {
unsafe { ioctl::qbuf(self.fd.as_raw_fd(), buf) }?;
Ok(())
}
pub fn expbuf(&self, expbuf: &mut ExportBuffer) -> Result<(), Errno> {
unsafe { ioctl::expbuf(self.fd.as_raw_fd(), expbuf) }?;
Ok(())
}
pub fn dqbuf(&self, buf: &mut Buffer) -> Result<(), Errno> {
unsafe { ioctl::dqbuf(self.fd.as_raw_fd(), buf) }?;
Ok(())
}
pub fn streamon(&self, type_: BufType) -> Result<(), Errno> {
unsafe { ioctl::streamon(self.fd.as_raw_fd(), &type_) }?;
Ok(())
}
pub fn streamoff(&self, type_: BufType) -> Result<(), Errno> {
unsafe { ioctl::streamoff(self.fd.as_raw_fd(), &type_) }?;
Ok(())
}
pub fn s_ext_ctrls(&self, ctrl: &mut ExtControls) -> Result<(), Errno> {
unsafe { ioctl::s_ext_ctrls(self.fd.as_raw_fd(), ctrl) }?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
);
);
#[test]
fn size() {
assert_size!(Video, 4);
}
}