chore: vendor trimmed v4l2r capture crate

This commit is contained in:
mofeng-git
2026-05-23 02:36:40 +00:00
parent 032f47a891
commit 6a1616c32a
34 changed files with 4851 additions and 0 deletions

1
libs/v4l2r/.cargo-ok Normal file
View File

@@ -0,0 +1 @@
{"v":1}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "7b441383125ae583017a1c18b3fc9ec6c88ddbe8"
},
"path_in_vcs": "lib"
}

52
libs/v4l2r/Android.bp Normal file
View File

@@ -0,0 +1,52 @@
// This file is generated by cargo_embargo.
// Do not modify this file because the changes will be overridden on upgrade.
package {
default_applicable_licenses: ["external_rust_crates_v4l2r_license"],
}
rust_library {
name: "libv4l2r",
crate_name: "v4l2r",
cargo_env_compat: true,
cargo_pkg_version: "0.0.7",
crate_root: "src/lib.rs",
edition: "2021",
rustlibs: [
"libbitflags",
"liblog_rust",
"libnix",
"libthiserror",
],
proc_macros: ["libenumn"],
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
],
product_available: true,
vendor_available: true,
// Bindgen-generated bindings of our local videodev2.h.
srcs: [":libv4l2r_bindgen"],
}
rust_test {
name: "v4l2r_test_src_lib",
crate_name: "v4l2r",
cargo_env_compat: true,
cargo_pkg_version: "0.0.7",
crate_root: "src/lib.rs",
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2021",
rustlibs: [
"libbitflags",
"liblog_rust",
"libnix",
"libthiserror",
],
proc_macros: ["libenumn"],
// Bindgen-generated bindings of our local videodev2.h.
srcs: [":libv4l2r_bindgen"],
}

65
libs/v4l2r/Cargo.toml Normal file
View File

@@ -0,0 +1,65 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "v4l2r"
version = "0.0.7"
authors = ["Alexandre Courbot <gnurou@gmail.com>"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Safe and flexible abstraction over V4L2"
readme = "README.md"
keywords = [
"v4l2",
"video",
"linux",
]
categories = ["os"]
license-file = "LICENSE"
repository = "https://github.com/Gnurou/v4l2r"
[features]
arch32 = []
arch64 = []
[lib]
name = "v4l2r"
path = "src/lib.rs"
[dependencies.bitflags]
version = "2.4"
[dependencies.enumn]
version = "0.1.6"
[dependencies.log]
version = "0.4.14"
[dependencies.nix]
version = "0.28"
features = [
"ioctl",
"mman",
"poll",
"fs",
"event",
]
[dependencies.thiserror]
version = "1.0"
[build-dependencies.bindgen]
version = "0.70.1"

28
libs/v4l2r/Cargo.toml.orig generated Normal file
View File

@@ -0,0 +1,28 @@
[package]
name = "v4l2r"
version = "0.0.7"
authors = ["Alexandre Courbot <gnurou@gmail.com>"]
edition = "2021"
description = "Safe and flexible abstraction over V4L2"
repository = "https://github.com/Gnurou/v4l2r"
categories = ["os"]
keywords = ["v4l2", "video", "linux"]
license-file.workspace = true
readme.workspace = true
[features]
# Generate the bindings for 64-bit even if the host is 32-bit.
arch64 = []
# Generate the bindings for 32-bit even if the host is 64-bit.
arch32 = []
[dependencies]
nix = { version = "0.28", features = ["ioctl", "mman", "poll", "fs", "event"] }
bitflags = "2.4"
thiserror = "1.0"
log = "0.4.14"
enumn = "0.1.6"
[build-dependencies]
bindgen = "0.70.1"

23
libs/v4l2r/LICENSE Normal file
View File

@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

24
libs/v4l2r/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Rust bindings for V4L2
This is a vendored, One-KVM-specific subset of `v4l2r`.
It keeps only the pieces needed for video capture:
- generated Linux V4L2 bindings,
- safe low-level ioctl wrappers used by capture and device probing,
- memory handle helpers for `MMAP`, `USERPTR`, and `DMABUF`,
- core V4L2 types such as `Format`, `PixelFormat`, and `QueueType`.
The upstream crate also contains high-level device abstractions, stateful
decoder/encoder helpers, stateless codec controls, examples, and C FFI. Those
parts are intentionally removed here so this dependency stays scoped to capture.
## Build options
`cargo build` generates V4L2 bindings from `/usr/include/linux/videodev2.h` by
default. Set `V4L2R_VIDEODEV2_H_PATH` to a directory containing `videodev2.h` to
generate bindings from a different header.
For Android targets, the build script uses the Android NDK sysroot. Set one of
`ANDROID_NDK_HOME`, `ANDROID_NDK_ROOT`, `NDK_HOME`, `ANDROID_HOME`, or
`ANDROID_SDK_ROOT` if the NDK cannot be found automatically.

22
libs/v4l2r/bindgen.rs Normal file
View File

@@ -0,0 +1,22 @@
// This file defines the customizations to the bindgen builder used to generate the v4l2r
// bindings.
//
// It is meant to be included from `lib/build.rs` and `android/build.rs`.
#[derive(Debug)]
/// Workaround for https://github.com/rust-lang/rust-bindgen/issues/753.
pub struct Fix753;
impl bindgen::callbacks::ParseCallbacks for Fix753 {
fn item_name(&self, original_item_name: &str) -> Option<String> {
Some(original_item_name.trim_start_matches("Fix753_").to_owned())
}
}
fn v4l2r_bindgen_builder(builder: bindgen::Builder) -> bindgen::Builder {
builder
.parse_callbacks(Box::new(Fix753))
.derive_partialeq(true)
.derive_eq(true)
.derive_default(true)
}

180
libs/v4l2r/build.rs Normal file
View File

@@ -0,0 +1,180 @@
use std::env::{self, VarError};
use std::path::PathBuf;
include!("bindgen.rs");
/// Environment variable that can be set to point to the directory containing the `videodev2.h`
/// file to use to generate the bindings.
const V4L2R_VIDEODEV_ENV: &str = "V4L2R_VIDEODEV2_H_PATH";
/// Default header file to parse if the `V4L2R_VIDEODEV2_H_PATH` environment variable is not set.
const DEFAULT_VIDEODEV2_H_PATH: &str = "/usr/include/linux";
/// Wrapper file to use as input of bindgen.
const WRAPPER_H: &str = "v4l2r_wrapper.h";
// Fix for https://github.com/rust-lang/rust-bindgen/issues/753
const FIX753_H: &str = "fix753.h";
fn main() {
let target = env::var("TARGET").unwrap_or_default();
let is_android = target.contains("android");
let default_videodev2_h_path = if is_android {
android_sysroot().join("usr/include").display().to_string()
} else {
DEFAULT_VIDEODEV2_H_PATH.to_string()
};
let videodev2_h_path = env::var(V4L2R_VIDEODEV_ENV)
.or_else(|e| {
if let VarError::NotPresent = e {
Ok(default_videodev2_h_path.clone())
} else {
Err(e)
}
})
.expect("invalid `V4L2R_VIDEODEV2_H_PATH` environment variable");
let videodev2_h = PathBuf::from(videodev2_h_path.clone()).join(if is_android {
"linux/videodev2.h"
} else {
"videodev2.h"
});
println!("cargo::rerun-if-env-changed={}", V4L2R_VIDEODEV_ENV);
println!("cargo::rerun-if-env-changed=ANDROID_NDK_HOME");
println!("cargo::rerun-if-env-changed=ANDROID_NDK_ROOT");
println!("cargo::rerun-if-env-changed=NDK_HOME");
println!("cargo::rerun-if-env-changed=ANDROID_HOME");
println!("cargo::rerun-if-env-changed=ANDROID_SDK_ROOT");
println!("cargo::rerun-if-env-changed=CARGO_NDK_PLATFORM");
println!("cargo::rerun-if-changed={}", videodev2_h.display());
println!("cargo::rerun-if-changed={}", FIX753_H);
println!("cargo::rerun-if-changed={}", WRAPPER_H);
let mut clang_args = vec![
format!("-I{videodev2_h_path}"),
#[cfg(all(feature = "arch64", not(feature = "arch32")))]
"--target=x86_64-linux-gnu".into(),
#[cfg(all(feature = "arch32", not(feature = "arch64")))]
"--target=i686-linux-gnu".into(),
];
if is_android {
clang_args.extend(android_clang_args(&target));
}
let bindings = v4l2r_bindgen_builder(bindgen::Builder::default())
.header(WRAPPER_H)
.clang_args(clang_args)
.generate()
.expect("unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
fn android_clang_args(target: &str) -> Vec<String> {
let ndk = android_ndk_home();
let toolchain = ndk.join("toolchains/llvm/prebuilt").join(host_tag());
let sysroot = toolchain.join("sysroot");
let clang_include = toolchain
.join("lib/clang")
.join(clang_version(&toolchain))
.join("include");
let api = env::var("CARGO_NDK_PLATFORM")
.ok()
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or(21);
let clang_target = android_clang_target(target);
vec![
format!("--target={clang_target}"),
format!("--sysroot={}", sysroot.display()),
format!("-D__ANDROID_API__={api}"),
format!("-isystem{}", clang_include.display()),
format!("-isystem{}", sysroot.join("usr/include").display()),
format!(
"-isystem{}",
sysroot.join("usr/include").join(clang_target).display()
),
]
}
fn android_clang_target(target: &str) -> &'static str {
match target {
"aarch64-linux-android" => "aarch64-linux-android",
"armv7-linux-androideabi" => "armv7a-linux-androideabi",
"i686-linux-android" => "i686-linux-android",
"x86_64-linux-android" => "x86_64-linux-android",
other => panic!("unsupported Android target for v4l2r bindgen: {other}"),
}
}
fn android_sysroot() -> PathBuf {
android_ndk_home()
.join("toolchains/llvm/prebuilt")
.join(host_tag())
.join("sysroot")
}
fn android_ndk_home() -> PathBuf {
for key in ["ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "NDK_HOME"] {
if let Ok(value) = env::var(key) {
return PathBuf::from(value);
}
}
for key in ["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
if let Ok(value) = env::var(key) {
let ndk_dir = PathBuf::from(value).join("ndk");
if let Some(newest) = newest_child_dir(&ndk_dir) {
return newest;
}
}
}
panic!(
"v4l2r Android bindgen requires ANDROID_NDK_HOME, ANDROID_NDK_ROOT, NDK_HOME, \
or ANDROID_HOME/ANDROID_SDK_ROOT with an ndk directory"
);
}
fn newest_child_dir(path: &PathBuf) -> Option<PathBuf> {
let mut entries = std::fs::read_dir(path)
.ok()?
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| path.is_dir())
.collect::<Vec<_>>();
entries.sort();
entries.pop()
}
fn host_tag() -> &'static str {
if cfg!(target_os = "linux") {
"linux-x86_64"
} else if cfg!(target_os = "macos") {
"darwin-x86_64"
} else if cfg!(target_os = "windows") {
"windows-x86_64"
} else {
panic!("unsupported host OS for Android NDK");
}
}
fn clang_version(toolchain: &PathBuf) -> String {
let clang_dir = toolchain.join("lib/clang");
let mut entries = std::fs::read_dir(&clang_dir)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", clang_dir.display()))
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect::<Vec<_>>();
entries.sort();
entries
.pop()
.unwrap_or_else(|| panic!("no clang resource directory in {}", clang_dir.display()))
}

55
libs/v4l2r/fix753.h Normal file
View File

@@ -0,0 +1,55 @@
#undef V4L2_FWHT_FL_COMPONENTS_NUM_MSK
#undef V4L2_FWHT_FL_PIXENC_MSK
#ifdef V4L2_FWHT_FL_IS_INTERLACED
MARK_FIX_753(V4L2_FWHT_FL_IS_INTERLACED);
#endif
#ifdef V4L2_FWHT_FL_IS_BOTTOM_FIRST
MARK_FIX_753(V4L2_FWHT_FL_IS_BOTTOM_FIRST);
#endif
#ifdef V4L2_FWHT_FL_IS_ALTERNATE
MARK_FIX_753(V4L2_FWHT_FL_IS_ALTERNATE);
#endif
#ifdef V4L2_FWHT_FL_IS_BOTTOM_FIELD
MARK_FIX_753(V4L2_FWHT_FL_IS_BOTTOM_FIELD);
#endif
#ifdef V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED
MARK_FIX_753(V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED);
#endif
#ifdef V4L2_FWHT_FL_CB_IS_UNCOMPRESSED
MARK_FIX_753(V4L2_FWHT_FL_CB_IS_UNCOMPRESSED);
#endif
#ifdef V4L2_FWHT_FL_CR_IS_UNCOMPRESSED
MARK_FIX_753(V4L2_FWHT_FL_CR_IS_UNCOMPRESSED);
#endif
#ifdef V4L2_FWHT_FL_CHROMA_FULL_HEIGHT
MARK_FIX_753(V4L2_FWHT_FL_CHROMA_FULL_HEIGHT);
#endif
#ifdef V4L2_FWHT_FL_CHROMA_FULL_WIDTH
MARK_FIX_753(V4L2_FWHT_FL_CHROMA_FULL_WIDTH);
#endif
#ifdef V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED
MARK_FIX_753(V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED);
#endif
#ifdef V4L2_FWHT_FL_I_FRAME
MARK_FIX_753(V4L2_FWHT_FL_I_FRAME);
#endif
#ifdef V4L2_FWHT_FL_COMPONENTS_NUM_OFFSET
#define V4L2_FWHT_FL_COMPONENTS_NUM_MSK \
(7 << V4L2_FWHT_FL_COMPONENTS_NUM_OFFSET)
#endif
#ifdef V4L2_FWHT_FL_PIXENC_OFFSET
#define V4L2_FWHT_FL_PIXENC_MSK (3 << V4L2_FWHT_FL_PIXENC_OFFSET)
#endif

