#[macro_use]
mod macros;
mod buf_type;
pub mod controls;
pub mod format;
mod pixel_format;
mod raw;
mod shared;
pub mod stream;
pub mod uvc;
use pixel_format::PixelFormat;
use std::{
fmt,
fs::{self, File, OpenOptions},
io::{self, Read, Write},
mem::{self, MaybeUninit},
os::unix::prelude::*,
path::{Path, PathBuf},
};
use controls::{ControlDesc, ControlIter, TextMenuIter};
use format::{Format, FormatDescIter, FrameIntervals, FrameSizes, MetaFormat, PixFormat};
use raw::controls::Cid;
use shared::{CaptureParamFlags, Memory, StreamParamCaps};
use stream::{ReadStream, WriteStream, DEFAULT_BUFFER_COUNT};
pub use buf_type::*;
pub use shared::{
AnalogStd, CapabilityFlags, Fract, InputCapabilities, InputStatus, InputType,
OutputCapabilities, OutputType,
};
const DEVICE_PREFIXES: &[&str] = &[
"video",
"vbi",
"radio",
"swradio",
"v4l-touch",
"v4l-subdev",
];
pub fn list() -> io::Result<impl Iterator<Item = io::Result<Device>>> {
Ok(fs::read_dir("/dev")?.flat_map(|file| {
let file = match file {
Ok(file) => file,
Err(e) => return Some(Err(e.into())),
};
let name = file.file_name();
if !DEVICE_PREFIXES
.iter()
.any(|p| name.as_bytes().starts_with(p.as_bytes()))
{
return None;
}
match file.file_type() {
Ok(ty) => {
if !ty.is_char_device() {
log::warn!(
"'{}' is not a character device: {:?}",
name.to_string_lossy(),
ty,
);
return None;
}
}
Err(e) => return Some(Err(e.into())),
}
Some(Device::open(&file.path()))
}))
}
#[derive(Debug)]
pub struct Device {
file: File,
available_capabilities: CapabilityFlags,
}
impl Device {
pub fn open<A: AsRef<Path>>(path: A) -> io::Result<Self> {
Self::open_impl(path.as_ref())
}
fn open_impl(path: &Path) -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
let mut this = Self {
file,
available_capabilities: CapabilityFlags::empty(),
};
let caps = this.capabilities()?;
this.available_capabilities = caps.device_capabilities();
Ok(this)
}
fn fd(&self) -> RawFd {
self.file.as_raw_fd()
}
pub fn path(&self) -> io::Result<PathBuf> {
fs::read_link(format!("/proc/self/fd/{}", self.fd()))
}
pub fn capabilities(&self) -> io::Result<Capabilities> {
unsafe {
let mut caps = MaybeUninit::uninit();
let res = raw::VIDIOC_QUERYCAP.ioctl(self, caps.as_mut_ptr())?;
assert_eq!(res, 0);
Ok(Capabilities(caps.assume_init()))
}
}
pub fn supported_buf_types(&self) -> BufTypes {
BufTypes::from_capabilities(self.available_capabilities)
}
pub fn formats(&self, buf_type: BufType) -> FormatDescIter<'_> {
FormatDescIter::new(self, buf_type)
}
pub fn frame_sizes(&self, pixel_format: PixelFormat) -> io::Result<FrameSizes> {
FrameSizes::new(self, pixel_format)
}
pub fn frame_intervals(
&self,
pixel_format: PixelFormat,
width: u32,
height: u32,
) -> io::Result<FrameIntervals> {
FrameIntervals::new(self, pixel_format, width, height)
}
pub fn inputs(&self) -> InputIter<'_> {
InputIter {
device: self,
next_index: 0,
finished: false,
}
}
pub fn outputs(&self) -> OutputIter<'_> {
OutputIter {
device: self,
next_index: 0,
finished: false,
}
}
pub fn controls(&self) -> ControlIter<'_> {
ControlIter::new(self)
}
pub fn enumerate_menu(&self, ctrl: &ControlDesc) -> TextMenuIter<'_> {
TextMenuIter::new(self, ctrl)
}
pub fn read_control_raw(&self, cid: Cid) -> io::Result<i32> {
let mut control = raw::controls::Control { id: cid, value: 0 };
unsafe {
raw::VIDIOC_G_CTRL.ioctl(self, &mut control)?;
}
Ok(control.value)
}
pub fn write_control_raw(&mut self, cid: Cid, value: i32) -> io::Result<()> {
let mut control = raw::controls::Control { id: cid, value };
unsafe {
raw::VIDIOC_S_CTRL.ioctl(self, &mut control)?;
}
Ok(())
}
pub fn format(&self, buf_type: BufType) -> io::Result<Format> {
unsafe {
let mut format = raw::Format {
type_: buf_type,
..mem::zeroed()
};
raw::VIDIOC_G_FMT.ioctl(self, &mut format)?;
let fmt = Format::from_raw(format)
.unwrap_or_else(|| todo!("unsupported buffer type {:?}", buf_type));
Ok(fmt)
}
}
fn set_format_raw(&mut self, format: Format) -> io::Result<Format> {
unsafe {
let mut raw_format: raw::Format = mem::zeroed();
match format {
Format::VideoCapture(f) => {
raw_format.type_ = BufType::VIDEO_CAPTURE;
raw_format.fmt.pix = f.to_raw();
}
Format::VideoOutput(f) => {
raw_format.type_ = BufType::VIDEO_OUTPUT;
raw_format.fmt.pix = f.to_raw();
}
Format::VideoCaptureMplane(f) => {
raw_format.type_ = BufType::VIDEO_CAPTURE_MPLANE;
raw_format.fmt.pix_mp = f.to_raw();
}
Format::VideoOutputMplane(f) => {
raw_format.type_ = BufType::VIDEO_OUTPUT_MPLANE;
raw_format.fmt.pix_mp = f.to_raw();
}
Format::VideoOverlay(f) => {
raw_format.type_ = BufType::VIDEO_OVERLAY;
raw_format.fmt.win = f.to_raw();
}
Format::MetaCapture(f) => {
raw_format.type_ = BufType::META_CAPTURE;
raw_format.fmt.meta = f.to_raw();
}
Format::MetaOutput(f) => {
raw_format.type_ = BufType::META_OUTPUT;
raw_format.fmt.meta = f.to_raw();
}
}
raw::VIDIOC_S_FMT.ioctl(self, &mut raw_format)?;
let fmt = Format::from_raw(raw_format).unwrap();
Ok(fmt)
}
}
pub fn video_capture(mut self, format: PixFormat) -> io::Result<VideoCaptureDevice> {
let format = match self.set_format_raw(Format::VideoCapture(format))? {
Format::VideoCapture(fmt) => fmt,
_ => unreachable!(),
};
Ok(VideoCaptureDevice {
file: self.file,
format,
})
}
pub fn video_output(mut self, format: PixFormat) -> io::Result<VideoOutputDevice> {
let format = match self.set_format_raw(Format::VideoOutput(format))? {
Format::VideoOutput(fmt) => fmt,
_ => unreachable!(),
};
Ok(VideoOutputDevice {
file: self.file,
format,
})
}
pub fn meta_capture(mut self, format: MetaFormat) -> io::Result<MetaCaptureDevice> {
let format = match self.set_format_raw(Format::MetaCapture(format))? {
Format::MetaCapture(fmt) => fmt,
_ => unreachable!(),
};
Ok(MetaCaptureDevice {
file: self.file,
format,
})
}
}
impl AsRawFd for Device {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for Device {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct VideoCaptureDevice {
file: File,
format: PixFormat,
}
impl VideoCaptureDevice {
pub fn format(&self) -> &PixFormat {
&self.format
}
pub fn set_frame_interval(&self, interval: Fract) -> io::Result<Fract> {
unsafe {
let mut parm = raw::StreamParm {
type_: BufType::VIDEO_CAPTURE,
union: raw::StreamParmUnion {
capture: raw::CaptureParm {
timeperframe: interval,
capability: StreamParamCaps::TIMEPERFRAME,
capturemode: CaptureParamFlags::empty(),
extendedmode: 0,
readbuffers: 0,
reserved: [0; 4],
},
},
};
raw::VIDIOC_S_PARM.ioctl(self, &mut parm)?;
Ok(parm.union.capture.timeperframe)
}
}
pub fn into_stream(self) -> io::Result<ReadStream> {
Ok(ReadStream::new(
self.file,
BufType::VIDEO_CAPTURE,
Memory::MMAP,
DEFAULT_BUFFER_COUNT,
)?)
}
}
impl Read for VideoCaptureDevice {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
}
impl AsRawFd for VideoCaptureDevice {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for VideoCaptureDevice {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct VideoOutputDevice {
file: File,
format: PixFormat,
}
impl VideoOutputDevice {
pub fn format(&self) -> &PixFormat {
&self.format
}
pub fn into_stream(self) -> io::Result<WriteStream> {
Ok(WriteStream::new(
self.file,
BufType::VIDEO_CAPTURE,
Memory::MMAP,
DEFAULT_BUFFER_COUNT,
)?)
}
}
impl Write for VideoOutputDevice {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl AsRawFd for VideoOutputDevice {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for VideoOutputDevice {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct MetaCaptureDevice {
file: File,
format: MetaFormat,
}
impl MetaCaptureDevice {
pub fn format(&self) -> &MetaFormat {
&self.format
}
pub fn into_stream(self) -> io::Result<ReadStream> {
Ok(ReadStream::new(
self.file,
BufType::META_CAPTURE,
Memory::MMAP,
DEFAULT_BUFFER_COUNT,
)?)
}
}
impl Read for MetaCaptureDevice {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
}
impl AsRawFd for MetaCaptureDevice {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for MetaCaptureDevice {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct Capabilities(raw::Capabilities);
impl Capabilities {
pub fn driver(&self) -> &str {
byte_array_to_str(&self.0.driver)
}
pub fn card(&self) -> &str {
byte_array_to_str(&self.0.card)
}
pub fn bus_info(&self) -> &str {
byte_array_to_str(&self.0.bus_info)
}
pub fn all_capabilities(&self) -> CapabilityFlags {
self.0.capabilities
}
pub fn device_capabilities(&self) -> CapabilityFlags {
if self.0.capabilities.contains(CapabilityFlags::DEVICE_CAPS) {
self.0.device_caps
} else {
self.0.capabilities
}
}
}
impl fmt::Debug for Capabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Capabilities")
.field("driver", &self.driver())
.field("card", &self.card())
.field("bus_info", &self.bus_info())
.field("capabilities", &self.0.capabilities)
.field("device_caps", &self.0.device_caps)
.finish()
}
}
pub struct OutputIter<'a> {
device: &'a Device,
next_index: u32,
finished: bool,
}
impl Iterator for OutputIter<'_> {
type Item = io::Result<Output>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
unsafe {
let mut raw = raw::Output {
index: self.next_index,
..mem::zeroed()
};
match raw::VIDIOC_ENUMOUTPUT.ioctl(self.device, &mut raw) {
Ok(_) => {}
Err(e) => {
self.finished = true;
if e.raw_os_error() == Some(libc::EINVAL as _) {
return None;
} else {
return Some(Err(e));
}
}
}
self.next_index += 1;
Some(Ok(Output(raw)))
}
}
}
pub struct InputIter<'a> {
device: &'a Device,
next_index: u32,
finished: bool,
}
impl Iterator for InputIter<'_> {
type Item = io::Result<Input>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
unsafe {
let mut raw = raw::Input {
index: self.next_index,
..mem::zeroed()
};
match raw::VIDIOC_ENUMINPUT.ioctl(self.device, &mut raw) {
Ok(_) => {}
Err(e) => {
self.finished = true;
if e.raw_os_error() == Some(libc::EINVAL as _) {
return None;
} else {
return Some(Err(e));
}
}
}
self.next_index += 1;
Some(Ok(Input(raw)))
}
}
}
pub struct Output(raw::Output);
impl Output {
pub fn name(&self) -> &str {
byte_array_to_str(&self.0.name)
}
#[inline]
pub fn output_type(&self) -> OutputType {
self.0.type_
}
#[inline]
pub fn audioset(&self) -> u32 {
self.0.audioset
}
#[inline]
pub fn modulator(&self) -> u32 {
self.0.modulator
}
#[inline]
pub fn std(&self) -> AnalogStd {
self.0.std
}
#[inline]
pub fn capabilities(&self) -> OutputCapabilities {
self.0.capabilities
}
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Output")
.field("index", &self.0.index)
.field("name", &self.name())
.field("output_type", &self.output_type())
.field("audioset", &self.0.audioset)
.field("modulator", &self.0.modulator)
.field("std", &self.0.std)
.field("capabilities", &self.0.capabilities)
.finish()
}
}
pub struct Input(raw::Input);
impl Input {
pub fn name(&self) -> &str {
byte_array_to_str(&self.0.name)
}
#[inline]
pub fn input_type(&self) -> InputType {
self.0.type_
}
#[inline]
pub fn audioset(&self) -> u32 {
self.0.audioset
}
#[inline]
pub fn tuner(&self) -> u32 {
self.0.tuner
}
#[inline]
pub fn std(&self) -> AnalogStd {
self.0.std
}
#[inline]
pub fn status(&self) -> InputStatus {
self.0.status
}
#[inline]
pub fn capabilities(&self) -> InputCapabilities {
self.0.capabilities
}
}
impl fmt::Debug for Input {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Input")
.field("index", &self.0.index)
.field("name", &self.name())
.field("input_type", &self.input_type())
.field("audioset", &self.0.audioset)
.field("tuner", &self.0.tuner)
.field("std", &self.0.std)
.field("status", &self.0.status)
.field("capabilities", &self.0.capabilities)
.finish()
}
}
fn byte_array_to_str(bytes: &[u8]) -> &str {
let len = bytes
.iter()
.position(|b| *b == 0)
.expect("missing NUL terminator");
std::str::from_utf8(&bytes[..len]).unwrap()
}