View File

@@ -0,0 +1,8 @@
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(deref_nullptr)]
#![allow(clippy::all)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

1177
libs/v4l2r/src/ioctl.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
use crate::ioctl::ioctl_and_convert;
use crate::ioctl::IoctlConvertError;
use crate::ioctl::IoctlConvertResult;
use crate::ioctl::UncheckedV4l2Buffer;
use crate::QueueType;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::os::unix::io::AsRawFd;
use nix::errno::Errno;
use thiserror::Error;
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_buffer;
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
}
#[derive(Debug, Error)]
pub enum DqBufIoctlError {
#[error("end-of-stream reached")]
Eos,
#[error("no buffer ready for dequeue")]
NotReady,
#[error("unexpected ioctl error: {0}")]
Other(Errno),
}
impl From<Errno> for DqBufIoctlError {
fn from(error: Errno) -> Self {
match error {
Errno::EAGAIN => Self::NotReady,
Errno::EPIPE => Self::Eos,
error => Self::Other(error),
}
}
}
impl From<DqBufIoctlError> for Errno {
fn from(err: DqBufIoctlError) -> Self {
match err {
DqBufIoctlError::Eos => Errno::EPIPE,
DqBufIoctlError::NotReady => Errno::EAGAIN,
DqBufIoctlError::Other(e) => e,
}
}
}
pub type DqBufError<CE> = IoctlConvertError<DqBufIoctlError, CE>;
pub type DqBufResult<O, CE> = IoctlConvertResult<O, DqBufIoctlError, CE>;
/// Safe wrapper around the `VIDIOC_DQBUF` ioctl.
pub fn dqbuf<O>(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult<O, O::Error>
where
O: TryFrom<UncheckedV4l2Buffer>,
O::Error: std::fmt::Debug,
{
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, None);
ioctl_and_convert(
unsafe { ioctl::vidioc_dqbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
.map(|_| v4l2_buf)
.map_err(Into::into),
)
}

View File

@@ -0,0 +1,137 @@
//! Safe wrapper for the `VIDIOC_ENUM_FMT` ioctl.
use super::string_from_cstr;
use crate::bindings;
use crate::bindings::v4l2_fmtdesc;
use crate::{PixelFormat, QueueType};
use bitflags::bitflags;
use log::error;
use nix::errno::Errno;
use std::fmt;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
bitflags! {
/// Flags returned by the `VIDIOC_ENUM_FMT` ioctl into the `flags` field of
/// `struct v4l2_fmtdesc`.
#[derive(Clone, Copy, Debug)]
pub struct FormatFlags: u32 {
const COMPRESSED = bindings::V4L2_FMT_FLAG_COMPRESSED;
const EMULATED = bindings::V4L2_FMT_FLAG_EMULATED;
}
}
/// Quickly get the Fourcc code of a format.
impl From<v4l2_fmtdesc> for PixelFormat {
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
fmtdesc.pixelformat.into()
}
}
/// Safe variant of the `v4l2_fmtdesc` struct, to be used with `enum_fmt`.
#[derive(Debug)]
pub struct FmtDesc {
pub flags: FormatFlags,
pub description: String,
pub pixelformat: PixelFormat,
}
impl fmt::Display for FmtDesc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}: {} {}",
self.pixelformat,
self.description,
if self.flags.is_empty() {
"".into()
} else {
format!("({:?})", self.flags)
}
)
}
}
impl From<v4l2_fmtdesc> for FmtDesc {
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
FmtDesc {
flags: FormatFlags::from_bits_truncate(fmtdesc.flags),
description: string_from_cstr(&fmtdesc.description).unwrap_or_else(|_| "".into()),
pixelformat: fmtdesc.pixelformat.into(),
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_fmtdesc;
nix::ioctl_readwrite!(vidioc_enum_fmt, b'V', 2, v4l2_fmtdesc);
}
#[derive(Debug, Error)]
pub enum EnumFmtError {
#[error("ioctl error: {0}")]
IoctlError(#[from] nix::Error),
}
impl From<EnumFmtError> for Errno {
fn from(err: EnumFmtError) -> Self {
match err {
EnumFmtError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_ENUM_FMT` ioctl.
pub fn enum_fmt<T: From<v4l2_fmtdesc>>(
fd: &impl AsRawFd,
queue: QueueType,
index: u32,
) -> Result<T, EnumFmtError> {
let mut fmtdesc = v4l2_fmtdesc {
type_: queue as u32,
index,
..Default::default()
};
unsafe { ioctl::vidioc_enum_fmt(fd.as_raw_fd(), &mut fmtdesc) }?;
Ok(T::from(fmtdesc))
}
/// Iterator over the formats of the given queue. This takes a reference to the
/// device's file descriptor so no operation that could affect the format
/// enumeration can take place while the iterator exists.
pub struct FormatIterator<'a, F: AsRawFd> {
fd: &'a F,
queue: QueueType,
index: u32,
}
impl<'a, F: AsRawFd> FormatIterator<'a, F> {
/// Create a new iterator listing all the currently valid formats on
/// `queue`.
pub fn new(fd: &'a F, queue: QueueType) -> Self {
FormatIterator {
fd,
queue,
index: 0,
}
}
}
impl<'a, F: AsRawFd> Iterator for FormatIterator<'a, F> {
type Item = FmtDesc;
fn next(&mut self) -> Option<Self::Item> {
match enum_fmt(self.fd, self.queue, self.index) {
Ok(fmtdesc) => {
self.index += 1;
Some(fmtdesc)
}
// EINVAL means we have reached the last format.
Err(EnumFmtError::IoctlError(Errno::EINVAL)) => None,
_ => {
error!("Unexpected return value for VIDIOC_ENUM_FMT!");
None
}
}
}
}

View File

@@ -0,0 +1,61 @@
//! Safe wrapper for the `VIDIOC_EXPBUF` ioctl.
use bitflags::bitflags;
use nix::errno::Errno;
use nix::fcntl::OFlag;
use std::os::unix::io::{AsRawFd, FromRawFd};
use thiserror::Error;
use crate::bindings::v4l2_exportbuffer;
use crate::QueueType;
bitflags! {
/// Flags that can be passed when exporting the buffer.
#[derive(Clone, Copy, Debug)]
pub struct ExpbufFlags: u32 {
const CLOEXEC = OFlag::O_CLOEXEC.bits() as u32;
const RDONLY = OFlag::O_RDONLY.bits() as u32;
const WRONLY = OFlag::O_WRONLY.bits() as u32;
const RDWR = OFlag::O_RDWR.bits() as u32;
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_exportbuffer;
nix::ioctl_readwrite!(vidioc_expbuf, b'V', 16, v4l2_exportbuffer);
}
#[derive(Debug, Error)]
pub enum ExpbufError {
#[error("ioctl error: {0}")]
IoctlError(#[from] Errno),
}
impl From<ExpbufError> for Errno {
fn from(err: ExpbufError) -> Self {
match err {
ExpbufError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_EXPBUF` ioctl.
pub fn expbuf<R: FromRawFd>(
fd: &impl AsRawFd,
queue: QueueType,
index: usize,
plane: usize,
flags: ExpbufFlags,
) -> Result<R, ExpbufError> {
let mut v4l2_expbuf = v4l2_exportbuffer {
type_: queue as u32,
index: index as u32,
plane: plane as u32,
flags: flags.bits(),
..Default::default()
};
unsafe { ioctl::vidioc_expbuf(fd.as_raw_fd(), &mut v4l2_expbuf) }?;
Ok(unsafe { R::from_raw_fd(v4l2_expbuf.fd) })
}

View File

@@ -0,0 +1,82 @@
use nix::errno::Errno;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_frmivalenum;
use crate::PixelFormat;
/// A wrapper for the 'v4l2_frmivalenum' union member types
#[derive(Debug)]
pub enum FrmIvalTypes<'a> {
Discrete(&'a bindings::v4l2_fract),
StepWise(&'a bindings::v4l2_frmival_stepwise),
}
impl v4l2_frmivalenum {
/// Safely access the intervals member of the struct based on the
/// returned type.
pub fn intervals(&self) -> Option<FrmIvalTypes<'_>> {
match self.type_ {
// SAFETY: the member of the union that gets used by the driver
// is determined by the type
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_DISCRETE => {
Some(FrmIvalTypes::Discrete(unsafe {
&self.__bindgen_anon_1.discrete
}))
}
// SAFETY: the member of the union that gets used by the driver
// is determined by the type
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_CONTINUOUS
| bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_STEPWISE => {
Some(FrmIvalTypes::StepWise(unsafe {
&self.__bindgen_anon_1.stepwise
}))
}
_ => None,
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_frmivalenum;
nix::ioctl_readwrite!(vidioc_enum_frameintervals, b'V', 75, v4l2_frmivalenum);
}
#[derive(Debug, Error)]
pub enum FrameIntervalsError {
#[error("Unexpected ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<FrameIntervalsError> for Errno {
fn from(err: FrameIntervalsError) -> Self {
match err {
FrameIntervalsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_ENUM_FRAMEINTERVALS` ioctl.
pub fn enum_frame_intervals<O: From<v4l2_frmivalenum>>(
fd: &impl AsRawFd,
index: u32,
pixel_format: PixelFormat,
width: u32,
height: u32,
) -> Result<O, FrameIntervalsError> {
let mut frame_interval = v4l2_frmivalenum {
index,
pixel_format: pixel_format.into(),
width,
height,
..Default::default()
};
match unsafe { ioctl::vidioc_enum_frameintervals(fd.as_raw_fd(), &mut frame_interval) } {
Ok(_) => Ok(O::from(frame_interval)),
Err(e) => Err(FrameIntervalsError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,79 @@
use nix::errno::Errno;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_frmsizeenum;
use crate::PixelFormat;
/// A wrapper for the 'v4l2_frmsizeenum' union member types
#[derive(Debug)]
pub enum FrmSizeTypes<'a> {
Discrete(&'a bindings::v4l2_frmsize_discrete),
StepWise(&'a bindings::v4l2_frmsize_stepwise),
}
impl v4l2_frmsizeenum {
/// Safely access the size member of the struct based on the
/// returned type.
pub fn size(&self) -> Option<FrmSizeTypes<'_>> {
match self.type_ {
// SAFETY: the member of the union that gets used by the driver
// is determined by the type
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE => {
Some(FrmSizeTypes::Discrete(unsafe {
&self.__bindgen_anon_1.discrete
}))
}
// SAFETY: the member of the union that gets used by the driver
// is determined by the type
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_CONTINUOUS
| bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_STEPWISE => {
Some(FrmSizeTypes::StepWise(unsafe {
&self.__bindgen_anon_1.stepwise
}))
}
_ => None,
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_frmsizeenum;
nix::ioctl_readwrite!(vidioc_enum_framesizes, b'V', 74, v4l2_frmsizeenum);
}
#[derive(Debug, Error)]
pub enum FrameSizeError {
#[error("Unexpected ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<FrameSizeError> for Errno {
fn from(err: FrameSizeError) -> Self {
match err {
FrameSizeError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_ENUM_FRAMESIZES` ioctl.
pub fn enum_frame_sizes<O: From<v4l2_frmsizeenum>>(
fd: &impl AsRawFd,
index: u32,
pixel_format: PixelFormat,
) -> Result<O, FrameSizeError> {
let mut frame_size = v4l2_frmsizeenum {
index,
pixel_format: pixel_format.into(),
..Default::default()
};
match unsafe { ioctl::vidioc_enum_framesizes(fd.as_raw_fd(), &mut frame_size) } {
Ok(_) => Ok(O::from(frame_size)),
Err(e) => Err(FrameSizeError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,189 @@
use std::os::unix::io::AsRawFd;
use enumn::N;
use nix::errno::Errno;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_dv_timings;
use crate::bindings::v4l2_dv_timings_cap;
use crate::bindings::v4l2_enum_dv_timings;
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_dv_timings;
use crate::bindings::v4l2_dv_timings_cap;
use crate::bindings::v4l2_enum_dv_timings;
nix::ioctl_readwrite!(vidioc_s_dv_timings, b'V', 87, v4l2_dv_timings);
nix::ioctl_readwrite!(vidioc_g_dv_timings, b'V', 88, v4l2_dv_timings);
nix::ioctl_readwrite!(vidioc_enum_dv_timings, b'V', 98, v4l2_enum_dv_timings);
nix::ioctl_read!(vidioc_query_dv_timings, b'V', 99, v4l2_dv_timings);
nix::ioctl_readwrite!(vidioc_dv_timings_cap, b'V', 100, v4l2_dv_timings_cap);
}
#[derive(Debug, N)]
#[repr(u32)]
pub enum DvTimingsType {
Bt6561120 = bindings::V4L2_DV_BT_656_1120,
}
#[derive(Debug, Error)]
pub enum GDvTimingsError {
#[error("ioctl not supported or invalid parameters")]
Invalid,
#[error("Digital video timings are not supported on this input or output")]
Unsupported,
#[error("Device is busy and cannot change timings")]
Busy,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<GDvTimingsError> for Errno {
fn from(err: GDvTimingsError) -> Self {
match err {
GDvTimingsError::Invalid => Errno::EINVAL,
GDvTimingsError::Unsupported => Errno::ENODATA,
GDvTimingsError::Busy => Errno::EBUSY,
GDvTimingsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_S_DV_TIMINGS` ioctl.
pub fn s_dv_timings<I: Into<v4l2_dv_timings>, O: From<v4l2_dv_timings>>(
fd: &impl AsRawFd,
timings: I,
) -> Result<O, GDvTimingsError> {
let mut timings: v4l2_dv_timings = timings.into();
match unsafe { ioctl::vidioc_s_dv_timings(fd.as_raw_fd(), &mut timings) } {
Ok(_) => Ok(O::from(timings)),
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
Err(e) => Err(GDvTimingsError::IoctlError(e)),
}
}
/// Safe wrapper around the `VIDIOC_G_DV_TIMINGS` ioctl.
pub fn g_dv_timings<O: From<v4l2_dv_timings>>(fd: &impl AsRawFd) -> Result<O, GDvTimingsError> {
let mut timings = v4l2_dv_timings {
..Default::default()
};
match unsafe { ioctl::vidioc_g_dv_timings(fd.as_raw_fd(), &mut timings) } {
Ok(_) => Ok(O::from(timings)),
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
Err(e) => Err(GDvTimingsError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum EnumDvTimingsError {
#[error("timing index is out of bounds")]
Invalid,
#[error("Digital video timings are not supported on this input or output")]
Unsupported,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<EnumDvTimingsError> for Errno {
fn from(err: EnumDvTimingsError) -> Self {
match err {
EnumDvTimingsError::Invalid => Errno::EINVAL,
EnumDvTimingsError::Unsupported => Errno::ENODATA,
EnumDvTimingsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_ENUM_DV_TIMINGS` ioctl.
pub fn enum_dv_timings<O: From<v4l2_dv_timings>>(
fd: &impl AsRawFd,
index: u32,
) -> Result<O, EnumDvTimingsError> {
let mut timings = v4l2_enum_dv_timings {
index,
..Default::default()
};
match unsafe { ioctl::vidioc_enum_dv_timings(fd.as_raw_fd(), &mut timings) } {
Ok(_) => Ok(O::from(timings.timings)),
Err(Errno::EINVAL) => Err(EnumDvTimingsError::Invalid),
Err(Errno::ENODATA) => Err(EnumDvTimingsError::Unsupported),
Err(e) => Err(EnumDvTimingsError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum QueryDvTimingsError {
#[error("Digital video timings are not supported on this input or output")]
Unsupported,
#[error("No timings could be detected because no signal was found")]
NoLink,
#[error("Unstable signal")]
UnstableSignal,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<QueryDvTimingsError> for Errno {
fn from(err: QueryDvTimingsError) -> Self {
match err {
QueryDvTimingsError::Unsupported => Errno::ENODATA,
QueryDvTimingsError::NoLink => Errno::ENOLINK,
QueryDvTimingsError::UnstableSignal => Errno::ENOLCK,
QueryDvTimingsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_QUERY_DV_TIMINGS` ioctl.
pub fn query_dv_timings<O: From<v4l2_dv_timings>>(
fd: &impl AsRawFd,
) -> Result<O, QueryDvTimingsError> {
let mut timings = v4l2_dv_timings {
..Default::default()
};
match unsafe { ioctl::vidioc_query_dv_timings(fd.as_raw_fd(), &mut timings) } {
Ok(_) => Ok(O::from(timings)),
Err(Errno::ENODATA) => Err(QueryDvTimingsError::Unsupported),
Err(Errno::ENOLINK) => Err(QueryDvTimingsError::NoLink),
Err(Errno::ENOLCK) => Err(QueryDvTimingsError::UnstableSignal),
Err(e) => Err(QueryDvTimingsError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum DvTimingsCapError {
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<DvTimingsCapError> for Errno {
fn from(err: DvTimingsCapError) -> Self {
match err {
DvTimingsCapError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_DV_TIMINGS_CAP` ioctl.
pub fn dv_timings_cap<O: From<v4l2_dv_timings_cap>>(
fd: &impl AsRawFd,
) -> Result<O, DvTimingsCapError> {
let mut caps = v4l2_dv_timings_cap {
..Default::default()
};
match unsafe { ioctl::vidioc_dv_timings_cap(fd.as_raw_fd(), &mut caps) } {
Ok(_) => Ok(O::from(caps)),
Err(e) => Err(DvTimingsCapError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,300 @@
//! Safe wrapper for the `VIDIOC_(G|S|TRY)_FMT` ioctls.
use nix::errno::Errno;
use std::convert::{From, Into, TryFrom, TryInto};
use std::default::Default;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_format;
use crate::Format;
use crate::FormatConversionError;
use crate::PlaneLayout;
use crate::QueueType;
impl TryFrom<(QueueType, &Format)> for v4l2_format {
type Error = FormatConversionError;
fn try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error> {
Ok(v4l2_format {
type_: queue as u32,
fmt: match queue {
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => {
bindings::v4l2_format__bindgen_ty_1 {
pix_mp: {
if format.plane_fmt.len() > bindings::VIDEO_MAX_PLANES as usize {
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
}
let mut pix_mp = bindings::v4l2_pix_format_mplane {
width: format.width,
height: format.height,
pixelformat: format.pixelformat.into(),
num_planes: format.plane_fmt.len() as u8,
plane_fmt: Default::default(),
..Default::default()
};
for (plane, v4l2_plane) in
format.plane_fmt.iter().zip(pix_mp.plane_fmt.iter_mut())
{
*v4l2_plane = plane.into();
}
pix_mp
},
}
}
_ => bindings::v4l2_format__bindgen_ty_1 {
pix: {
if format.plane_fmt.len() > 1 {
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
}
let (bytesperline, sizeimage) = if !format.plane_fmt.is_empty() {
(
format.plane_fmt[0].bytesperline,
format.plane_fmt[0].sizeimage,
)
} else {
Default::default()
};
bindings::v4l2_pix_format {
width: format.width,
height: format.height,
pixelformat: format.pixelformat.into(),
bytesperline,
sizeimage,
..Default::default()
}
},
},
},
})
}
}
impl From<&PlaneLayout> for bindings::v4l2_plane_pix_format {
fn from(plane: &PlaneLayout) -> Self {
bindings::v4l2_plane_pix_format {
sizeimage: plane.sizeimage,
bytesperline: plane.bytesperline,
..Default::default()
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_format;
nix::ioctl_readwrite!(vidioc_g_fmt, b'V', 4, v4l2_format);
nix::ioctl_readwrite!(vidioc_s_fmt, b'V', 5, v4l2_format);
nix::ioctl_readwrite!(vidioc_try_fmt, b'V', 64, v4l2_format);
}
#[derive(Debug, Error)]
pub enum GFmtError {
#[error("error while converting from V4L2 format")]
FromV4L2FormatConversionError,
#[error("invalid buffer type requested")]
InvalidBufferType,
#[error("unexpected ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<GFmtError> for Errno {
fn from(err: GFmtError) -> Self {
match err {
GFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
GFmtError::InvalidBufferType => Errno::EINVAL,
GFmtError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_G_FMT` ioctl.
pub fn g_fmt<O: TryFrom<v4l2_format>>(fd: &impl AsRawFd, queue: QueueType) -> Result<O, GFmtError> {
let mut fmt = v4l2_format {
type_: queue as u32,
..Default::default()
};
match unsafe { ioctl::vidioc_g_fmt(fd.as_raw_fd(), &mut fmt) } {
Ok(_) => Ok(fmt
.try_into()
.map_err(|_| GFmtError::FromV4L2FormatConversionError)?),
Err(Errno::EINVAL) => Err(GFmtError::InvalidBufferType),
Err(e) => Err(GFmtError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum SFmtError {
#[error("error while converting from V4L2 format")]
FromV4L2FormatConversionError,
#[error("error while converting to V4L2 format")]
ToV4L2FormatConversionError,
#[error("invalid buffer type requested")]
InvalidBufferType,
#[error("device currently busy")]
DeviceBusy,
#[error("ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<SFmtError> for Errno {
fn from(err: SFmtError) -> Self {
match err {
SFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
SFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
SFmtError::InvalidBufferType => Errno::EINVAL,
SFmtError::DeviceBusy => Errno::EBUSY,
SFmtError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_S_FMT` ioctl.
pub fn s_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
fd: &mut impl AsRawFd,
format: I,
) -> Result<O, SFmtError> {
let mut fmt: v4l2_format = format
.try_into()
.map_err(|_| SFmtError::ToV4L2FormatConversionError)?;
match unsafe { ioctl::vidioc_s_fmt(fd.as_raw_fd(), &mut fmt) } {
Ok(_) => Ok(fmt
.try_into()
.map_err(|_| SFmtError::FromV4L2FormatConversionError)?),
Err(Errno::EINVAL) => Err(SFmtError::InvalidBufferType),
Err(Errno::EBUSY) => Err(SFmtError::DeviceBusy),
Err(e) => Err(SFmtError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum TryFmtError {
#[error("error while converting from V4L2 format")]
FromV4L2FormatConversionError,
#[error("error while converting to V4L2 format")]
ToV4L2FormatConversionError,
#[error("invalid buffer type requested")]
InvalidBufferType,
#[error("ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<TryFmtError> for Errno {
fn from(err: TryFmtError) -> Self {
match err {
TryFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
TryFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
TryFmtError::InvalidBufferType => Errno::EINVAL,
TryFmtError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_TRY_FMT` ioctl.
pub fn try_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
fd: &impl AsRawFd,
format: I,
) -> Result<O, TryFmtError> {
let mut fmt: v4l2_format = format
.try_into()
.map_err(|_| TryFmtError::ToV4L2FormatConversionError)?;
match unsafe { ioctl::vidioc_try_fmt(fd.as_raw_fd(), &mut fmt) } {
Ok(_) => Ok(fmt
.try_into()
.map_err(|_| TryFmtError::FromV4L2FormatConversionError)?),
Err(Errno::EINVAL) => Err(TryFmtError::InvalidBufferType),
Err(e) => Err(TryFmtError::IoctlError(e)),
}
}
#[cfg(test)]
mod test {
use super::*;
use std::convert::TryInto;
#[test]
// Convert from Format to multi-planar v4l2_format and back.
fn mplane_to_v4l2_format() {
// This is not a real format but let us use unique values per field.
let mplane = Format {
width: 632,
height: 480,
pixelformat: b"NM12".into(),
plane_fmt: vec![
PlaneLayout {
sizeimage: 307200,
bytesperline: 640,
},
PlaneLayout {
sizeimage: 153600,
bytesperline: 320,
},
PlaneLayout {
sizeimage: 76800,
bytesperline: 160,
},
],
};
let v4l2_format = v4l2_format {
..(QueueType::VideoCaptureMplane, &mplane).try_into().unwrap()
};
let mplane2: Format = v4l2_format.try_into().unwrap();
assert_eq!(mplane, mplane2);
}
#[test]
// Convert from Format to single-planar v4l2_format and back.
fn splane_to_v4l2_format() {
// This is not a real format but let us use unique values per field.
let splane = Format {
width: 632,
height: 480,
pixelformat: b"NV12".into(),
plane_fmt: vec![PlaneLayout {
sizeimage: 307200,
bytesperline: 640,
}],
};
// Conversion to/from single-planar format.
let v4l2_format = v4l2_format {
..(QueueType::VideoCapture, &splane).try_into().unwrap()
};
let splane2: Format = v4l2_format.try_into().unwrap();
assert_eq!(splane, splane2);
// Trying to use a multi-planar format with the single-planar API should
// fail.
let mplane = Format {
width: 632,
height: 480,
pixelformat: b"NM12".into(),
// This is not a real format but let us use unique values per field.
plane_fmt: vec![
PlaneLayout {
sizeimage: 307200,
bytesperline: 640,
},
PlaneLayout {
sizeimage: 153600,
bytesperline: 320,
},
PlaneLayout {
sizeimage: 76800,
bytesperline: 160,
},
],
};
assert_eq!(
TryInto::<v4l2_format>::try_into((QueueType::VideoCapture, &mplane)).err(),
Some(FormatConversionError::TooManyPlanes(3))
);
}
}

View File

@@ -0,0 +1,149 @@
use std::os::unix::io::AsRawFd;
use nix::errno::Errno;
use thiserror::Error;
use crate::bindings::v4l2_standard;
use crate::bindings::v4l2_std_id;
use crate::bindings::v4l2_streamparm;
use crate::QueueType;
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_standard;
use crate::bindings::v4l2_std_id;
use crate::bindings::v4l2_streamparm;
nix::ioctl_readwrite!(vidioc_g_parm, b'V', 21, v4l2_streamparm);
nix::ioctl_readwrite!(vidioc_s_parm, b'V', 22, v4l2_streamparm);
nix::ioctl_read!(vidioc_g_std, b'V', 23, v4l2_std_id);
nix::ioctl_write_ptr!(vidioc_s_std, b'V', 24, v4l2_std_id);
nix::ioctl_readwrite!(vidioc_enumstd, b'V', 25, v4l2_standard);
nix::ioctl_read!(vidioc_querystd, b'V', 63, v4l2_std_id);
}
#[derive(Debug, Error)]
pub enum GParmError {
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<GParmError> for Errno {
fn from(err: GParmError) -> Self {
match err {
GParmError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_G_PARM` ioctl.
pub fn g_parm<O: From<v4l2_streamparm>>(
fd: &impl AsRawFd,
queue: QueueType,
) -> Result<O, GParmError> {
let mut parm = v4l2_streamparm {
type_: queue as u32,
..Default::default()
};
match unsafe { ioctl::vidioc_g_parm(fd.as_raw_fd(), &mut parm) } {
Ok(_) => Ok(O::from(parm)),
Err(e) => Err(GParmError::IoctlError(e)),
}
}
/// Safe wrapper around the `VIDIOC_S_PARM` ioctl.
pub fn s_parm<I: Into<v4l2_streamparm>, O: From<v4l2_streamparm>>(
fd: &impl AsRawFd,
parm: I,
) -> Result<O, GParmError> {
let mut parm = parm.into();
match unsafe { ioctl::vidioc_s_parm(fd.as_raw_fd(), &mut parm) } {
Ok(_) => Ok(O::from(parm)),
Err(e) => Err(GParmError::IoctlError(e)),
}
}
/// Safe wrapper around the `VIDIOC_G_STD` ioctl.
pub fn g_std<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
let mut std_id: v4l2_std_id = 0;
match unsafe { ioctl::vidioc_g_std(fd.as_raw_fd(), &mut std_id) } {
Ok(_) => Ok(O::from(std_id)),
Err(e) => Err(GParmError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum SStdError {
#[error("unsupported standard requested")]
Unsupported,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<SStdError> for Errno {
fn from(err: SStdError) -> Self {
match err {
SStdError::Unsupported => Errno::EINVAL,
SStdError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_S_STD` ioctl.
pub fn s_std<I: Into<v4l2_std_id>>(fd: &impl AsRawFd, std_id: I) -> Result<(), SStdError> {
let std_id = std_id.into();
match unsafe { ioctl::vidioc_s_std(fd.as_raw_fd(), &std_id) } {
Ok(_) => Ok(()),
Err(Errno::EINVAL) => Err(SStdError::Unsupported),
Err(e) => Err(SStdError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum EnumStdError {
#[error("requested index is out of bounds")]
OutOfBounds,
#[error("standard video timings are not supported for this input or output")]
Unsupported,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<EnumStdError> for Errno {
fn from(err: EnumStdError) -> Self {
match err {
EnumStdError::OutOfBounds => Errno::EINVAL,
EnumStdError::Unsupported => Errno::ENODATA,
EnumStdError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_ENUMSTD` ioctl.
pub fn enumstd<O: From<v4l2_standard>>(fd: &impl AsRawFd, index: u32) -> Result<O, EnumStdError> {
let mut standard = v4l2_standard {
index,
..Default::default()
};
match unsafe { ioctl::vidioc_enumstd(fd.as_raw_fd(), &mut standard) } {
Ok(_) => Ok(O::from(standard)),
Err(Errno::EINVAL) => Err(EnumStdError::OutOfBounds),
Err(Errno::ENODATA) => Err(EnumStdError::Unsupported),
Err(e) => Err(EnumStdError::IoctlError(e)),
}
}
/// Safe wrapper around the `VIDIOC_QUERYSTD` ioctl.
pub fn querystd<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
let mut std_id: v4l2_std_id = 0;
match unsafe { ioctl::vidioc_querystd(fd.as_raw_fd(), &mut std_id) } {
Ok(_) => Ok(O::from(std_id)),
Err(e) => Err(GParmError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,130 @@
use std::os::unix::io::AsRawFd;
use bitflags::bitflags;
use enumn::N;
use nix::errno::Errno;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_rect;
use crate::bindings::v4l2_selection;
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SelectionType {
Capture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
Output = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
}
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SelectionTarget {
Crop = bindings::V4L2_SEL_TGT_CROP,
CropDefault = bindings::V4L2_SEL_TGT_CROP_DEFAULT,
CropBounds = bindings::V4L2_SEL_TGT_CROP_BOUNDS,
NativeSize = bindings::V4L2_SEL_TGT_NATIVE_SIZE,
Compose = bindings::V4L2_SEL_TGT_COMPOSE,
ComposeDefault = bindings::V4L2_SEL_TGT_COMPOSE_DEFAULT,
ComposeBounds = bindings::V4L2_SEL_TGT_COMPOSE_BOUNDS,
ComposePadded = bindings::V4L2_SEL_TGT_COMPOSE_PADDED,
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct SelectionFlags: u32 {
const GE = bindings::V4L2_SEL_FLAG_GE;
const LE = bindings::V4L2_SEL_FLAG_LE;
const KEEP_CONFIG = bindings::V4L2_SEL_FLAG_KEEP_CONFIG;
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_selection;
nix::ioctl_readwrite!(vidioc_g_selection, b'V', 94, v4l2_selection);
nix::ioctl_readwrite!(vidioc_s_selection, b'V', 95, v4l2_selection);
}
#[derive(Debug, Error)]
pub enum GSelectionError {
#[error("invalid type or target requested")]
Invalid,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<GSelectionError> for Errno {
fn from(err: GSelectionError) -> Self {
match err {
GSelectionError::Invalid => Errno::EINVAL,
GSelectionError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_G_SELECTION` ioctl.
pub fn g_selection<R: From<v4l2_rect>>(
fd: &impl AsRawFd,
selection: SelectionType,
target: SelectionTarget,
) -> Result<R, GSelectionError> {
let mut sel = v4l2_selection {
type_: selection as u32,
target: target as u32,
..Default::default()
};
match unsafe { ioctl::vidioc_g_selection(fd.as_raw_fd(), &mut sel) } {
Ok(_) => Ok(R::from(sel.r)),
Err(Errno::EINVAL) => Err(GSelectionError::Invalid),
Err(e) => Err(GSelectionError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum SSelectionError {
#[error("invalid type or target requested")]
Invalid,
#[error("invalid range requested")]
InvalidRange,
#[error("cannot change selection rectangle currently")]
Busy,
#[error("ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<SSelectionError> for Errno {
fn from(err: SSelectionError) -> Self {
match err {
SSelectionError::Invalid => Errno::EINVAL,
SSelectionError::InvalidRange => Errno::ERANGE,
SSelectionError::Busy => Errno::EBUSY,
SSelectionError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_S_SELECTION` ioctl.
pub fn s_selection<RI: Into<v4l2_rect>, RO: From<v4l2_rect>>(
fd: &impl AsRawFd,
selection: SelectionType,
target: SelectionTarget,
rect: RI,
flags: SelectionFlags,
) -> Result<RO, SSelectionError> {
let mut sel = v4l2_selection {
type_: selection as u32,
target: target as u32,
flags: flags.bits(),
r: rect.into(),
..Default::default()
};
match unsafe { ioctl::vidioc_s_selection(fd.as_raw_fd(), &mut sel) } {
Ok(_) => Ok(RO::from(sel.r)),
Err(Errno::EINVAL) => Err(SSelectionError::Invalid),
Err(Errno::ERANGE) => Err(SSelectionError::InvalidRange),
Err(Errno::EBUSY) => Err(SSelectionError::Busy),
Err(e) => Err(SSelectionError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,117 @@
use core::num::NonZeroUsize;
use std::{
cmp::{max, min},
ops::Deref,
ptr::NonNull,
slice,
};
use std::{ops::DerefMut, os::unix::io::AsFd};
use log::error;
use nix::{errno::Errno, libc::off_t, sys::mman};
use thiserror::Error;
pub struct PlaneMapping {
// A mapping remains valid until we munmap it, that is, until the
// PlaneMapping object is deleted. Hence the static lifetime.
pub data: &'static mut [u8],
start: usize,
end: usize,
}
impl PlaneMapping {
pub fn size(&self) -> usize {
self.end - self.start
}
pub fn restrict(mut self, start: usize, end: usize) -> Self {
self.start = max(self.start, start);
self.end = min(self.end, end);
self
}
}
impl AsRef<[u8]> for PlaneMapping {
fn as_ref(&self) -> &[u8] {
&self.data[self.start..self.end]
}
}
impl AsMut<[u8]> for PlaneMapping {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.data[self.start..self.end]
}
}
impl Deref for PlaneMapping {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.data[self.start..self.end]
}
}
impl DerefMut for PlaneMapping {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data[self.start..self.end]
}
}
impl Drop for PlaneMapping {
fn drop(&mut self) {
// Safe because the pointer and length were constructed in mmap() and
// are always valid.
unsafe {
mman::munmap(
NonNull::new_unchecked(self.data.as_mut_ptr().cast()),
self.data.len(),
)
}
.unwrap_or_else(|e| {
error!("Error while unmapping plane: {}", e);
});
}
}
#[derive(Debug, Error)]
pub enum MmapError {
#[error("provided length was 0")]
ZeroLength,
#[error("ioctl error: {0}")]
IoctlError(#[from] Errno),
}
impl From<MmapError> for Errno {
fn from(err: MmapError) -> Self {
match err {
MmapError::ZeroLength => Errno::EINVAL,
MmapError::IoctlError(e) => e,
}
}
}
// TODO should be unsafe because the mapping can be used after a buffer is queued?
// Or not, since this cannot cause a crash...
pub fn mmap(fd: &impl AsFd, mem_offset: u32, length: u32) -> Result<PlaneMapping, MmapError> {
let non_zero_length = NonZeroUsize::new(length as usize).ok_or(MmapError::ZeroLength)?;
let data = unsafe {
mman::mmap(
None,
non_zero_length,
mman::ProtFlags::PROT_READ | mman::ProtFlags::PROT_WRITE,
mman::MapFlags::MAP_SHARED,
fd,
mem_offset as off_t,
)
}?;
Ok(PlaneMapping {
// Safe because we know the pointer is valid and has enough data mapped
// to cover the length.
data: unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), length as usize) },
start: 0,
end: length as usize,
})
}

View File

@@ -0,0 +1,197 @@
//! Safe wrapper for the VIDIOC_(D)QBUF and VIDIOC_QUERYBUF ioctls.
use nix::errno::Errno;
use nix::libc::{suseconds_t, time_t};
use nix::sys::time::{TimeVal, TimeValLike};
use std::convert::TryFrom;
use std::fmt::Debug;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use crate::bindings;
use crate::ioctl::ioctl_and_convert;
use crate::ioctl::BufferFlags;
use crate::ioctl::IoctlConvertError;
use crate::ioctl::IoctlConvertResult;
use crate::ioctl::UncheckedV4l2Buffer;
use crate::memory::Memory;
use crate::memory::PlaneHandle;
use crate::QueueType;
#[derive(Debug, Error)]
pub enum QBufIoctlError {
#[error("invalid number of planes specified for the buffer: got {0}, expected {1}")]
NumPlanesMismatch(usize, usize),
#[error("data offset specified while using the single-planar API")]
DataOffsetNotSupported,
#[error("unexpected ioctl error: {0}")]
Other(Errno),
}
impl From<Errno> for QBufIoctlError {
fn from(errno: Errno) -> Self {
Self::Other(errno)
}
}
impl From<QBufIoctlError> for Errno {
fn from(err: QBufIoctlError) -> Self {
match err {
QBufIoctlError::NumPlanesMismatch(_, _) => Errno::EINVAL,
QBufIoctlError::DataOffsetNotSupported => Errno::EINVAL,
QBufIoctlError::Other(e) => e,
}
}
}
/// Representation of a single plane of a V4L2 buffer.
pub struct QBufPlane(pub bindings::v4l2_plane);
impl QBufPlane {
// TODO remove as this is not safe - we should always specify a handle.
pub fn new(bytes_used: usize) -> Self {
QBufPlane(bindings::v4l2_plane {
bytesused: bytes_used as u32,
data_offset: 0,
..Default::default()
})
}
pub fn new_from_handle<H: PlaneHandle>(handle: &H, bytes_used: usize) -> Self {
let mut plane = Self::new(bytes_used);
handle.fill_v4l2_plane(&mut plane.0);
plane
}
}
impl Debug for QBufPlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QBufPlane")
.field("bytesused", &self.0.bytesused)
.field("data_offset", &self.0.data_offset)
.finish()
}
}
/// Contains all the information that can be passed to the `qbuf` ioctl.
// TODO Change this to contain a v4l2_buffer, and create constructors/methods
// to change it? Then during qbuf we just need to set m.planes to planes
// (after resizing it to 8) and we are good to use it as-is.
// We could even turn the trait into AsRef<v4l2_buffer> for good measure.
#[derive(Debug)]
pub struct QBuffer<H: PlaneHandle> {
index: u32,
queue: QueueType,
pub flags: BufferFlags,
pub field: u32,
pub sequence: u32,
pub timestamp: TimeVal,
pub planes: Vec<QBufPlane>,
pub _h: std::marker::PhantomData<H>,
}
impl<H: PlaneHandle> QBuffer<H> {
pub fn new(queue: QueueType, index: u32) -> Self {
QBuffer {
index,
queue,
flags: Default::default(),
field: Default::default(),
sequence: Default::default(),
timestamp: TimeVal::zero(),
planes: Vec::new(),
_h: std::marker::PhantomData,
}
}
}
impl<H: PlaneHandle> QBuffer<H> {
pub fn set_timestamp(mut self, sec: time_t, usec: suseconds_t) -> Self {
self.timestamp = TimeVal::new(sec, usec);
self
}
}
impl<H: PlaneHandle> From<QBuffer<H>> for UncheckedV4l2Buffer {
fn from(qbuf: QBuffer<H>) -> Self {
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(qbuf.queue, Some(qbuf.index));
v4l2_buf.0.index = qbuf.index;
v4l2_buf.0.type_ = qbuf.queue as u32;
v4l2_buf.0.memory = H::Memory::MEMORY_TYPE as u32;
v4l2_buf.0.flags = qbuf.flags.bits();
v4l2_buf.0.field = qbuf.field;
v4l2_buf.0.sequence = qbuf.sequence;
v4l2_buf.0.timestamp.tv_sec = qbuf.timestamp.tv_sec();
v4l2_buf.0.timestamp.tv_usec = qbuf.timestamp.tv_usec();
if let Some(planes) = &mut v4l2_buf.1 {
for (dst_plane, src_plane) in planes.iter_mut().zip(qbuf.planes.into_iter()) {
*dst_plane = src_plane.0;
}
} else {
let plane = &qbuf.planes[0];
v4l2_buf.0.length = plane.0.length;
v4l2_buf.0.bytesused = plane.0.bytesused;
v4l2_buf.0.m = (&plane.0.m, H::Memory::MEMORY_TYPE).into();
}
v4l2_buf
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_buffer;
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_qbuf, b'V', 15, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_prepare_buf, b'V', 93, v4l2_buffer);
}
pub type QBufError<CE> = IoctlConvertError<QBufIoctlError, CE>;
pub type QBufResult<O, CE> = IoctlConvertResult<O, QBufIoctlError, CE>;
/// Safe wrapper around the `VIDIOC_QBUF` ioctl.
///
/// TODO: `qbuf` should be unsafe! The following invariants need to be guaranteed
/// by the caller:
///
/// For MMAP buffers, any mapping must not be accessed by the caller (or any
/// mapping must be unmapped before queueing?). Also if the buffer has been
/// DMABUF-exported, its consumers must likewise not access it.
///
/// For DMABUF buffers, the FD must not be duplicated and accessed anywhere else.
///
/// For USERPTR buffers, things are most tricky. Not only must the data not be
/// 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<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
where
I: Into<UncheckedV4l2Buffer>,
O: TryFrom<UncheckedV4l2Buffer>,
O::Error: std::fmt::Debug,
{
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
ioctl_and_convert(
unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
.map(|_| v4l2_buf)
.map_err(Into::into),
)
}
/// Safe wrapper around the `VIDIOC_PREPARE_BUF` ioctl.
pub fn prepare_buf<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
where
I: Into<UncheckedV4l2Buffer>,
O: TryFrom<UncheckedV4l2Buffer>,
O::Error: std::fmt::Debug,
{
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
ioctl_and_convert(
unsafe { ioctl::vidioc_prepare_buf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
.map(|_| v4l2_buf)
.map_err(Into::into),
)
}

View File

@@ -0,0 +1,113 @@
use std::convert::Infallible;
use std::convert::TryFrom;
use std::os::unix::io::AsRawFd;
use nix::errno::Errno;
use thiserror::Error;
use crate::ioctl::ioctl_and_convert;
use crate::ioctl::BufferFlags;
use crate::ioctl::IoctlConvertError;
use crate::ioctl::IoctlConvertResult;
use crate::ioctl::UncheckedV4l2Buffer;
use crate::QueueType;
#[derive(Debug)]
pub struct QueryBufPlane {
/// Offset to pass to `mmap()` in order to obtain a mapping for this plane.
pub mem_offset: u32,
/// Length of this plane.
pub length: u32,
}
/// Contains information about a buffer's layout, as obtained from [`crate::ioctl::querybuf`].
///
/// It is a subset of [`crate::ioctl::V4l2Buffer`], only more convenient on occasion because its
/// conversion from an unchecked v4l2_buffer cannot fail.
///
/// Single-planar buffers have one entry in [`planes`] representing the layout of their unique
/// plane.
#[derive(Debug)]
pub struct QueryBuffer {
pub index: usize,
pub flags: BufferFlags,
pub planes: Vec<QueryBufPlane>,
}
impl TryFrom<UncheckedV4l2Buffer> for QueryBuffer {
type Error = Infallible;
fn try_from(buffer: UncheckedV4l2Buffer) -> Result<Self, Self::Error> {
let v4l2_buf = buffer.0;
let planes = match buffer.1 {
None => vec![QueryBufPlane {
mem_offset: unsafe { v4l2_buf.m.offset },
length: v4l2_buf.length,
}],
Some(v4l2_planes) => v4l2_planes
.iter()
.take(v4l2_buf.length as usize)
.map(|v4l2_plane| QueryBufPlane {
mem_offset: unsafe { v4l2_plane.m.mem_offset },
length: v4l2_plane.length,
})
.collect(),
};
Ok(QueryBuffer {
index: v4l2_buf.index as usize,
flags: BufferFlags::from_bits_truncate(v4l2_buf.flags),
planes,
})
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_buffer;
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
}
#[derive(Debug, Error)]
pub enum QueryBufIoctlError {
#[error("unsupported queue or out-of-bounds index")]
InvalidInput,
#[error("unexpected ioctl error: {0}")]
Other(Errno),
}
impl From<Errno> for QueryBufIoctlError {
fn from(err: Errno) -> Self {
match err {
Errno::EINVAL => QueryBufIoctlError::InvalidInput,
e => QueryBufIoctlError::Other(e),
}
}
}
impl From<QueryBufIoctlError> for Errno {
fn from(err: QueryBufIoctlError) -> Self {
match err {
QueryBufIoctlError::InvalidInput => Errno::EINVAL,
QueryBufIoctlError::Other(e) => e,
}
}
}
pub type QueryBufError<CE> = IoctlConvertError<QueryBufIoctlError, CE>;
pub type QueryBufResult<O, CE> = IoctlConvertResult<O, QueryBufIoctlError, CE>;
/// Safe wrapper around the `VIDIOC_QUERYBUF` ioctl.
pub fn querybuf<O>(fd: &impl AsRawFd, queue: QueueType, index: usize) -> QueryBufResult<O, O::Error>
where
O: TryFrom<UncheckedV4l2Buffer>,
O::Error: std::fmt::Debug,
{
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, Some(index as u32));
ioctl_and_convert(
unsafe { ioctl::vidioc_querybuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
.map(|_| v4l2_buf)
.map_err(Into::into),
)
}

View File

@@ -0,0 +1,136 @@
//! Safe wrapper for the `VIDIOC_QUERYCAP` ioctl.
use super::string_from_cstr;
use crate::bindings;
use crate::bindings::v4l2_capability;
use bitflags::bitflags;
use nix::errno::Errno;
use std::fmt;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
bitflags! {
/// Flags returned by the `VIDIOC_QUERYCAP` ioctl into the `capabilities`
/// or `device_capabilities` field of `v4l2_capability`.
#[derive(Clone, Copy, Debug)]
pub struct Capabilities: u32 {
const VIDEO_CAPTURE = bindings::V4L2_CAP_VIDEO_CAPTURE;
const VIDEO_OUTPUT = bindings::V4L2_CAP_VIDEO_OUTPUT;
const VIDEO_OVERLAY = bindings::V4L2_CAP_VIDEO_OVERLAY;
const VBI_CAPTURE = bindings::V4L2_CAP_VBI_CAPTURE;
const VBI_OUTPUT = bindings::V4L2_CAP_VBI_OUTPUT;
const SLICED_VBI_CAPTURE = bindings::V4L2_CAP_SLICED_VBI_CAPTURE;
const SLICED_VBI_OUTPUT = bindings::V4L2_CAP_SLICED_VBI_OUTPUT;
const RDS_CAPTURE = bindings::V4L2_CAP_RDS_CAPTURE;
const VIDEO_OUTPUT_OVERLAY = bindings::V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
const HW_FREQ_SEEK = bindings::V4L2_CAP_HW_FREQ_SEEK;
const RDS_OUTPUT = bindings::V4L2_CAP_RDS_OUTPUT;
const VIDEO_CAPTURE_MPLANE = bindings::V4L2_CAP_VIDEO_CAPTURE_MPLANE;
const VIDEO_OUTPUT_MPLANE = bindings::V4L2_CAP_VIDEO_OUTPUT_MPLANE;
const VIDEO_M2M_MPLANE = bindings::V4L2_CAP_VIDEO_M2M_MPLANE;
const VIDEO_M2M = bindings::V4L2_CAP_VIDEO_M2M;
const TUNER = bindings::V4L2_CAP_TUNER;
const AUDIO = bindings::V4L2_CAP_AUDIO;
const RADIO = bindings::V4L2_CAP_RADIO;
const MODULATOR = bindings::V4L2_CAP_MODULATOR;
const SDR_CAPTURE = bindings::V4L2_CAP_SDR_CAPTURE;
const EXT_PIX_FORMAT = bindings::V4L2_CAP_EXT_PIX_FORMAT;
const SDR_OUTPUT = bindings::V4L2_CAP_SDR_OUTPUT;
const META_CAPTURE = bindings::V4L2_CAP_META_CAPTURE;
const READWRITE = bindings::V4L2_CAP_READWRITE;
const ASYNCIO = bindings::V4L2_CAP_ASYNCIO;
const STREAMING = bindings::V4L2_CAP_STREAMING;
const META_OUTPUT = bindings::V4L2_CAP_META_OUTPUT;
const TOUCH = bindings::V4L2_CAP_TOUCH;
const DEVICE_CAPS = bindings::V4L2_CAP_DEVICE_CAPS;
}
}
impl fmt::Display for Capabilities {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
/// Used to get the capability flags from a `VIDIOC_QUERYCAP` ioctl.
impl From<v4l2_capability> for Capabilities {
fn from(qcap: v4l2_capability) -> Self {
Capabilities::from_bits_truncate(qcap.capabilities)
}
}
/// Safe variant of the `v4l2_capability` struct, to be used with `querycap`.
#[derive(Debug)]
pub struct Capability {
pub driver: String,
pub card: String,
pub bus_info: String,
pub version: u32,
pub capabilities: Capabilities,
pub device_caps: Option<Capabilities>,
}
impl Capability {
/// Returns the set of capabilities of the hardware as a whole.
pub fn capabilities(&self) -> Capabilities {
self.capabilities
}
/// Returns the capabilities that apply to the currently opened V4L2 node.
pub fn device_caps(&self) -> Capabilities {
self.device_caps
.unwrap_or_else(|| self.capabilities.difference(Capabilities::DEVICE_CAPS))
}
}
impl From<v4l2_capability> for Capability {
fn from(qcap: v4l2_capability) -> Self {
Capability {
driver: string_from_cstr(&qcap.driver).unwrap_or_else(|_| "".into()),
card: string_from_cstr(&qcap.card).unwrap_or_else(|_| "".into()),
bus_info: string_from_cstr(&qcap.bus_info).unwrap_or_else(|_| "".into()),
version: qcap.version,
capabilities: Capabilities::from_bits_truncate(qcap.capabilities),
device_caps: if qcap.capabilities & bindings::V4L2_CAP_DEVICE_CAPS != 0 {
Some(Capabilities::from_bits_truncate(qcap.device_caps))
} else {
None
},
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_capability;
nix::ioctl_read!(vidioc_querycap, b'V', 0, v4l2_capability);
}
#[derive(Debug, Error)]
pub enum QueryCapError {
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<QueryCapError> for Errno {
fn from(err: QueryCapError) -> Self {
match err {
QueryCapError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_QUERYCAP` ioctl.
pub fn querycap<T: From<v4l2_capability>>(fd: &impl AsRawFd) -> Result<T, QueryCapError> {
let mut qcap: v4l2_capability = Default::default();
match unsafe { ioctl::vidioc_querycap(fd.as_raw_fd(), &mut qcap) } {
Ok(_) => Ok(T::from(qcap)),
Err(e) => Err(QueryCapError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,162 @@
//! Safe wrapper for the `VIDIOC_REQBUFS` ioctl.
use crate::bindings;
use crate::bindings::v4l2_create_buffers;
use crate::bindings::v4l2_format;
use crate::bindings::v4l2_requestbuffers;
use crate::memory::MemoryType;
use crate::QueueType;
use bitflags::bitflags;
use nix::{self, errno::Errno};
use std::os::unix::io::AsRawFd;
use thiserror::Error;
bitflags! {
/// Flags returned by the `VIDIOC_REQBUFS` ioctl into the `capabilities`
/// field of `struct v4l2_requestbuffers`.
#[derive(Clone, Copy, Debug)]
pub struct BufferCapabilities: u32 {
const SUPPORTS_MMAP = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP;
const SUPPORTS_USERPTR = bindings::V4L2_BUF_CAP_SUPPORTS_USERPTR;
const SUPPORTS_DMABUF = bindings::V4L2_BUF_CAP_SUPPORTS_DMABUF;
const SUPPORTS_REQUESTS = bindings::V4L2_BUF_CAP_SUPPORTS_REQUESTS;
const SUPPORTS_ORPHANED_BUFS = bindings::V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS;
const SUPPORTS_M2M_HOLD_CAPTURE_BUF = bindings::V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
const SUPPORTS_MMAP_CACHE_HINTS = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS;
}
}
bitflags! {
/// Memory Consistency Flags passed to the `VIDIOC_REQBUFS` ioctl in the `flags`
/// field of `struct v4l2_requestbuffers`.
#[derive(Clone, Copy, Debug)]
pub struct MemoryConsistency: u8 {
const MEMORY_FLAG_NON_COHERENT = bindings::V4L2_MEMORY_FLAG_NON_COHERENT as u8;
}
}
impl From<v4l2_requestbuffers> for () {
fn from(_reqbufs: v4l2_requestbuffers) -> Self {}
}
/// In case we are just interested in the number of buffers that `reqbufs`
/// created.
impl From<v4l2_requestbuffers> for usize {
fn from(reqbufs: v4l2_requestbuffers) -> Self {
reqbufs.count as usize
}
}
/// If we just want to query the buffer capabilities.
impl From<v4l2_requestbuffers> for BufferCapabilities {
fn from(reqbufs: v4l2_requestbuffers) -> Self {
BufferCapabilities::from_bits_truncate(reqbufs.capabilities)
}
}
/// Full result of the `reqbufs` ioctl.
pub struct RequestBuffers {
pub count: u32,
pub capabilities: BufferCapabilities,
pub flags: MemoryConsistency,
}
impl From<v4l2_requestbuffers> for RequestBuffers {
fn from(reqbufs: v4l2_requestbuffers) -> Self {
RequestBuffers {
count: reqbufs.count,
capabilities: BufferCapabilities::from_bits_truncate(reqbufs.capabilities),
flags: MemoryConsistency::from_bits_truncate(reqbufs.flags),
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_create_buffers;
use crate::bindings::v4l2_requestbuffers;
nix::ioctl_readwrite!(vidioc_reqbufs, b'V', 8, v4l2_requestbuffers);
nix::ioctl_readwrite!(vidioc_create_bufs, b'V', 92, v4l2_create_buffers);
}
#[derive(Debug, Error)]
pub enum ReqbufsError {
#[error("invalid buffer ({0}) or memory type ({1:?}) requested")]
InvalidBufferType(QueueType, MemoryType),
#[error("ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<ReqbufsError> for Errno {
fn from(err: ReqbufsError) -> Self {
match err {
ReqbufsError::InvalidBufferType(_, _) => Errno::EINVAL,
ReqbufsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_REQBUFS` ioctl.
pub fn reqbufs<O: From<v4l2_requestbuffers>>(
fd: &impl AsRawFd,
queue: QueueType,
memory: MemoryType,
count: u32,
flags: MemoryConsistency,
) -> Result<O, ReqbufsError> {
let mut reqbufs = v4l2_requestbuffers {
count,
type_: queue as u32,
memory: memory as u32,
flags: flags.bits(),
..Default::default()
};
match unsafe { ioctl::vidioc_reqbufs(fd.as_raw_fd(), &mut reqbufs) } {
Ok(_) => Ok(O::from(reqbufs)),
Err(Errno::EINVAL) => Err(ReqbufsError::InvalidBufferType(queue, memory)),
Err(e) => Err(ReqbufsError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum CreateBufsError {
#[error("no memory available to allocate MMAP buffers")]
NoMem,
#[error("invalid format or memory type requested")]
Invalid,
#[error("ioctl error: {0}")]
IoctlError(nix::Error),
}
impl From<CreateBufsError> for Errno {
fn from(err: CreateBufsError) -> Self {
match err {
CreateBufsError::NoMem => Errno::ENOMEM,
CreateBufsError::Invalid => Errno::EINVAL,
CreateBufsError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_CREATE_BUFS` ioctl.
pub fn create_bufs<F: Into<v4l2_format>, O: From<v4l2_create_buffers>>(
fd: &impl AsRawFd,
count: u32,
memory: MemoryType,
format: F,
) -> Result<O, CreateBufsError> {
let mut create_bufs = v4l2_create_buffers {
count,
memory: memory as u32,
format: format.into(),
..Default::default()
};
match unsafe { ioctl::vidioc_create_bufs(fd.as_raw_fd(), &mut create_bufs) } {
Ok(_) => Ok(O::from(create_bufs)),
Err(Errno::ENOMEM) => Err(CreateBufsError::NoMem),
Err(Errno::EINVAL) => Err(CreateBufsError::Invalid),
Err(e) => Err(CreateBufsError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,71 @@
//! Safe wrapper for the `VIDIOC_STREAM(ON|OFF)` ioctls.
use crate::QueueType;
use nix::errno::Errno;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
#[doc(hidden)]
mod ioctl {
nix::ioctl_write_ptr!(vidioc_streamon, b'V', 18, u32);
nix::ioctl_write_ptr!(vidioc_streamoff, b'V', 19, u32);
}
#[derive(Debug, Error)]
pub enum StreamOnError {
#[error("queue type ({0}) not supported, or no buffers allocated or enqueued")]
InvalidQueue(QueueType),
#[error("invalid pad configuration")]
InvalidPadConfig,
#[error("invalid pipeline link configuration")]
InvalidPipelineConfig,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<StreamOnError> for Errno {
fn from(err: StreamOnError) -> Self {
match err {
StreamOnError::InvalidQueue(_) => Errno::EINVAL,
StreamOnError::InvalidPadConfig => Errno::EPIPE,
StreamOnError::InvalidPipelineConfig => Errno::ENOLINK,
StreamOnError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_STREAMON` ioctl.
pub fn streamon(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOnError> {
match unsafe { ioctl::vidioc_streamon(fd.as_raw_fd(), &(queue as u32)) } {
Ok(_) => Ok(()),
Err(Errno::EINVAL) => Err(StreamOnError::InvalidQueue(queue)),
Err(Errno::EPIPE) => Err(StreamOnError::InvalidPadConfig),
Err(Errno::ENOLINK) => Err(StreamOnError::InvalidPipelineConfig),
Err(e) => Err(StreamOnError::IoctlError(e)),
}
}
#[derive(Debug, Error)]
pub enum StreamOffError {
#[error("queue type not supported")]
InvalidQueue,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl From<StreamOffError> for Errno {
fn from(err: StreamOffError) -> Self {
match err {
StreamOffError::InvalidQueue => Errno::EINVAL,
StreamOffError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_STREAMOFF` ioctl.
pub fn streamoff(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOffError> {
match unsafe { ioctl::vidioc_streamoff(fd.as_raw_fd(), &(queue as u32)) } {
Ok(_) => Ok(()),
Err(Errno::EINVAL) => Err(StreamOffError::InvalidQueue),
Err(e) => Err(StreamOffError::IoctlError(e)),
}
}

View File

@@ -0,0 +1,210 @@
//! Safe wrapper for the `VIDIOC_SUBSCRIBE_EVENT` and `VIDIOC_UNSUBSCRIBE_EVENT
//! ioctls.
use std::convert::TryFrom;
use std::convert::TryInto;
use std::os::unix::io::AsRawFd;
use bitflags::bitflags;
use nix::errno::Errno;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_event;
use crate::bindings::v4l2_event_subscription;
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct SubscribeEventFlags: u32 {
const SEND_INITIAL = bindings::V4L2_EVENT_SUB_FL_SEND_INITIAL;
const ALLOW_FEEDBACK = bindings::V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK;
}
}
#[derive(Debug)]
pub enum EventType {
VSync,
Eos,
Ctrl(u32),
FrameSync,
SourceChange(u32),
MotionDet,
}
#[derive(Debug, Error)]
pub enum EventConversionError {
#[error("unrecognized event {0}")]
UnrecognizedEvent(u32),
#[error("unrecognized source change {0}")]
UnrecognizedSourceChange(u32),
}
impl TryFrom<&v4l2_event_subscription> for EventType {
type Error = EventConversionError;
fn try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error> {
Ok(match event.type_ {
bindings::V4L2_EVENT_VSYNC => EventType::VSync,
bindings::V4L2_EVENT_EOS => EventType::Eos,
bindings::V4L2_EVENT_CTRL => EventType::Ctrl(event.id),
bindings::V4L2_EVENT_FRAME_SYNC => EventType::FrameSync,
bindings::V4L2_EVENT_SOURCE_CHANGE => EventType::SourceChange(event.id),
bindings::V4L2_EVENT_MOTION_DET => EventType::MotionDet,
e => return Err(EventConversionError::UnrecognizedEvent(e)),
})
}
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct SrcChanges: u32 {
const RESOLUTION = bindings::V4L2_EVENT_SRC_CH_RESOLUTION;
}
}
#[derive(Debug)]
pub enum Event {
SrcChangeEvent(SrcChanges),
Eos,
}
impl TryFrom<v4l2_event> for Event {
type Error = EventConversionError;
fn try_from(value: v4l2_event) -> Result<Self, Self::Error> {
Ok(match value.type_ {
bindings::V4L2_EVENT_VSYNC => todo!(),
bindings::V4L2_EVENT_EOS => Event::Eos,
bindings::V4L2_EVENT_CTRL => todo!(),
bindings::V4L2_EVENT_FRAME_SYNC => todo!(),
bindings::V4L2_EVENT_SOURCE_CHANGE => {
let changes = unsafe { value.u.src_change.changes };
Event::SrcChangeEvent(
SrcChanges::from_bits(changes)
.ok_or(EventConversionError::UnrecognizedSourceChange(changes))?,
)
}
bindings::V4L2_EVENT_MOTION_DET => todo!(),
t => return Err(EventConversionError::UnrecognizedEvent(t)),
})
}
}
fn build_v4l2_event_subscription(
event: EventType,
flags: SubscribeEventFlags,
) -> v4l2_event_subscription {
v4l2_event_subscription {
type_: match event {
EventType::VSync => bindings::V4L2_EVENT_VSYNC,
EventType::Eos => bindings::V4L2_EVENT_EOS,
EventType::Ctrl(_) => bindings::V4L2_EVENT_CTRL,
EventType::FrameSync => bindings::V4L2_EVENT_FRAME_SYNC,
EventType::SourceChange(_) => bindings::V4L2_EVENT_SOURCE_CHANGE,
EventType::MotionDet => bindings::V4L2_EVENT_MOTION_DET,
},
id: match event {
EventType::Ctrl(id) => id,
EventType::SourceChange(id) => id,
_ => 0,
},
flags: flags.bits(),
..Default::default()
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::{v4l2_event, v4l2_event_subscription};
nix::ioctl_read!(vidioc_dqevent, b'V', 89, v4l2_event);
nix::ioctl_write_ptr!(vidioc_subscribe_event, b'V', 90, v4l2_event_subscription);
nix::ioctl_write_ptr!(vidioc_unsubscribe_event, b'V', 91, v4l2_event_subscription);
}
#[derive(Debug, Error)]
pub enum SubscribeEventError {
#[error("ioctl error: {0}")]
IoctlError(#[from] Errno),
}
impl From<SubscribeEventError> for Errno {
fn from(err: SubscribeEventError) -> Self {
match err {
SubscribeEventError::IoctlError(e) => e,
}
}
}
/// Safe wrapper around the `VIDIOC_SUBSCRIBE_EVENT` ioctl.
pub fn subscribe_event(
fd: &impl AsRawFd,
event: EventType,
flags: SubscribeEventFlags,
) -> Result<(), SubscribeEventError> {
let subscription = build_v4l2_event_subscription(event, flags);
unsafe { ioctl::vidioc_subscribe_event(fd.as_raw_fd(), &subscription) }?;
Ok(())
}
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl.
pub fn unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError> {
let subscription = build_v4l2_event_subscription(event, SubscribeEventFlags::empty());
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
Ok(())
}
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl to unsubscribe from all events.
pub fn unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError> {
let subscription = v4l2_event_subscription {
type_: bindings::V4L2_EVENT_ALL,
..Default::default()
};
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
Ok(())
}
#[derive(Debug, Error)]
pub enum DqEventError {
#[error("no event ready for dequeue")]
NotReady,
#[error("error while converting event")]
EventConversionError,
#[error("unexpected ioctl error: {0}")]
IoctlError(Errno),
}
impl From<Errno> for DqEventError {
fn from(error: Errno) -> Self {
match error {
Errno::ENOENT => Self::NotReady,
error => Self::IoctlError(error),
}
}
}
impl From<DqEventError> for Errno {
fn from(err: DqEventError) -> Self {
match err {
DqEventError::NotReady => Errno::ENOENT,
DqEventError::EventConversionError => Errno::EINVAL,
DqEventError::IoctlError(e) => e,
}
}
}
pub fn dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError> {
let mut event: v4l2_event = Default::default();
match unsafe { ioctl::vidioc_dqevent(fd.as_raw_fd(), &mut event) } {
Ok(_) => Ok(event
.try_into()
.map_err(|_| DqEventError::EventConversionError)?),
Err(Errno::ENOENT) => Err(DqEventError::NotReady),
Err(e) => Err(DqEventError::IoctlError(e)),
}
}

496
libs/v4l2r/src/lib.rs Normal file
View File

@@ -0,0 +1,496 @@
//! This library provides the V4L2 pieces One-KVM needs for video capture:
//!
//! * The `ioctl` module provides direct, thin wrappers over the V4L2 ioctls
//! with added safety. Note that "safety" here is in terms of memory safety:
//! this layer won't guard against passing invalid data that the ioctls will
//! reject - it just makes sure that data passed from and to the kernel can
//! be accessed safely. Since this is a 1:1 mapping over the V4L2 ioctls,
//! working at this level is a bit laborious, although more comfortable than
//! doing the same in C.
//!
//! The upstream v4l2r crate also contains high-level decoder/encoder and C FFI
//! layers. This vendored copy intentionally excludes those pieces and keeps the
//! capture-oriented ioctl/memory surface only.
//!
#[doc(hidden)]
pub mod bindings;
pub mod ioctl;
pub mod memory;
// This can be needed to match nix errors that we expose.
pub use nix;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{Debug, Display};
use enumn::N;
use thiserror::Error;
// The goal of this library is to provide two layers of abstraction:
// ioctl: direct, safe counterparts of the V4L2 ioctls.
// device/queue/buffer: higher abstraction, still mapping to core V4L2 mechanics.
/// Possible directions for the queue
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum QueueDirection {
Output,
Capture,
}
/// Possible classes for this queue.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum QueueClass {
Video,
Vbi,
SlicedVbi,
VideoOverlay,
VideoMplane,
Sdr,
Meta,
}
/// Types of queues currently supported by this library.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, N)]
#[repr(u32)]
pub enum QueueType {
VideoCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
VideoOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
VideoOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OVERLAY,
VbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_CAPTURE,
VbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_OUTPUT,
SlicedVbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_CAPTURE,
SlicedVbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
VideoOutputOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
VideoCaptureMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
VideoOutputMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
SdrCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_CAPTURE,
SdrOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_OUTPUT,
MetaCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_CAPTURE,
MetaOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_OUTPUT,
}
impl QueueType {
/// Returns the queue corresponding to the passed `direction` and `class`.
pub fn from_dir_and_class(direction: QueueDirection, class: QueueClass) -> Self {
match (direction, class) {
(QueueDirection::Capture, QueueClass::Video) => Self::VideoCapture,
(QueueDirection::Output, QueueClass::Video) => Self::VideoOutput,
(QueueDirection::Capture, QueueClass::VideoOverlay) => Self::VideoOverlay,
(QueueDirection::Output, QueueClass::VideoOverlay) => Self::VideoOutputOverlay,
(QueueDirection::Capture, QueueClass::Vbi) => Self::VbiCapture,
(QueueDirection::Output, QueueClass::Vbi) => Self::VbiOutput,
(QueueDirection::Capture, QueueClass::SlicedVbi) => Self::SlicedVbiCapture,
(QueueDirection::Output, QueueClass::SlicedVbi) => Self::SlicedVbiOutput,
(QueueDirection::Capture, QueueClass::VideoMplane) => Self::VideoCaptureMplane,
(QueueDirection::Output, QueueClass::VideoMplane) => Self::VideoOutputMplane,
(QueueDirection::Capture, QueueClass::Sdr) => Self::SdrCapture,
(QueueDirection::Output, QueueClass::Sdr) => Self::SdrOutput,
(QueueDirection::Capture, QueueClass::Meta) => Self::MetaCapture,
(QueueDirection::Output, QueueClass::Meta) => Self::MetaOutput,
}
}
/// Returns whether the queue type is multiplanar.
pub fn is_multiplanar(&self) -> bool {
matches!(
self,
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane
)
}
/// Returns the direction of the queue type (Output or Capture).
pub fn direction(&self) -> QueueDirection {
match self {
QueueType::VideoOutput
| QueueType::VideoOutputMplane
| QueueType::VideoOverlay
| QueueType::VideoOutputOverlay
| QueueType::VbiOutput
| QueueType::SlicedVbiOutput
| QueueType::SdrOutput
| QueueType::MetaOutput => QueueDirection::Output,
QueueType::VideoCapture
| QueueType::VbiCapture
| QueueType::SlicedVbiCapture
| QueueType::VideoCaptureMplane
| QueueType::SdrCapture
| QueueType::MetaCapture => QueueDirection::Capture,
}
}
pub fn class(&self) -> QueueClass {
match self {
QueueType::VideoCapture | QueueType::VideoOutput => QueueClass::Video,
QueueType::VideoOverlay | QueueType::VideoOutputOverlay => QueueClass::VideoOverlay,
QueueType::VbiCapture | QueueType::VbiOutput => QueueClass::Vbi,
QueueType::SlicedVbiCapture | QueueType::SlicedVbiOutput => QueueClass::SlicedVbi,
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => QueueClass::VideoMplane,
QueueType::SdrCapture | QueueType::SdrOutput => QueueClass::Sdr,
QueueType::MetaCapture | QueueType::MetaOutput => QueueClass::Meta,
}
}
}
impl Display for QueueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
}
}
/// A Fourcc pixel format, used to pass formats to V4L2. It can be converted
/// back and forth from a 32-bit integer, or a 4-bytes string.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct PixelFormat(u32);
impl PixelFormat {
pub const fn from_u32(v: u32) -> Self {
Self(v)
}
pub const fn to_u32(self) -> u32 {
self.0
}
pub const fn from_fourcc(n: &[u8; 4]) -> Self {
Self(n[0] as u32 | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24)
}
pub const fn to_fourcc(self) -> [u8; 4] {
self.0.to_le_bytes()
}
}
/// Converts a Fourcc in 32-bit integer format (like the ones passed in V4L2
/// structures) into the matching pixel format.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// // Fourcc representation of NV12.
/// let nv12 = u32::from_le(0x3231564e);
/// let f = PixelFormat::from(nv12);
/// assert_eq!(u32::from(f), nv12);
/// ```
impl From<u32> for PixelFormat {
fn from(i: u32) -> Self {
Self::from_u32(i)
}
}
/// Converts a pixel format back to its 32-bit representation.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// // Fourcc representation of NV12.
/// let nv12 = u32::from_le(0x3231564e);
/// let f = PixelFormat::from(nv12);
/// assert_eq!(u32::from(f), nv12);
/// ```
impl From<PixelFormat> for u32 {
fn from(format: PixelFormat) -> Self {
format.to_u32()
}
}
/// Simple way to convert a string litteral (e.g. b"NV12") into a pixel
/// format that can be passed to V4L2.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// let nv12 = b"NV12";
/// let f = PixelFormat::from(nv12);
/// assert_eq!(&<[u8; 4]>::from(f), nv12);
/// ```
impl From<&[u8; 4]> for PixelFormat {
fn from(n: &[u8; 4]) -> Self {
Self::from_fourcc(n)
}
}
/// Convert a pixel format back to its 4-character representation.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// let nv12 = b"NV12";
/// let f = PixelFormat::from(nv12);
/// assert_eq!(&<[u8; 4]>::from(f), nv12);
/// ```
impl From<PixelFormat> for [u8; 4] {
fn from(format: PixelFormat) -> Self {
format.to_fourcc()
}
}
/// Produces a debug string for this PixelFormat, including its hexadecimal
/// and string representation.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// // Fourcc representation of NV12.
/// let nv12 = u32::from_le(0x3231564e);
/// let f = PixelFormat::from(nv12);
/// assert_eq!(format!("{:?}", f), "0x3231564e (NV12)");
/// ```
impl fmt::Debug for PixelFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!("0x{:08x} ({})", self.0, self))
}
}
/// Produces a displayable form of this PixelFormat.
///
/// # Examples
///
/// ```
/// # use v4l2r::PixelFormat;
/// // Fourcc representation of NV12.
/// let nv12 = u32::from_le(0x3231564e);
/// let f = PixelFormat::from(nv12);
/// assert_eq!(f.to_string(), "NV12");
/// ```
impl fmt::Display for PixelFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let fourcc = self
.0
.to_le_bytes()
.iter()
.map(|&x| x as char)
.collect::<String>();
f.write_str(fourcc.as_str())
}
}
/// Description of a single plane in a format.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct PlaneLayout {
/// Useful size of the plane ; the backing memory must be at least that large.
pub sizeimage: u32,
/// Bytes per line of data. Only meaningful for image formats.
pub bytesperline: u32,
}
/// Unified representation of a V4L2 format capable of handling both single
/// and multi-planar formats. When the single-planar API is used, only
/// one plane shall be used - attempts to have more will be rejected by the
/// ioctl wrappers.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct Format {
/// Width of the image in pixels.
pub width: u32,
/// Height of the image in pixels.
pub height: u32,
/// Format each pixel is encoded in.
pub pixelformat: PixelFormat,
/// Individual layout of each plane in this format. The exact number of planes
/// is defined by `pixelformat`.
pub plane_fmt: Vec<PlaneLayout>,
}
#[derive(Debug, Error, PartialEq)]
pub enum FormatConversionError {
#[error("too many planes ({0}) specified,")]
TooManyPlanes(usize),
#[error("invalid buffer type requested")]
InvalidBufferType(u32),
}
impl TryFrom<bindings::v4l2_format> for Format {
type Error = FormatConversionError;
fn try_from(fmt: bindings::v4l2_format) -> std::result::Result<Self, Self::Error> {
match fmt.type_ {
bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE
| bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT => {
let pix = unsafe { &fmt.fmt.pix };
Ok(Format {
width: pix.width,
height: pix.height,
pixelformat: PixelFormat::from(pix.pixelformat),
plane_fmt: vec![PlaneLayout {
bytesperline: pix.bytesperline,
sizeimage: pix.sizeimage,
}],
})
}
bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
| bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE => {
let pix_mp = unsafe { &fmt.fmt.pix_mp };
// Can only happen if we passed a malformed v4l2_format.
if pix_mp.num_planes as usize > pix_mp.plane_fmt.len() {
return Err(Self::Error::TooManyPlanes(pix_mp.num_planes as usize));
}
let mut plane_fmt = Vec::new();
for i in 0..pix_mp.num_planes as usize {
let plane = &pix_mp.plane_fmt[i];
plane_fmt.push(PlaneLayout {
sizeimage: plane.sizeimage,
bytesperline: plane.bytesperline,
});
}
Ok(Format {
width: pix_mp.width,
height: pix_mp.height,
pixelformat: PixelFormat::from(pix_mp.pixelformat),
plane_fmt,
})
}
t => Err(Self::Error::InvalidBufferType(t)),
}
}
}
/// Quickly build a usable `Format` from a pixel format and resolution.
///
/// # Examples
///
/// ```
/// # use v4l2r::Format;
/// let f = Format::from((b"NV12", (640, 480)));
/// assert_eq!(f.width, 640);
/// assert_eq!(f.height, 480);
/// assert_eq!(f.pixelformat.to_string(), "NV12");
/// assert_eq!(f.plane_fmt.len(), 0);
/// ```
impl<T: Into<PixelFormat>> From<(T, (usize, usize))> for Format {
fn from((pixel_format, (width, height)): (T, (usize, usize))) -> Self {
Format {
width: width as u32,
height: height as u32,
pixelformat: pixel_format.into(),
..Default::default()
}
}
}
/// A more elegant representation for `v4l2_rect`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub left: i32,
pub top: i32,
pub width: u32,
pub height: u32,
}
impl Rect {
pub fn new(left: i32, top: i32, width: u32, height: u32) -> Rect {
Rect {
left,
top,
width,
height,
}
}
}
impl From<bindings::v4l2_rect> for Rect {
fn from(rect: bindings::v4l2_rect) -> Self {
Rect {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
}
}
}
impl From<bindings::v4l2_selection> for Rect {
fn from(selection: bindings::v4l2_selection) -> Self {
Self::from(selection.r)
}
}
impl From<Rect> for bindings::v4l2_rect {
fn from(rect: Rect) -> Self {
bindings::v4l2_rect {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
}
}
}
impl Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({}, {}), {}x{}",
self.left, self.top, self.width, self.height
)
}
}
/// Equivalent of `enum v4l2_colorspace`.
#[repr(u32)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
pub enum Colorspace {
#[default]
Default = bindings::v4l2_colorspace_V4L2_COLORSPACE_DEFAULT,
Smpte170M = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE170M,
Smpte240M = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE240M,
Rec709 = bindings::v4l2_colorspace_V4L2_COLORSPACE_REC709,
Bt878 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT878,
SystemM470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_M,
SystemBG470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_BG,
Jpeg = bindings::v4l2_colorspace_V4L2_COLORSPACE_JPEG,
Srgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB,
OpRgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_OPRGB,
Bt2020 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT2020,
Raw = bindings::v4l2_colorspace_V4L2_COLORSPACE_RAW,
DciP3 = bindings::v4l2_colorspace_V4L2_COLORSPACE_DCI_P3,
}
/// Equivalent of `enum v4l2_xfer_func`.
#[repr(u32)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
pub enum XferFunc {
#[default]
Default = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_DEFAULT,
F709 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_709,
Srgb = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SRGB,
OpRgb = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_OPRGB,
Smpte240M = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SMPTE240M,
None = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_NONE,
DciP3 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_DCI_P3,
Smpte2084 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SMPTE2084,
}
/// Equivalent of `enum v4l2_ycbcr_encoding`.
#[repr(u32)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
pub enum YCbCrEncoding {
#[default]
Default = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_DEFAULT,
E601 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_601,
E709 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_709,
Xv601 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_XV601,
Xv709 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_XV709,
Sycc = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_SYCC,
Bt2020 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_BT2020,
Bt2020ConstLum = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_BT2020_CONST_LUM,
Smpte240M = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_SMPTE240M,
}
/// Equivalent of `enum v4l2_quantization`.
#[repr(u32)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
pub enum Quantization {
#[default]
Default = bindings::v4l2_quantization_V4L2_QUANTIZATION_DEFAULT,
FullRange = bindings::v4l2_quantization_V4L2_QUANTIZATION_FULL_RANGE,
LimRange = bindings::v4l2_quantization_V4L2_QUANTIZATION_LIM_RANGE,
}

279
libs/v4l2r/src/memory.rs Normal file
View File

@@ -0,0 +1,279 @@
//! Abstracts the different kinds of backing memory (`MMAP`, `USERPTR`,
//! `DMABUF`) supported by V4L2.
//!
//! V4L2 allows to use either memory that is provided by the device itself
//! (MMAP) or memory imported via user allocation (USERPTR) or the dma-buf
//! subsystem (DMABUF). This results in 2 very different behaviors and 3 memory
//! types that we need to model.
//!
//! The `Memory` trait represents these memory types and is thus implemented
//! by exacly 3 types: `MMAP`, `UserPtr`, and `DMABuf`. These types do very
//! little apart from providing a constant with the corresponding V4L2 memory
//! type they model, and implement the `SelfBacked` (for MMAP) or `Imported`
//! (for `UserPtr` and `DMABuf`) traits to indicate where their memory comes
//! from.
//!
//! The `PlaneHandle` trait is used by types which can bind to one of these
//! memory types, i.e. a type that can represent a single memory plane of a
//! buffer. For `MMAP` memory this is a void type (since `MMAP` provides its
//! own memory). `UserPtr`, a `Vec<u8>` can adequately be used as backing
//! memory, and for `DMABuf` we will use a file descriptor. For handles that
//! can be mapped into the user address-space (and indeed for `MMAP` this is
//! the only way to access the memory), the `Mappable` trait can be implemented.
//!
//! The set of handles that make all the planes for a given buffer is
//! represented by the `BufferHandles` trait. This trait is more abstract since
//! we may want to decide at runtime the kind of memory we want to use ;
//! therefore this trait does not have any particular kind of memory attached to
//! it. `PrimitiveBufferHandles` is used to represent plane handles which memory
//! type is known at compilation time, and thus includes a reference to a
//! `PlaneHandle` type and by transition its `Memory` type.
mod dmabuf;
mod mmap;
mod userptr;
pub use dmabuf::*;
pub use mmap::*;
pub use userptr::*;
use crate::{
bindings::{self, v4l2_buffer__bindgen_ty_1, v4l2_plane__bindgen_ty_1},
ioctl::{PlaneMapping, QueryBufPlane},
};
use enumn::N;
use std::os::unix::io::AsFd;
use std::{fmt::Debug, ops::Deref};
/// All the supported V4L2 memory types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, N)]
#[repr(u32)]
pub enum MemoryType {
Mmap = bindings::v4l2_memory_V4L2_MEMORY_MMAP,
UserPtr = bindings::v4l2_memory_V4L2_MEMORY_USERPTR,
Overlay = bindings::v4l2_memory_V4L2_MEMORY_OVERLAY,
DmaBuf = bindings::v4l2_memory_V4L2_MEMORY_DMABUF,
}
/// Trait describing a memory type that can be used to back V4L2 buffers.
pub trait Memory: 'static {
/// The memory type represented.
const MEMORY_TYPE: MemoryType;
/// The final type of the memory backing information in `struct v4l2_buffer` or `struct
/// v4l2_plane`.
type RawBacking;
/// Returns a reference to the memory backing information for `m` that is relevant for this
/// memory type.
///
/// # Safety
///
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
unsafe fn get_plane_buffer_backing(m: &bindings::v4l2_plane__bindgen_ty_1)
-> &Self::RawBacking;
/// Returns a reference to the memory backing information for `m` that is relevant for this memory type.
///
/// # Safety
///
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
unsafe fn get_single_planar_buffer_backing(
m: &bindings::v4l2_buffer__bindgen_ty_1,
) -> &Self::RawBacking;
/// Returns a mutable reference to the memory backing information for `m` that is relevant for
/// this memory type.
///
/// # Safety
///
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
unsafe fn get_plane_buffer_backing_mut(
m: &mut bindings::v4l2_plane__bindgen_ty_1,
) -> &mut Self::RawBacking;
/// Returns a mutable reference to the memory backing information for `m` that is relevant for
/// this memory type.
///
/// # Safety
///
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
unsafe fn get_single_planar_buffer_backing_mut(
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
) -> &mut Self::RawBacking;
}
/// Trait for memory types that provide their own memory, i.e. MMAP.
pub trait SelfBacked: Memory + Default {}
/// Trait for memory types to which external memory must be attached to, i.e. UserPtr and
/// DMABuf.
pub trait Imported: Memory {}
/// Trait for a handle that represents actual data for a single place. A buffer
/// will have as many of these as it has planes.
pub trait PlaneHandle: Debug + Send + 'static {
/// The kind of memory the handle attaches to.
type Memory: Memory;
/// Fill a plane of a multi-planar V4L2 buffer with the handle's information.
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane);
}
// Trait for plane handles that provide access to their content through a map()
// method (typically, MMAP buffers).
pub trait Mappable: PlaneHandle {
/// Return a `PlaneMapping` enabling access to the memory of this handle.
fn map<D: AsFd>(device: &D, plane_info: &QueryBufPlane) -> Option<PlaneMapping>;
}
/// Trait for structures providing all the handles of a single buffer.
pub trait BufferHandles: Send + Debug + 'static {
/// Enumeration of all the `MemoryType` supported by this type. Typically
/// a subset of `MemoryType` or `MemoryType` itself.
type SupportedMemoryType: Into<MemoryType> + Send + Clone + Copy;
/// Number of planes.
fn len(&self) -> usize;
/// Fill a plane of a multi-planar V4L2 buffer with the `index` handle's information.
fn fill_v4l2_plane(&self, index: usize, plane: &mut bindings::v4l2_plane);
/// Returns true if there are no handles here (unlikely).
fn is_empty(&self) -> bool {
self.len() == 0
}
}
/// Implementation of `BufferHandles` for all indexables of `PlaneHandle` (e.g. [`std::vec::Vec`]).
///
/// This is The simplest way to use primitive handles.
impl<P, Q> BufferHandles for Q
where
P: PlaneHandle,
Q: Send + Debug + 'static + Deref<Target = [P]>,
{
type SupportedMemoryType = MemoryType;
fn len(&self) -> usize {
self.deref().len()
}
fn fill_v4l2_plane(&self, index: usize, plane: &mut bindings::v4l2_plane) {
self.deref()[index].fill_v4l2_plane(plane);
}
}
/// Trait for plane handles for which the final memory type is known at compile
/// time.
pub trait PrimitiveBufferHandles: BufferHandles {
type HandleType: PlaneHandle;
const MEMORY_TYPE: Self::SupportedMemoryType;
}
/// Implementation of `PrimitiveBufferHandles` for all indexables of `PlaneHandle` (e.g.
/// [`std::vec::Vec`]).
impl<P, Q> PrimitiveBufferHandles for Q
where
P: PlaneHandle,
Q: Send + Debug + 'static + Deref<Target = [P]>,
{
type HandleType = P;
const MEMORY_TYPE: Self::SupportedMemoryType = P::Memory::MEMORY_TYPE;
}
/// Conversion from `v4l2_buffer`'s backing information to `v4l2_plane`'s.
impl From<(&v4l2_buffer__bindgen_ty_1, MemoryType)> for v4l2_plane__bindgen_ty_1 {
fn from((m, memory): (&v4l2_buffer__bindgen_ty_1, MemoryType)) -> Self {
match memory {
MemoryType::Mmap => v4l2_plane__bindgen_ty_1 {
// Safe because the buffer type is determined to be MMAP.
mem_offset: unsafe { m.offset },
},
MemoryType::UserPtr => v4l2_plane__bindgen_ty_1 {
// Safe because the buffer type is determined to be USERPTR.
userptr: unsafe { m.userptr },
},
MemoryType::DmaBuf => v4l2_plane__bindgen_ty_1 {
// Safe because the buffer type is determined to be DMABUF.
fd: unsafe { m.fd },
},
MemoryType::Overlay => Default::default(),
}
}
}
/// Conversion from `v4l2_plane`'s backing information to `v4l2_buffer`'s.
impl From<(&v4l2_plane__bindgen_ty_1, MemoryType)> for v4l2_buffer__bindgen_ty_1 {
fn from((m, memory): (&v4l2_plane__bindgen_ty_1, MemoryType)) -> Self {
match memory {
MemoryType::Mmap => v4l2_buffer__bindgen_ty_1 {
// Safe because the buffer type is determined to be MMAP.
offset: unsafe { m.mem_offset },
},
MemoryType::UserPtr => v4l2_buffer__bindgen_ty_1 {
// Safe because the buffer type is determined to be USERPTR.
userptr: unsafe { m.userptr },
},
MemoryType::DmaBuf => v4l2_buffer__bindgen_ty_1 {
// Safe because the buffer type is determined to be DMABUF.
fd: unsafe { m.fd },
},
MemoryType::Overlay => Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use crate::bindings::v4l2_buffer__bindgen_ty_1;
use crate::bindings::v4l2_plane__bindgen_ty_1;
use crate::memory::MemoryType;
#[test]
// Purpose of this test is dubious as the members are overlapping anyway.
fn plane_m_to_buffer_m() {
let plane_m = v4l2_plane__bindgen_ty_1 {
mem_offset: 0xfeedc0fe,
};
assert_eq!(
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::Mmap)).offset },
0xfeedc0fe
);
let plane_m = v4l2_plane__bindgen_ty_1 {
userptr: 0xfeedc0fe,
};
assert_eq!(
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::UserPtr)).userptr },
0xfeedc0fe
);
let plane_m = v4l2_plane__bindgen_ty_1 { fd: 0x76543210 };
assert_eq!(
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::DmaBuf)).fd },
0x76543210
);
}
#[test]
// Purpose of this test is dubious as the members are overlapping anyway.
fn buffer_m_to_plane_m() {
let buffer_m = v4l2_buffer__bindgen_ty_1 { offset: 0xfeedc0fe };
assert_eq!(
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::Mmap)).mem_offset },
0xfeedc0fe
);
let buffer_m = v4l2_buffer__bindgen_ty_1 {
userptr: 0xfeedc0fe,
};
assert_eq!(
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::UserPtr)).userptr },
0xfeedc0fe
);
let buffer_m = v4l2_buffer__bindgen_ty_1 { fd: 0x76543210 };
assert_eq!(
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::DmaBuf)).fd },
0x76543210
);
}
}

View File

@@ -0,0 +1,91 @@
//! Operations specific to DMABuf-type buffers.
use log::warn;
use super::*;
use crate::{bindings, ioctl};
use std::os::fd::RawFd;
use std::os::unix::io::{AsFd, AsRawFd};
pub struct DmaBuf;
pub type DmaBufferHandles<T> = Vec<DmaBufHandle<T>>;
impl Memory for DmaBuf {
const MEMORY_TYPE: MemoryType = MemoryType::DmaBuf;
type RawBacking = RawFd;
unsafe fn get_plane_buffer_backing(
m: &bindings::v4l2_plane__bindgen_ty_1,
) -> &Self::RawBacking {
&m.fd
}
unsafe fn get_single_planar_buffer_backing(
m: &bindings::v4l2_buffer__bindgen_ty_1,
) -> &Self::RawBacking {
&m.fd
}
unsafe fn get_plane_buffer_backing_mut(
m: &mut bindings::v4l2_plane__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.fd
}
unsafe fn get_single_planar_buffer_backing_mut(
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.fd
}
}
impl Imported for DmaBuf {}
pub trait DmaBufSource: AsRawFd + AsFd + Debug + Send {
fn len(&self) -> u64;
/// Make Clippy happy.
fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl DmaBufSource for std::fs::File {
fn len(&self) -> u64 {
match self.metadata() {
Err(_) => {
warn!("Failed to compute File size for use as DMABuf, using 0...");
0
}
Ok(m) => m.len(),
}
}
}
/// Handle for a DMABUF plane. Any type that can provide a file descriptor is
/// valid.
#[derive(Debug)]
pub struct DmaBufHandle<T: DmaBufSource>(pub T);
impl<T: DmaBufSource> From<T> for DmaBufHandle<T> {
fn from(dmabuf: T) -> Self {
DmaBufHandle(dmabuf)
}
}
impl<T: DmaBufSource + 'static> PlaneHandle for DmaBufHandle<T> {
type Memory = DmaBuf;
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane) {
plane.m.fd = self.0.as_raw_fd();
plane.length = self.0.len() as u32;
}
}
impl<T: DmaBufSource> DmaBufHandle<T> {
pub fn map(&self) -> Result<PlaneMapping, ioctl::MmapError> {
let len = self.0.len();
ioctl::mmap(&self.0, 0, len as u32)
}
}

View File

@@ -0,0 +1,58 @@
//! Operations specific to MMAP-type buffers.
use super::*;
use crate::{bindings, ioctl};
use std::fmt::Debug;
use std::os::fd::AsFd;
#[derive(Default)]
pub struct Mmap;
impl Memory for Mmap {
const MEMORY_TYPE: MemoryType = MemoryType::Mmap;
type RawBacking = u32;
unsafe fn get_plane_buffer_backing(
m: &bindings::v4l2_plane__bindgen_ty_1,
) -> &Self::RawBacking {
&m.mem_offset
}
unsafe fn get_single_planar_buffer_backing(
m: &bindings::v4l2_buffer__bindgen_ty_1,
) -> &Self::RawBacking {
&m.offset
}
unsafe fn get_plane_buffer_backing_mut(
m: &mut bindings::v4l2_plane__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.mem_offset
}
unsafe fn get_single_planar_buffer_backing_mut(
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.offset
}
}
impl SelfBacked for Mmap {}
/// Dummy handle for a MMAP plane, to use with APIs that require handles. MMAP
/// buffers are backed by the device, and thus we don't need to attach any extra
/// information to them.
#[derive(Default, Debug, Clone)]
pub struct MmapHandle;
// There is no information to fill with MMAP buffers ; the index is enough.
impl PlaneHandle for MmapHandle {
type Memory = Mmap;
fn fill_v4l2_plane(&self, _plane: &mut bindings::v4l2_plane) {}
}
impl Mappable for MmapHandle {
fn map<D: AsFd>(device: &D, plane_info: &QueryBufPlane) -> Option<PlaneMapping> {
ioctl::mmap(device, plane_info.mem_offset, plane_info.length).ok()
}
}

View File

@@ -0,0 +1,77 @@
//! Operations specific to UserPtr-type buffers.
use super::*;
use crate::bindings;
pub struct UserPtr;
impl Memory for UserPtr {
const MEMORY_TYPE: MemoryType = MemoryType::UserPtr;
type RawBacking = core::ffi::c_ulong;
unsafe fn get_plane_buffer_backing(
m: &bindings::v4l2_plane__bindgen_ty_1,
) -> &Self::RawBacking {
&m.userptr
}
unsafe fn get_single_planar_buffer_backing(
m: &bindings::v4l2_buffer__bindgen_ty_1,
) -> &Self::RawBacking {
&m.userptr
}
unsafe fn get_plane_buffer_backing_mut(
m: &mut bindings::v4l2_plane__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.userptr
}
unsafe fn get_single_planar_buffer_backing_mut(
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
) -> &mut Self::RawBacking {
&mut m.userptr
}
}
impl Imported for UserPtr {}
/// Handle for a USERPTR plane. These buffers are backed by userspace-allocated
/// memory, which translates well into Rust's slice of `u8`s. Since slices also
/// carry size information, we know that we are not passing unallocated areas
/// of the address-space to the kernel.
///
/// USERPTR buffers have the particularity that the `length` field of `struct
/// v4l2_buffer` must be set before doing a `QBUF` ioctl. This handle struct
/// also takes care of that.
#[derive(Debug)]
pub struct UserPtrHandle<T: AsRef<[u8]> + Debug + Send + 'static>(pub T);
impl<T: AsRef<[u8]> + Debug + Send + Clone> Clone for UserPtrHandle<T> {
fn clone(&self) -> Self {
UserPtrHandle(self.0.clone())
}
}
impl<T: AsRef<[u8]> + Debug + Send + 'static> AsRef<[u8]> for UserPtrHandle<T> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<T: AsRef<[u8]> + Debug + Send> From<T> for UserPtrHandle<T> {
fn from(buffer: T) -> Self {
UserPtrHandle(buffer)
}
}
impl<T: AsRef<[u8]> + Debug + Send + 'static> PlaneHandle for UserPtrHandle<T> {
type Memory = UserPtr;
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane) {
let slice = AsRef::<[u8]>::as_ref(&self.0);
plane.m.userptr = slice.as_ptr() as std::os::raw::c_ulong;
plane.length = slice.len() as u32;
}
}

View File

@@ -0,0 +1,10 @@
#ifdef __ANDROID__
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#endif
#include <linux/videodev2.h>
#define MARK_FIX_753(name) const unsigned long int Fix753_##name = name;
#include "fix753.h"