From 6a1616c32af2cb1cec7a719d62b1195948633ed0 Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Sat, 23 May 2026 02:36:40 +0000 Subject: [PATCH] chore: vendor trimmed v4l2r capture crate --- libs/v4l2r/.cargo-ok | 1 + libs/v4l2r/.cargo_vcs_info.json | 6 + libs/v4l2r/Android.bp | 52 + libs/v4l2r/Cargo.toml | 65 ++ libs/v4l2r/Cargo.toml.orig | 28 + libs/v4l2r/LICENSE | 23 + libs/v4l2r/README.md | 24 + libs/v4l2r/bindgen.rs | 22 + libs/v4l2r/build.rs | 180 ++++ libs/v4l2r/fix753.h | 55 ++ libs/v4l2r/src/bindings.rs | 8 + libs/v4l2r/src/ioctl.rs | 1177 +++++++++++++++++++++++ libs/v4l2r/src/ioctl/dqbuf.rs | 66 ++ libs/v4l2r/src/ioctl/enum_fmt.rs | 137 +++ libs/v4l2r/src/ioctl/expbuf.rs | 61 ++ libs/v4l2r/src/ioctl/frameintervals.rs | 82 ++ libs/v4l2r/src/ioctl/framesizes.rs | 79 ++ libs/v4l2r/src/ioctl/g_dv_timings.rs | 189 ++++ libs/v4l2r/src/ioctl/g_fmt.rs | 300 ++++++ libs/v4l2r/src/ioctl/g_parm.rs | 149 +++ libs/v4l2r/src/ioctl/g_selection.rs | 130 +++ libs/v4l2r/src/ioctl/mmap.rs | 117 +++ libs/v4l2r/src/ioctl/qbuf.rs | 197 ++++ libs/v4l2r/src/ioctl/querybuf.rs | 113 +++ libs/v4l2r/src/ioctl/querycap.rs | 136 +++ libs/v4l2r/src/ioctl/reqbufs.rs | 162 ++++ libs/v4l2r/src/ioctl/streamon.rs | 71 ++ libs/v4l2r/src/ioctl/subscribe_event.rs | 210 ++++ libs/v4l2r/src/lib.rs | 496 ++++++++++ libs/v4l2r/src/memory.rs | 279 ++++++ libs/v4l2r/src/memory/dmabuf.rs | 91 ++ libs/v4l2r/src/memory/mmap.rs | 58 ++ libs/v4l2r/src/memory/userptr.rs | 77 ++ libs/v4l2r/v4l2r_wrapper.h | 10 + 34 files changed, 4851 insertions(+) create mode 100644 libs/v4l2r/.cargo-ok create mode 100644 libs/v4l2r/.cargo_vcs_info.json create mode 100644 libs/v4l2r/Android.bp create mode 100644 libs/v4l2r/Cargo.toml create mode 100644 libs/v4l2r/Cargo.toml.orig create mode 100644 libs/v4l2r/LICENSE create mode 100644 libs/v4l2r/README.md create mode 100644 libs/v4l2r/bindgen.rs create mode 100644 libs/v4l2r/build.rs create mode 100644 libs/v4l2r/fix753.h create mode 100644 libs/v4l2r/src/bindings.rs create mode 100644 libs/v4l2r/src/ioctl.rs create mode 100644 libs/v4l2r/src/ioctl/dqbuf.rs create mode 100644 libs/v4l2r/src/ioctl/enum_fmt.rs create mode 100644 libs/v4l2r/src/ioctl/expbuf.rs create mode 100644 libs/v4l2r/src/ioctl/frameintervals.rs create mode 100644 libs/v4l2r/src/ioctl/framesizes.rs create mode 100644 libs/v4l2r/src/ioctl/g_dv_timings.rs create mode 100644 libs/v4l2r/src/ioctl/g_fmt.rs create mode 100644 libs/v4l2r/src/ioctl/g_parm.rs create mode 100644 libs/v4l2r/src/ioctl/g_selection.rs create mode 100644 libs/v4l2r/src/ioctl/mmap.rs create mode 100644 libs/v4l2r/src/ioctl/qbuf.rs create mode 100644 libs/v4l2r/src/ioctl/querybuf.rs create mode 100644 libs/v4l2r/src/ioctl/querycap.rs create mode 100644 libs/v4l2r/src/ioctl/reqbufs.rs create mode 100644 libs/v4l2r/src/ioctl/streamon.rs create mode 100644 libs/v4l2r/src/ioctl/subscribe_event.rs create mode 100644 libs/v4l2r/src/lib.rs create mode 100644 libs/v4l2r/src/memory.rs create mode 100644 libs/v4l2r/src/memory/dmabuf.rs create mode 100644 libs/v4l2r/src/memory/mmap.rs create mode 100644 libs/v4l2r/src/memory/userptr.rs create mode 100644 libs/v4l2r/v4l2r_wrapper.h diff --git a/libs/v4l2r/.cargo-ok b/libs/v4l2r/.cargo-ok new file mode 100644 index 00000000..5f8b7958 --- /dev/null +++ b/libs/v4l2r/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/libs/v4l2r/.cargo_vcs_info.json b/libs/v4l2r/.cargo_vcs_info.json new file mode 100644 index 00000000..70854a40 --- /dev/null +++ b/libs/v4l2r/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "7b441383125ae583017a1c18b3fc9ec6c88ddbe8" + }, + "path_in_vcs": "lib" +} \ No newline at end of file diff --git a/libs/v4l2r/Android.bp b/libs/v4l2r/Android.bp new file mode 100644 index 00000000..01858ee3 --- /dev/null +++ b/libs/v4l2r/Android.bp @@ -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"], + +} diff --git a/libs/v4l2r/Cargo.toml b/libs/v4l2r/Cargo.toml new file mode 100644 index 00000000..176813ec --- /dev/null +++ b/libs/v4l2r/Cargo.toml @@ -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 "] +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" diff --git a/libs/v4l2r/Cargo.toml.orig b/libs/v4l2r/Cargo.toml.orig new file mode 100644 index 00000000..019e1386 --- /dev/null +++ b/libs/v4l2r/Cargo.toml.orig @@ -0,0 +1,28 @@ +[package] +name = "v4l2r" +version = "0.0.7" +authors = ["Alexandre Courbot "] +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" diff --git a/libs/v4l2r/LICENSE b/libs/v4l2r/LICENSE new file mode 100644 index 00000000..31aa7938 --- /dev/null +++ b/libs/v4l2r/LICENSE @@ -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. diff --git a/libs/v4l2r/README.md b/libs/v4l2r/README.md new file mode 100644 index 00000000..35422e0e --- /dev/null +++ b/libs/v4l2r/README.md @@ -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. diff --git a/libs/v4l2r/bindgen.rs b/libs/v4l2r/bindgen.rs new file mode 100644 index 00000000..8d09e96f --- /dev/null +++ b/libs/v4l2r/bindgen.rs @@ -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 { + 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) +} diff --git a/libs/v4l2r/build.rs b/libs/v4l2r/build.rs new file mode 100644 index 00000000..58225480 --- /dev/null +++ b/libs/v4l2r/build.rs @@ -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 { + 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::().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 { + let mut entries = std::fs::read_dir(path) + .ok()? + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_dir()) + .collect::>(); + 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::>(); + entries.sort(); + entries + .pop() + .unwrap_or_else(|| panic!("no clang resource directory in {}", clang_dir.display())) +} diff --git a/libs/v4l2r/fix753.h b/libs/v4l2r/fix753.h new file mode 100644 index 00000000..4c235de9 --- /dev/null +++ b/libs/v4l2r/fix753.h @@ -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 diff --git a/libs/v4l2r/src/bindings.rs b/libs/v4l2r/src/bindings.rs new file mode 100644 index 00000000..5b08b552 --- /dev/null +++ b/libs/v4l2r/src/bindings.rs @@ -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")); diff --git a/libs/v4l2r/src/ioctl.rs b/libs/v4l2r/src/ioctl.rs new file mode 100644 index 00000000..ce05a177 --- /dev/null +++ b/libs/v4l2r/src/ioctl.rs @@ -0,0 +1,1177 @@ +//! This module provides safer versions of the V4L2 ioctls through simple functions working on a +//! `RawFd`, and safer variants of the main V4L2 structures. This module can be used directly, but +//! the `device` module is very likely to be a better fit for application code. +//! +//! V4L2 ioctls are usually called with a single structure as argument, which serves to store both +//! the input and output of the ioctl. This is quite error-prone as the user needs to remember +//! which parts of the structure they are supposed to fill, and which parts the driver will update. +//! +//! To alleviate this issue, this module tries to provide, for each ioctl: +//! +//! Consequently, each ioctl proxy function is designed as follows: +//! +//! * A function that takes the relevant input as parameters and not the entire input/output +//! structure. This lifts any ambiguity as to which parts of the structure userspace is supposed to +//! fill. +//! * Safe variants of V4L2 structures used in ioctls that can be build from their C counterparts +//! (and vice-versa) and include a validation step, to be used as return values. +//! +//! For instance, the `VIDIOC_G_FMT` ioctl takes a `struct v4l2_format` as argument, but only the +//! its `type` field is set by user-space - the rest of the structure is to be filled by the +//! driver. +//! +//! Therefore, our [`crate::ioctl::g_fmt()`] ioctl function takes the requested queue type as +//! argument and takes care of managing the `struct v4l2_format` to be passed to the kernel. The +//! filled structure is then converted into the type desired by the caller using +//! `TryFrom`: +//! +//! ```text +//! pub fn g_fmt>( +//! fd: &impl AsRawFd, +//! queue: QueueType, +//! ) -> Result; +//! ``` +//! +//! Since `struct v4l2_format` has C unions that are unsafe to use in Rust, the [`crate::Format`] +//! type can be used as the output type of this function, to validate the `struct v4l2_format` +//! returned by the kernel and convert it to a safe type. +//! +//! Most ioctls also have their own error type: this helps discern scenarios where the ioctl +//! returned non-zero, but the situation is not necessarily an error. For instance, `VIDIOC_DQBUF` +//! can return -EAGAIN if no buffer is available to dequeue, which is not an error and thus is +//! represented by its own variant. Actual errors are captured by the `IoctlError` variant, and all +//! error types can be converted to their original error code using their `Into` +//! implementation. + +mod dqbuf; +mod enum_fmt; +mod expbuf; +mod frameintervals; +mod framesizes; +mod g_dv_timings; +mod g_fmt; +mod g_parm; +mod g_selection; +mod mmap; +mod qbuf; +mod querybuf; +mod querycap; +mod reqbufs; +mod streamon; +mod subscribe_event; + +pub use dqbuf::*; +pub use enum_fmt::*; +pub use expbuf::*; +pub use frameintervals::*; +pub use framesizes::*; +pub use g_dv_timings::*; +pub use g_fmt::*; +pub use g_parm::*; +pub use g_selection::*; +pub use mmap::*; +pub use qbuf::*; +pub use querybuf::*; +pub use querycap::*; +pub use reqbufs::*; +pub use streamon::*; +pub use subscribe_event::*; + +use std::convert::Infallible; +use std::convert::TryFrom; +use std::ffi::CStr; +use std::ffi::FromBytesWithNulError; +use std::fmt::Debug; +use std::ops::Deref; +use std::ops::DerefMut; + +use bitflags::bitflags; +use enumn::N; +use nix::errno::Errno; +use thiserror::Error; + +use crate::bindings; +use crate::memory::DmaBuf; +use crate::memory::Memory; +use crate::memory::MemoryType; +use crate::memory::Mmap; +use crate::memory::UserPtr; +use crate::Colorspace; +use crate::PixelFormat; +use crate::Quantization; +use crate::QueueDirection; +use crate::QueueType; +use crate::XferFunc; +use crate::YCbCrEncoding; + +/// Utility function for sub-modules. +/// Constructs an owned String instance from a slice containing a nul-terminated +/// C string, after checking that the passed slice indeed contains a nul +/// character. +fn string_from_cstr(c_str: &[u8]) -> Result { + // Make sure that our string contains a nul character. + let slice = match c_str.iter().position(|x| *x == b'\0') { + // Pass the full slice, `from_bytes_with_nul` will return an error. + None => c_str, + Some(pos) => &c_str[..pos + 1], + }; + + Ok(CStr::from_bytes_with_nul(slice)? + .to_string_lossy() + .into_owned()) +} + +/// Extension trait for allowing easy conversion of ioctl errors into their originating error code. +pub trait IntoErrno { + fn into_errno(self) -> i32; +} + +impl IntoErrno for T +where + T: Into, +{ + fn into_errno(self) -> i32 { + self.into() as i32 + } +} + +/// Error type for a "run ioctl and try to convert to safer type" operation. +/// +/// [`IoctlError`] means that the ioctl itself has failed, while [`ConversionError`] indicates that +/// the output of the ioctl could not be converted to the desired output type for the ioctl +#[derive(Debug, Error)] +pub enum IoctlConvertError { + #[error("error during ioctl: {0}")] + IoctlError(#[from] IE), + #[error("error while converting ioctl result: {0}")] + ConversionError(CE), +} + +impl IntoErrno for IoctlConvertError +where + IE: Debug + Into, + CE: Debug, +{ + fn into_errno(self) -> i32 { + match self { + IoctlConvertError::IoctlError(e) => e.into_errno(), + IoctlConvertError::ConversionError(_) => Errno::EINVAL as i32, + } + } +} + +// We need a bound here, otherwise we cannot use `O::Error`. +#[allow(type_alias_bounds)] +pub type IoctlConvertResult = Result>; + +/// Tries to convert the raw output of an ioctl to a safer type. +/// +/// Ioctl wrappers always return a raw C type that most of the case is potentially invalid: for +/// instance C enums might have invalid values. +/// +/// This function takes a raw ioctl result and, if successful, attempts to convert its output to a +/// safer type using [`TryFrom`]. If either the ioctl or the conversion fails, then the appropriate +/// variant of [`IoctlConvertError`] is returned. +fn ioctl_and_convert(res: Result) -> IoctlConvertResult +where + IE: std::fmt::Debug, + O: TryFrom, + O::Error: std::fmt::Debug, +{ + res.map_err(IoctlConvertError::IoctlError) + .and_then(|o| O::try_from(o).map_err(IoctlConvertError::ConversionError)) +} + +/// A fully owned V4L2 buffer obtained from some untrusted place (typically an ioctl), or created +/// with the purpose of receiving the result of an ioctl. +/// +/// For any serious use it should be converted into something safer like [`V4l2Buffer`]. +pub struct UncheckedV4l2Buffer(pub bindings::v4l2_buffer, pub Option); + +impl UncheckedV4l2Buffer { + /// Returns a new buffer with the queue type set to `queue` and its index to `index`. + /// + /// If `queue` is multiplanar, then the number of planes will be set to `VIDEO_MAX_PLANES` so + /// the buffer can receive the result of ioctl that write into a `v4l2_buffer` such as + /// `VIDIOC_QUERYBUF` or `VIDIOC_DQBUF`. [`as_mut`] can be called in order to obtain a + /// reference to the buffer with its `planes` pointer properly set. + pub fn new_for_querybuf(queue: QueueType, index: Option) -> Self { + let multiplanar = queue.is_multiplanar(); + + UncheckedV4l2Buffer( + bindings::v4l2_buffer { + index: index.unwrap_or_default(), + type_: queue as u32, + length: if multiplanar { + bindings::VIDEO_MAX_PLANES + } else { + Default::default() + }, + ..Default::default() + }, + if multiplanar { + Some(Default::default()) + } else { + None + }, + ) + } +} + +/// For cases where we are not interested in the result of `qbuf` +impl TryFrom for () { + type Error = Infallible; + + fn try_from(_: UncheckedV4l2Buffer) -> Result { + Ok(()) + } +} + +impl From for UncheckedV4l2Buffer { + fn from(buffer: V4l2Buffer) -> Self { + let is_multiplanar = buffer.queue().is_multiplanar(); + + Self( + buffer.buffer, + if is_multiplanar { + Some(buffer.planes) + } else { + None + }, + ) + } +} + +/// Returns a mutable pointer to the buffer after making sure its plane pointer is valid, if the +/// buffer is multiplanar. +/// +/// This should be used to make sure the buffer is not going to move as long as the reference is +/// alive. +impl AsMut for UncheckedV4l2Buffer { + fn as_mut(&mut self) -> &mut bindings::v4l2_buffer { + match (QueueType::n(self.0.type_), &mut self.1) { + (Some(queue), Some(planes)) if queue.is_multiplanar() => { + self.0.m.planes = planes.as_mut_ptr() + } + _ => (), + } + + &mut self.0 + } +} + +/// A memory area we can pass to ioctls in order to get/set plane information +/// with the multi-planar API. +type V4l2BufferPlanes = [bindings::v4l2_plane; bindings::VIDEO_MAX_PLANES as usize]; + +bitflags! { + #[derive(Clone, Copy, Debug, Default)] + /// `flags` member of `struct `v4l2_buffer`. + pub struct BufferFlags: u32 { + const MAPPED = bindings::V4L2_BUF_FLAG_MAPPED; + const QUEUED = bindings::V4L2_BUF_FLAG_QUEUED; + const DONE = bindings::V4L2_BUF_FLAG_DONE; + const ERROR = bindings::V4L2_BUF_FLAG_ERROR; + const KEYFRAME = bindings::V4L2_BUF_FLAG_KEYFRAME; + const PFRAME = bindings::V4L2_BUF_FLAG_PFRAME; + const BFRAME = bindings::V4L2_BUF_FLAG_BFRAME; + const TIMECODE = bindings::V4L2_BUF_FLAG_TIMECODE; + const PREPARED = bindings::V4L2_BUF_FLAG_PREPARED; + const NO_CACHE_INVALIDATE = bindings::V4L2_BUF_FLAG_NO_CACHE_CLEAN; + const NO_CACHE_CLEAN = bindings::V4L2_BUF_FLAG_NO_CACHE_INVALIDATE; + const LAST = bindings::V4L2_BUF_FLAG_LAST; + const TIMESTAMP_MONOTONIC = bindings::V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + const TIMESTAMP_COPY = bindings::V4L2_BUF_FLAG_TIMESTAMP_COPY; + const TSTAMP_SRC_EOF = bindings::V4L2_BUF_FLAG_TSTAMP_SRC_EOF; + const TSTAMP_SRC_SOE = bindings::V4L2_BUF_FLAG_TSTAMP_SRC_SOE; + const REQUEST_FD = bindings::V4L2_BUF_FLAG_REQUEST_FD; + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, N)] +#[repr(u32)] +pub enum BufferField { + #[default] + Any = bindings::v4l2_field_V4L2_FIELD_ANY, + None = bindings::v4l2_field_V4L2_FIELD_NONE, + Top = bindings::v4l2_field_V4L2_FIELD_TOP, + Interlaced = bindings::v4l2_field_V4L2_FIELD_INTERLACED, + SeqTb = bindings::v4l2_field_V4L2_FIELD_SEQ_TB, + SeqBt = bindings::v4l2_field_V4L2_FIELD_SEQ_BT, + Alternate = bindings::v4l2_field_V4L2_FIELD_ALTERNATE, + InterlacedTb = bindings::v4l2_field_V4L2_FIELD_INTERLACED_TB, + InterlacedBt = bindings::v4l2_field_V4L2_FIELD_INTERLACED_BT, +} + +#[derive(Debug, Error)] +pub enum V4l2BufferResizePlanesError { + #[error("zero planes requested")] + ZeroPlanesRequested, + #[error("buffer is single planar and can only accomodate one plane")] + SinglePlanar, + #[error("more than VIDEO_MAX_PLANES have been requested")] + TooManyPlanes, +} + +/// Safe-ish representation of a `struct v4l2_buffer`. It owns its own planes array and can only be +/// constructed from valid data. +/// +/// This structure guarantees the following invariants: +/// +/// * The buffer's queue type is valid and cannot change, +/// * The buffer's memory type is valid and cannot change, +/// * The memory backing (MMAP offset/user pointer/DMABUF) can only be read and set according to +/// the memory type of the buffer. I.e. it is impossible to mistakenly set `fd` unless the +/// buffer's memory type is `DMABUF`. +/// * Single-planar buffers can only have exactly one and only one plane. +/// +/// Planes management is a bit complicated due to the existence of the single-planar and a +/// multi-planar buffer representations. There are situations where one wants to access plane +/// information regardless of the representation used, and others where one wants to access the +/// actual array of `struct v4l2_plane`, provided it exists. +/// +/// For the first situation, use the `planes_iter` and `planes_iter_mut` methods. They return an +/// iterator to an accessor to plane data that is identical whether the buffer is single or multi +/// planar (or course, for single-planar buffers the length of the iterator will be exactly 1). +/// +/// For the second situation, the `as_v4l2_planes` method returns an actual slice of `struct +/// v4l2_plane` with the plane information if the buffer is multi-planar (and an empty slice if the +/// it is single-planar). +#[derive(Clone)] +#[repr(C)] +pub struct V4l2Buffer { + buffer: bindings::v4l2_buffer, + planes: V4l2BufferPlanes, +} + +/// V4l2Buffer is safe to send across threads. `v4l2_buffer` is !Send & !Sync +/// because it contains a pointer, but we are making sure to use it safely here. +unsafe impl Send for V4l2Buffer {} +unsafe impl Sync for V4l2Buffer {} + +impl Debug for V4l2Buffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("V4l2Buffer") + .field("index", &self.index()) + .field("flags", &self.flags()) + .field("sequence", &self.sequence()) + .finish() + } +} + +impl V4l2Buffer { + pub fn new(queue: QueueType, index: u32, memory: MemoryType) -> Self { + Self { + buffer: bindings::v4l2_buffer { + index, + type_: queue as u32, + memory: memory as u32, + // Make sure that a multiplanar buffer always has at least one plane. + length: if queue.is_multiplanar() { + 1 + } else { + Default::default() + }, + ..Default::default() + }, + planes: Default::default(), + } + } + + pub fn index(&self) -> u32 { + self.buffer.index + } + + pub fn queue(&self) -> QueueType { + QueueType::n(self.buffer.type_).unwrap() + } + + pub fn memory(&self) -> MemoryType { + MemoryType::n(self.buffer.memory).unwrap() + } + + /// Returns the currently set flags for this buffer. + pub fn flags(&self) -> BufferFlags { + BufferFlags::from_bits_truncate(self.buffer.flags) + } + + /// Sets the flags of this buffer. + pub fn set_flags(&mut self, flags: BufferFlags) { + self.buffer.flags = flags.bits(); + } + + /// Add `flags` to the set of flags for this buffer. + pub fn add_flags(&mut self, flags: BufferFlags) { + self.set_flags(self.flags() | flags); + } + + /// Remove `flags` from the set of flags for this buffer. + pub fn clear_flags(&mut self, flags: BufferFlags) { + self.set_flags(self.flags() - flags); + } + + pub fn field(&self) -> BufferField { + BufferField::n(self.buffer.field).unwrap() + } + + pub fn set_field(&mut self, field: BufferField) { + self.buffer.field = field as u32; + } + + pub fn is_last(&self) -> bool { + self.flags().contains(BufferFlags::LAST) + } + + pub fn has_error(&self) -> bool { + self.flags().contains(BufferFlags::ERROR) + } + + pub fn timestamp(&self) -> bindings::timeval { + self.buffer.timestamp + } + + pub fn set_timestamp(&mut self, timestamp: bindings::timeval) { + self.buffer.timestamp = timestamp; + } + + pub fn sequence(&self) -> u32 { + self.buffer.sequence + } + + pub fn set_sequence(&mut self, sequence: u32) { + self.buffer.sequence = sequence; + } + + pub fn num_planes(&self) -> usize { + if self.queue().is_multiplanar() { + self.buffer.length as usize + } else { + 1 + } + } + + /// Sets the number of planes for this buffer to `num_planes`, which must be between `1` and + /// `VIDEO_MAX_PLANES`. + /// + /// This method only makes sense for multi-planar buffers. For single-planar buffers, any + /// `num_planes` value different from `1` will return an error. + pub fn set_num_planes(&mut self, num_planes: usize) -> Result<(), V4l2BufferResizePlanesError> { + match (num_planes, self.queue().is_multiplanar()) { + (0, _) => Err(V4l2BufferResizePlanesError::ZeroPlanesRequested), + (n, _) if n > bindings::VIDEO_MAX_PLANES as usize => { + Err(V4l2BufferResizePlanesError::TooManyPlanes) + } + (1, false) => Ok(()), + (_, false) => Err(V4l2BufferResizePlanesError::SinglePlanar), + (num_planes, true) => { + // If we are sizing down, clear the planes we are removing. + for plane in &mut self.planes[num_planes..self.buffer.length as usize] { + *plane = Default::default(); + } + self.buffer.length = num_planes as u32; + Ok(()) + } + } + } + + /// Returns the first plane of the buffer. This method is guaranteed to + /// succeed because every buffer has at least one plane. + pub fn get_first_plane(&self) -> V4l2PlaneAccessor<'_> { + self.planes_iter().next().unwrap() + } + + /// Returns the first plane of the buffer. This method is guaranteed to + /// succeed because every buffer has at least one plane. + pub fn get_first_plane_mut(&mut self) -> V4l2PlaneMutAccessor<'_> { + self.planes_iter_mut().next().unwrap() + } + + /// Returns a non-mutable reference to the internal `v4l2_buffer`. + /// + /// The returned value is not suitable for passing to C functions or ioctls (which anyway + /// require a mutable pointer), but can be useful to construct other values. + /// + /// In particular, if the buffer is multi-planar, then the `planes` pointer will be invalid. + /// Dereferencing it would require an `unsafe` block anyway. + /// + /// If you need to access the `v4l2_planes` of this buffer, use `as_v4l2_planes`. + /// + /// If you need to pass the `v4l2_buffer` to a C function or ioctl and need a valid `planes` + /// pointer, use `as_mut_ptr` and read the warning in its documentation. + pub fn as_v4l2_buffer(&self) -> &bindings::v4l2_buffer { + &self.buffer + } + + /// Returns a slice of this buffer's `v4l2_plane`s, if the buffer is multi-planar. + /// + /// If it is single-planar, an empty slice is returned. + /// + /// This method only exists for the rare case when one needs to access the original plane data. + /// For this reason there is no `v4l2_planes_mut` - use of the `planes_iter*_mut` methods + /// instead if you need to modify plane information. + pub fn as_v4l2_planes(&self) -> &[bindings::v4l2_plane] { + let planes_upper = if self.queue().is_multiplanar() { + self.buffer.length as usize + } else { + 0 + }; + + &self.planes[0..planes_upper] + } + + /// Returns a pointer to the internal `v4l2_buffer`. + /// + /// If this buffer is multi-planar then the `planes` pointer will be updated so the returned + /// data is valid if passed to a C function or an ioctl. + /// + /// Beware that as a consequence the returned pointer is only valid as long as the `V4l2Buffer` + /// is not moved anywhere. + /// + /// Also, any unsafe code called on this pointer must maintain the invariants listed in + /// [`V4l2Buffer`]'s documentation. + /// + /// Use with extreme caution. + pub fn as_mut_ptr(&mut self) -> *mut bindings::v4l2_buffer { + if self.queue().is_multiplanar() && self.buffer.length > 0 { + self.buffer.m.planes = self.planes.as_mut_ptr(); + } + + &mut self.buffer as *mut _ + } + + /// Returns planar information in a way that is consistent between single-planar and + /// multi-planar buffers. + pub fn planes_iter(&self) -> impl Iterator> { + let multiplanar = self.queue().is_multiplanar(); + let planes_iter = self.as_v4l2_planes().iter(); + + // In order to return a consistent type for both single-planar and multi-planar buffers, + // we chain the single-planar iterator to the multi-planar one. If the buffer is + // single-planar, then the multi-planar iterator will be empty. If the buffer is + // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. + std::iter::once(V4l2PlaneAccessor::new_single_planar(&self.buffer)) + .chain(planes_iter.map(V4l2PlaneAccessor::new_multi_planar)) + .skip(if multiplanar { 1 } else { 0 }) + } + + /// Returns planar information in a way that is consistent between single-planar and + /// multi-planar buffers. + pub fn planes_iter_mut(&mut self) -> impl Iterator> { + let multiplanar = self.queue().is_multiplanar(); + let planes_upper = if multiplanar { + self.buffer.length as usize + } else { + 0 + }; + let planes_iter = self.planes[0..planes_upper].iter_mut(); + + // In order to return a consistent type for both single-planar and multi-planar buffers, + // we chain the single-planar iterator to the multi-planar one. If the buffer is + // single-planar, then the multi-planar iterator will be empty. If the buffer is + // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. + std::iter::once(V4l2PlaneMutAccessor::new_single_planar(&mut self.buffer)) + .chain(planes_iter.map(V4l2PlaneMutAccessor::new_multi_planar)) + .skip(if multiplanar { 1 } else { 0 }) + } + + /// Build a plane iterator including the memory backings for memory type `M`. + /// + /// # Safety + /// + /// The caller must be sure that the buffer's memory type is indeed `M`. + unsafe fn planes_iter_with_backing( + &self, + ) -> impl Iterator> { + let is_multiplanar = self.queue().is_multiplanar(); + let planes_length = if is_multiplanar { + self.buffer.length as usize + } else { + 0 + }; + let planes = &self.planes[0..planes_length]; + // In order to return a consistent type for both single-planar and multi-planar buffers, + // we chain the single-planar iterator to the multi-planar one. If the buffer is + // single-planar, then the multi-planar iterator will be empty. If the buffer is + // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. + std::iter::once(V4l2PlaneAccessorWithRawBacking::new_single_planar( + &self.buffer, + )) + .chain( + planes + .iter() + .map(|p| V4l2PlaneAccessorWithRawBacking::new_multi_planar(p)), + ) + .skip(if self.queue().is_multiplanar() { 1 } else { 0 }) + } + + pub fn planes_with_backing_iter( + &self, + ) -> V4l2PlanesWithBacking< + '_, + impl Iterator>, + impl Iterator>, + impl Iterator>, + > { + match self.memory() { + MemoryType::Mmap => { + V4l2PlanesWithBacking::Mmap(unsafe { self.planes_iter_with_backing() }) + } + MemoryType::UserPtr => { + V4l2PlanesWithBacking::UserPtr(unsafe { self.planes_iter_with_backing() }) + } + MemoryType::DmaBuf => { + V4l2PlanesWithBacking::DmaBuf(unsafe { self.planes_iter_with_backing() }) + } + MemoryType::Overlay => V4l2PlanesWithBacking::Overlay, + } + } + + /// Build a mutable plane iterator including the memory backings for memory type `M`. + /// + /// # Safety + /// + /// The caller must be sure that the buffer's memory type is indeed `M`. + unsafe fn planes_iter_with_backing_mut( + &mut self, + ) -> impl Iterator> { + let is_multiplanar = self.queue().is_multiplanar(); + let planes_length = if is_multiplanar { + self.buffer.length as usize + } else { + 0 + }; + let planes = &mut self.planes[0..planes_length]; + + // In order to return a consistent type for both single-planar and multi-planar buffers, + // we chain the single-planar iterator to the multi-planar one. If the buffer is + // single-planar, then the multi-planar iterator will be empty. If the buffer is + // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. + std::iter::once(V4l2PlaneMutAccessorWithRawBacking::new_single_planar( + &mut self.buffer, + )) + .chain( + planes + .iter_mut() + .map(|p| V4l2PlaneMutAccessorWithRawBacking::new_multi_planar(p)), + ) + .skip(if is_multiplanar { 1 } else { 0 }) + } + + pub fn planes_with_backing_iter_mut( + &mut self, + ) -> V4l2PlanesWithBackingMut< + '_, + impl Iterator>, + impl Iterator>, + impl Iterator>, + > { + match self.memory() { + MemoryType::Mmap => { + V4l2PlanesWithBackingMut::Mmap(unsafe { self.planes_iter_with_backing_mut() }) + } + MemoryType::UserPtr => { + V4l2PlanesWithBackingMut::UserPtr(unsafe { self.planes_iter_with_backing_mut() }) + } + MemoryType::DmaBuf => { + V4l2PlanesWithBackingMut::DmaBuf(unsafe { self.planes_iter_with_backing_mut() }) + } + MemoryType::Overlay => V4l2PlanesWithBackingMut::Overlay, + } + } +} + +/// Accessor to a buffer's plane information. +/// +/// This is just a set of references, that are set to point to the right location depending on +/// whether the buffer is single or multi-planar. +pub struct V4l2PlaneAccessor<'a> { + pub bytesused: &'a u32, + pub length: &'a u32, + pub data_offset: Option<&'a u32>, +} + +impl<'a> V4l2PlaneAccessor<'a> { + fn new_single_planar(buffer: &'a bindings::v4l2_buffer) -> Self { + Self { + bytesused: &buffer.bytesused, + length: &buffer.length, + data_offset: None, + } + } + + fn new_multi_planar(plane: &'a bindings::v4l2_plane) -> Self { + Self { + bytesused: &plane.bytesused, + length: &plane.length, + data_offset: Some(&plane.data_offset), + } + } +} + +/// Mutable accessor to a buffer's plane information. +/// +/// This is just a set of references, that are set to point to the right location depending on +/// whether the buffer is single or multi-planar. +pub struct V4l2PlaneMutAccessor<'a> { + pub bytesused: &'a mut u32, + pub length: &'a mut u32, + pub data_offset: Option<&'a mut u32>, +} + +impl<'a> V4l2PlaneMutAccessor<'a> { + fn new_single_planar(buffer: &'a mut bindings::v4l2_buffer) -> Self { + Self { + bytesused: &mut buffer.bytesused, + length: &mut buffer.length, + data_offset: None, + } + } + + fn new_multi_planar(plane: &'a mut bindings::v4l2_plane) -> Self { + Self { + bytesused: &mut plane.bytesused, + length: &mut plane.length, + data_offset: Some(&mut plane.data_offset), + } + } +} + +pub struct V4l2PlaneAccessorWithRawBacking<'a, M: Memory> { + data: V4l2PlaneAccessor<'a>, + backing: &'a M::RawBacking, +} + +impl<'a, M: Memory> Deref for V4l2PlaneAccessorWithRawBacking<'a, M> { + type Target = V4l2PlaneAccessor<'a>; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<'a, M: Memory> V4l2PlaneAccessorWithRawBacking<'a, M> { + /// Create a new plane accessor for memory type `M`. + /// + /// # Safety + /// + /// `v4l2_buffer` must be of a single-planar type and use memory type `M`. + pub unsafe fn new_single_planar(buffer: &'a bindings::v4l2_buffer) -> Self { + Self { + data: V4l2PlaneAccessor::new_single_planar(buffer), + backing: M::get_single_planar_buffer_backing(&buffer.m), + } + } + + /// Create a new plane accessor for memory type `M`. + /// + /// # Safety + /// + /// `v4l2_plane` must come from a multi-planar buffer using memory type `M`. + pub unsafe fn new_multi_planar(plane: &'a bindings::v4l2_plane) -> Self { + Self { + data: V4l2PlaneAccessor::new_multi_planar(plane), + backing: M::get_plane_buffer_backing(&plane.m), + } + } +} + +impl<'a> V4l2PlaneAccessorWithRawBacking<'a, Mmap> { + pub fn mem_offset(&self) -> ::RawBacking { + *self.backing + } +} + +impl<'a> V4l2PlaneAccessorWithRawBacking<'a, UserPtr> { + pub fn userptr(&self) -> ::RawBacking { + *self.backing + } +} + +impl<'a> V4l2PlaneAccessorWithRawBacking<'a, DmaBuf> { + pub fn fd(&self) -> ::RawBacking { + *self.backing + } +} + +pub enum V4l2PlanesWithBacking< + 'a, + M: Iterator>, + U: Iterator>, + D: Iterator>, +> { + Mmap(M), + UserPtr(U), + DmaBuf(D), + Overlay, +} + +pub struct V4l2PlaneMutAccessorWithRawBacking<'a, M: Memory> { + data: V4l2PlaneMutAccessor<'a>, + backing: &'a mut M::RawBacking, +} + +impl<'a, M: Memory> Deref for V4l2PlaneMutAccessorWithRawBacking<'a, M> { + type Target = V4l2PlaneMutAccessor<'a>; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<'a, M: Memory> DerefMut for V4l2PlaneMutAccessorWithRawBacking<'a, M> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl<'a, M: Memory> V4l2PlaneMutAccessorWithRawBacking<'a, M> { + /// Create a new plane accessor for memory type `M`. + /// + /// # Safety + /// + /// `v4l2_buffer` must be of a single-planar type and use memory type `M`. + pub unsafe fn new_single_planar(buffer: &'a mut bindings::v4l2_buffer) -> Self { + Self { + data: V4l2PlaneMutAccessor { + bytesused: &mut buffer.bytesused, + length: &mut buffer.length, + data_offset: None, + }, + backing: M::get_single_planar_buffer_backing_mut(&mut buffer.m), + } + } + + /// Create a new plane accessor for memory type `M`. + /// + /// # Safety + /// + /// `v4l2_plane` must come from a multi-planar buffer using memory type `M`. + pub unsafe fn new_multi_planar(plane: &'a mut bindings::v4l2_plane) -> Self { + Self { + data: V4l2PlaneMutAccessor { + bytesused: &mut plane.bytesused, + length: &mut plane.length, + data_offset: Some(&mut plane.data_offset), + }, + backing: M::get_plane_buffer_backing_mut(&mut plane.m), + } + } +} + +impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, Mmap> { + pub fn mem_offset(&self) -> ::RawBacking { + *self.backing + } + + pub fn set_mem_offset(&mut self, mem_offset: ::RawBacking) { + *self.backing = mem_offset; + } +} + +impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, UserPtr> { + pub fn userptr(&self) -> ::RawBacking { + *self.backing + } + + pub fn set_userptr(&mut self, userptr: ::RawBacking) { + *self.backing = userptr; + } +} + +impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, DmaBuf> { + pub fn fd(&self) -> ::RawBacking { + *self.backing + } + + pub fn set_fd(&mut self, fd: ::RawBacking) { + *self.backing = fd; + } +} + +pub enum V4l2PlanesWithBackingMut< + 'a, + M: Iterator>, + U: Iterator>, + D: Iterator>, +> { + Mmap(M), + UserPtr(U), + DmaBuf(D), + Overlay, +} + +#[derive(Debug, Error)] +pub enum V4l2BufferFromError { + #[error("unknown queue type {0}")] + UnknownQueueType(u32), + #[error("unknown memory type {0}")] + UnknownMemoryType(u32), + #[error("invalid number of planes {0}")] + InvalidNumberOfPlanes(u32), + #[error("plane {0} has bytesused field larger than its length ({1} > {2})")] + PlaneSizeOverflow(usize, u32, u32), + #[error("plane {0} has data_offset field larger or equal to its bytesused ({1} >= {2})")] + InvalidDataOffset(usize, u32, u32), +} + +impl TryFrom for V4l2Buffer { + type Error = V4l2BufferFromError; + + /// Do some consistency checks to ensure methods of `V4l2Buffer` that do an `unwrap` can never + /// fail. + fn try_from(buffer: UncheckedV4l2Buffer) -> Result { + let v4l2_buf = buffer.0; + let queue = QueueType::n(v4l2_buf.type_) + .ok_or(V4l2BufferFromError::UnknownQueueType(v4l2_buf.type_))?; + MemoryType::n(v4l2_buf.memory) + .ok_or(V4l2BufferFromError::UnknownMemoryType(v4l2_buf.memory))?; + + let v4l2_planes = buffer.1.unwrap_or_default(); + + // Validate plane information + if queue.is_multiplanar() { + if v4l2_buf.length >= bindings::VIDEO_MAX_PLANES { + return Err(V4l2BufferFromError::InvalidNumberOfPlanes(v4l2_buf.length)); + } + + for (i, plane) in v4l2_planes[0..v4l2_buf.length as usize].iter().enumerate() { + if plane.bytesused > plane.length { + return Err(V4l2BufferFromError::PlaneSizeOverflow( + i, + plane.bytesused, + plane.length, + )); + } + + let bytesused = if plane.bytesused != 0 { + plane.bytesused + } else { + plane.length + }; + + if plane.data_offset != 0 && plane.data_offset >= bytesused { + return Err(V4l2BufferFromError::InvalidDataOffset( + i, + plane.data_offset, + bytesused, + )); + } + } + } else if v4l2_buf.bytesused > v4l2_buf.length { + return Err(V4l2BufferFromError::PlaneSizeOverflow( + 0, + v4l2_buf.bytesused, + v4l2_buf.length, + )); + } + + Ok(Self { + buffer: v4l2_buf, + planes: v4l2_planes, + }) + } +} + +/// Representation of a validated multi-planar `struct v4l2_format`. It provides accessors returning proper +/// types instead of `u32`s. +#[derive(Clone)] +#[repr(transparent)] +pub struct V4l2MplaneFormat(bindings::v4l2_format); + +impl AsRef for V4l2MplaneFormat { + fn as_ref(&self) -> &bindings::v4l2_format { + &self.0 + } +} + +impl AsRef for V4l2MplaneFormat { + fn as_ref(&self) -> &bindings::v4l2_pix_format_mplane { + // SAFETY: safe because we verify that the format is pixel multiplanar at construction + // time. + unsafe { &self.0.fmt.pix_mp } + } +} + +#[derive(Debug, Error)] +pub enum V4l2MplaneFormatFromError { + #[error("format is not multi-planar")] + NotMultiPlanar, + #[error("invalid field type {0}")] + InvalidField(u32), + #[error("invalid colorspace {0}")] + InvalidColorSpace(u32), + #[error("invalid number of planes {0}")] + InvalidPlanesNumber(u8), + #[error("invalid YCbCr encoding {0}")] + InvalidYCbCr(u8), + #[error("invalid quantization {0}")] + InvalidQuantization(u8), + #[error("invalid Xfer func {0}")] + InvalidXferFunc(u8), +} + +/// Turn a `struct v4l2_format` into its validated version, returning an error if any of the fields +/// cannot be validated. +impl TryFrom for V4l2MplaneFormat { + type Error = V4l2MplaneFormatFromError; + + fn try_from(format: bindings::v4l2_format) -> Result { + if !matches!( + QueueType::n(format.type_), + Some(QueueType::VideoCaptureMplane) | Some(QueueType::VideoOutputMplane) + ) { + return Err(V4l2MplaneFormatFromError::NotMultiPlanar); + } + let pix_mp = unsafe { &format.fmt.pix_mp }; + + if pix_mp.num_planes == 0 || pix_mp.num_planes > bindings::VIDEO_MAX_PLANES as u8 { + return Err(V4l2MplaneFormatFromError::InvalidPlanesNumber( + pix_mp.num_planes, + )); + } + + let _ = BufferField::n(pix_mp.field) + .ok_or(V4l2MplaneFormatFromError::InvalidField(pix_mp.field))?; + let _ = Colorspace::n(pix_mp.colorspace).ok_or( + V4l2MplaneFormatFromError::InvalidColorSpace(pix_mp.colorspace), + )?; + let ycbcr_enc = unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc }; + let _ = YCbCrEncoding::n(ycbcr_enc as u32) + .ok_or(V4l2MplaneFormatFromError::InvalidYCbCr(ycbcr_enc)); + + let _ = Quantization::n(pix_mp.quantization as u32).ok_or( + V4l2MplaneFormatFromError::InvalidQuantization(pix_mp.quantization), + )?; + let _ = XferFunc::n(pix_mp.xfer_func as u32) + .ok_or(V4l2MplaneFormatFromError::InvalidXferFunc(pix_mp.xfer_func))?; + + Ok(Self(format)) + } +} + +/// Turn a `struct v4l2_pix_format_mplane` into its validated version, turning any field that can +/// not be validated into its default value. +impl From<(QueueDirection, bindings::v4l2_pix_format_mplane)> for V4l2MplaneFormat { + fn from((direction, mut pix_mp): (QueueDirection, bindings::v4l2_pix_format_mplane)) -> Self { + pix_mp.field = BufferField::n(pix_mp.field).unwrap_or_default() as u32; + pix_mp.colorspace = Colorspace::n(pix_mp.colorspace).unwrap_or_default() as u32; + let ycbcr_enc = unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc }; + pix_mp.__bindgen_anon_1.ycbcr_enc = + YCbCrEncoding::n(ycbcr_enc as u32).unwrap_or_default() as u8; + pix_mp.quantization = Quantization::n(pix_mp.quantization as u32).unwrap_or_default() as u8; + pix_mp.xfer_func = XferFunc::n(pix_mp.xfer_func as u32).unwrap_or_default() as u8; + + Self(bindings::v4l2_format { + type_: QueueType::from_dir_and_class(direction, crate::QueueClass::VideoMplane) as u32, + fmt: bindings::v4l2_format__bindgen_ty_1 { pix_mp }, + }) + } +} + +impl V4l2MplaneFormat { + /// Returns the direction of the MPLANE queue this format applies to. + pub fn direction(&self) -> QueueDirection { + QueueType::n(self.0.type_).unwrap().direction() + } + + pub fn size(&self) -> (u32, u32) { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + (pix_mp.width, pix_mp.height) + } + + pub fn pixelformat(&self) -> PixelFormat { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + PixelFormat::from_u32(pix_mp.pixelformat) + } + + pub fn field(&self) -> BufferField { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + // Safe because we checked the boundaries at construction time. + BufferField::n(pix_mp.field).unwrap() + } + + pub fn colorspace(&self) -> Colorspace { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + // Safe because we checked the boundaries at construction time. + Colorspace::n(pix_mp.colorspace).unwrap() + } + + pub fn ycbcr_enc(&self) -> YCbCrEncoding { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + // Safe because we checked the boundaries at construction time. + YCbCrEncoding::n(unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc as u32 }).unwrap() + } + + pub fn quantization(&self) -> Quantization { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + Quantization::n(pix_mp.quantization as u32).unwrap() + } + + pub fn xfer_func(&self) -> XferFunc { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + XferFunc::n(pix_mp.xfer_func as u32).unwrap() + } + + pub fn planes(&self) -> &[bindings::v4l2_plane_pix_format] { + let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); + &pix_mp.plane_fmt[0..pix_mp.num_planes.min(bindings::VIDEO_MAX_PLANES as u8) as usize] + } +} + +#[cfg(test)] +mod tests { + use crate::{bindings, QueueType}; + + use super::UncheckedV4l2Buffer; + + #[test] + fn test_string_from_cstr() { + use super::string_from_cstr; + + // Nul-terminated slice. + assert_eq!(string_from_cstr(b"Hello\0"), Ok(String::from("Hello"))); + + // Slice with nul in the middle and not nul-terminated. + assert_eq!(string_from_cstr(b"Hi\0lo"), Ok(String::from("Hi"))); + + // Slice with nul in the middle and nul-terminated. + assert_eq!(string_from_cstr(b"Hi\0lo\0"), Ok(String::from("Hi"))); + + // Slice starting with nul. + assert_eq!(string_from_cstr(b"\0ello"), Ok(String::from(""))); + + // Slice without nul. + match string_from_cstr(b"Hello") { + Err(_) => {} + Ok(_) => panic!(), + }; + + // Empty slice. + match string_from_cstr(b"") { + Err(_) => {} + Ok(_) => panic!(), + }; + } + + #[test] + fn test_unchecked_v4l2_buffer() { + // Single-planar. + let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(QueueType::VideoCapture, Some(2)); + assert_eq!(v4l2_buf.0.type_, QueueType::VideoCapture as u32); + assert_eq!(v4l2_buf.0.index, 2); + assert_eq!(v4l2_buf.0.length, 0); + assert!(v4l2_buf.1.is_none()); + assert_eq!(unsafe { v4l2_buf.as_mut().m.planes }, std::ptr::null_mut()); + + // Multi-planar. + let mut v4l2_buf = + UncheckedV4l2Buffer::new_for_querybuf(QueueType::VideoCaptureMplane, None); + assert_eq!(v4l2_buf.0.type_, QueueType::VideoCaptureMplane as u32); + assert_eq!(v4l2_buf.0.index, 0); + assert_eq!(v4l2_buf.0.length, bindings::VIDEO_MAX_PLANES); + assert!(v4l2_buf.1.is_some()); + let planes_ptr = v4l2_buf.1.as_mut().map(|p| p.as_mut_ptr()).unwrap(); + let v4l2_buf_ref = v4l2_buf.as_mut(); + assert_eq!(unsafe { v4l2_buf_ref.m.planes }, planes_ptr); + } +} diff --git a/libs/v4l2r/src/ioctl/dqbuf.rs b/libs/v4l2r/src/ioctl/dqbuf.rs new file mode 100644 index 00000000..30ef05c3 --- /dev/null +++ b/libs/v4l2r/src/ioctl/dqbuf.rs @@ -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 for DqBufIoctlError { + fn from(error: Errno) -> Self { + match error { + Errno::EAGAIN => Self::NotReady, + Errno::EPIPE => Self::Eos, + error => Self::Other(error), + } + } +} + +impl From for Errno { + fn from(err: DqBufIoctlError) -> Self { + match err { + DqBufIoctlError::Eos => Errno::EPIPE, + DqBufIoctlError::NotReady => Errno::EAGAIN, + DqBufIoctlError::Other(e) => e, + } + } +} + +pub type DqBufError = IoctlConvertError; +pub type DqBufResult = IoctlConvertResult; + +/// Safe wrapper around the `VIDIOC_DQBUF` ioctl. +pub fn dqbuf(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult +where + O: TryFrom, + 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), + ) +} diff --git a/libs/v4l2r/src/ioctl/enum_fmt.rs b/libs/v4l2r/src/ioctl/enum_fmt.rs new file mode 100644 index 00000000..f659306d --- /dev/null +++ b/libs/v4l2r/src/ioctl/enum_fmt.rs @@ -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 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 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 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>( + fd: &impl AsRawFd, + queue: QueueType, + index: u32, +) -> Result { + 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 { + 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 + } + } + } +} diff --git a/libs/v4l2r/src/ioctl/expbuf.rs b/libs/v4l2r/src/ioctl/expbuf.rs new file mode 100644 index 00000000..3da4429c --- /dev/null +++ b/libs/v4l2r/src/ioctl/expbuf.rs @@ -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 for Errno { + fn from(err: ExpbufError) -> Self { + match err { + ExpbufError::IoctlError(e) => e, + } + } +} + +/// Safe wrapper around the `VIDIOC_EXPBUF` ioctl. +pub fn expbuf( + fd: &impl AsRawFd, + queue: QueueType, + index: usize, + plane: usize, + flags: ExpbufFlags, +) -> Result { + 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) }) +} diff --git a/libs/v4l2r/src/ioctl/frameintervals.rs b/libs/v4l2r/src/ioctl/frameintervals.rs new file mode 100644 index 00000000..6f52e556 --- /dev/null +++ b/libs/v4l2r/src/ioctl/frameintervals.rs @@ -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> { + 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 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>( + fd: &impl AsRawFd, + index: u32, + pixel_format: PixelFormat, + width: u32, + height: u32, +) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/framesizes.rs b/libs/v4l2r/src/ioctl/framesizes.rs new file mode 100644 index 00000000..6eb41ec9 --- /dev/null +++ b/libs/v4l2r/src/ioctl/framesizes.rs @@ -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> { + 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 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>( + fd: &impl AsRawFd, + index: u32, + pixel_format: PixelFormat, +) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/g_dv_timings.rs b/libs/v4l2r/src/ioctl/g_dv_timings.rs new file mode 100644 index 00000000..62180b83 --- /dev/null +++ b/libs/v4l2r/src/ioctl/g_dv_timings.rs @@ -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 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, O: From>( + fd: &impl AsRawFd, + timings: I, +) -> Result { + 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>(fd: &impl AsRawFd) -> Result { + 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 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>( + fd: &impl AsRawFd, + index: u32, +) -> Result { + 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 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>( + fd: &impl AsRawFd, +) -> Result { + 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 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>( + fd: &impl AsRawFd, +) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/g_fmt.rs b/libs/v4l2r/src/ioctl/g_fmt.rs new file mode 100644 index 00000000..2dd8d979 --- /dev/null +++ b/libs/v4l2r/src/ioctl/g_fmt.rs @@ -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 { + 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 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>(fd: &impl AsRawFd, queue: QueueType) -> Result { + 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 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, O: TryFrom>( + fd: &mut impl AsRawFd, + format: I, +) -> Result { + 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 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, O: TryFrom>( + fd: &impl AsRawFd, + format: I, +) -> Result { + 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::::try_into((QueueType::VideoCapture, &mplane)).err(), + Some(FormatConversionError::TooManyPlanes(3)) + ); + } +} diff --git a/libs/v4l2r/src/ioctl/g_parm.rs b/libs/v4l2r/src/ioctl/g_parm.rs new file mode 100644 index 00000000..442243a4 --- /dev/null +++ b/libs/v4l2r/src/ioctl/g_parm.rs @@ -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 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>( + fd: &impl AsRawFd, + queue: QueueType, +) -> Result { + 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, O: From>( + fd: &impl AsRawFd, + parm: I, +) -> Result { + 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>(fd: &impl AsRawFd) -> Result { + 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 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>(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 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>(fd: &impl AsRawFd, index: u32) -> Result { + 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>(fd: &impl AsRawFd) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/g_selection.rs b/libs/v4l2r/src/ioctl/g_selection.rs new file mode 100644 index 00000000..c3a8a94e --- /dev/null +++ b/libs/v4l2r/src/ioctl/g_selection.rs @@ -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 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>( + fd: &impl AsRawFd, + selection: SelectionType, + target: SelectionTarget, +) -> Result { + 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 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, RO: From>( + fd: &impl AsRawFd, + selection: SelectionType, + target: SelectionTarget, + rect: RI, + flags: SelectionFlags, +) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/mmap.rs b/libs/v4l2r/src/ioctl/mmap.rs new file mode 100644 index 00000000..dd17ea59 --- /dev/null +++ b/libs/v4l2r/src/ioctl/mmap.rs @@ -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 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 { + 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, + }) +} diff --git a/libs/v4l2r/src/ioctl/qbuf.rs b/libs/v4l2r/src/ioctl/qbuf.rs new file mode 100644 index 00000000..06752988 --- /dev/null +++ b/libs/v4l2r/src/ioctl/qbuf.rs @@ -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 for QBufIoctlError { + fn from(errno: Errno) -> Self { + Self::Other(errno) + } +} + +impl From 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(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 for good measure. +#[derive(Debug)] +pub struct QBuffer { + index: u32, + queue: QueueType, + pub flags: BufferFlags, + pub field: u32, + pub sequence: u32, + pub timestamp: TimeVal, + pub planes: Vec, + pub _h: std::marker::PhantomData, +} + +impl QBuffer { + 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 QBuffer { + pub fn set_timestamp(mut self, sec: time_t, usec: suseconds_t) -> Self { + self.timestamp = TimeVal::new(sec, usec); + self + } +} + +impl From> for UncheckedV4l2Buffer { + fn from(qbuf: QBuffer) -> 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 = IoctlConvertError; +pub type QBufResult = IoctlConvertResult; + +/// 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(fd: &impl AsRawFd, buffer: I) -> QBufResult +where + I: Into, + O: TryFrom, + 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(fd: &impl AsRawFd, buffer: I) -> QBufResult +where + I: Into, + O: TryFrom, + 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), + ) +} diff --git a/libs/v4l2r/src/ioctl/querybuf.rs b/libs/v4l2r/src/ioctl/querybuf.rs new file mode 100644 index 00000000..135749d7 --- /dev/null +++ b/libs/v4l2r/src/ioctl/querybuf.rs @@ -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, +} + +impl TryFrom for QueryBuffer { + type Error = Infallible; + + fn try_from(buffer: UncheckedV4l2Buffer) -> Result { + 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 for QueryBufIoctlError { + fn from(err: Errno) -> Self { + match err { + Errno::EINVAL => QueryBufIoctlError::InvalidInput, + e => QueryBufIoctlError::Other(e), + } + } +} + +impl From for Errno { + fn from(err: QueryBufIoctlError) -> Self { + match err { + QueryBufIoctlError::InvalidInput => Errno::EINVAL, + QueryBufIoctlError::Other(e) => e, + } + } +} + +pub type QueryBufError = IoctlConvertError; +pub type QueryBufResult = IoctlConvertResult; + +/// Safe wrapper around the `VIDIOC_QUERYBUF` ioctl. +pub fn querybuf(fd: &impl AsRawFd, queue: QueueType, index: usize) -> QueryBufResult +where + O: TryFrom, + 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), + ) +} diff --git a/libs/v4l2r/src/ioctl/querycap.rs b/libs/v4l2r/src/ioctl/querycap.rs new file mode 100644 index 00000000..7dcf76d1 --- /dev/null +++ b/libs/v4l2r/src/ioctl/querycap.rs @@ -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 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, +} + +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 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 for Errno { + fn from(err: QueryCapError) -> Self { + match err { + QueryCapError::IoctlError(e) => e, + } + } +} + +/// Safe wrapper around the `VIDIOC_QUERYCAP` ioctl. +pub fn querycap>(fd: &impl AsRawFd) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/reqbufs.rs b/libs/v4l2r/src/ioctl/reqbufs.rs new file mode 100644 index 00000000..e874b131 --- /dev/null +++ b/libs/v4l2r/src/ioctl/reqbufs.rs @@ -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 for () { + fn from(_reqbufs: v4l2_requestbuffers) -> Self {} +} + +/// In case we are just interested in the number of buffers that `reqbufs` +/// created. +impl From for usize { + fn from(reqbufs: v4l2_requestbuffers) -> Self { + reqbufs.count as usize + } +} + +/// If we just want to query the buffer capabilities. +impl From 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 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 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>( + fd: &impl AsRawFd, + queue: QueueType, + memory: MemoryType, + count: u32, + flags: MemoryConsistency, +) -> Result { + 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 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, O: From>( + fd: &impl AsRawFd, + count: u32, + memory: MemoryType, + format: F, +) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/streamon.rs b/libs/v4l2r/src/ioctl/streamon.rs new file mode 100644 index 00000000..26413bd3 --- /dev/null +++ b/libs/v4l2r/src/ioctl/streamon.rs @@ -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 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 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)), + } +} diff --git a/libs/v4l2r/src/ioctl/subscribe_event.rs b/libs/v4l2r/src/ioctl/subscribe_event.rs new file mode 100644 index 00000000..c0b62af2 --- /dev/null +++ b/libs/v4l2r/src/ioctl/subscribe_event.rs @@ -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 { + 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 for Event { + type Error = EventConversionError; + + fn try_from(value: v4l2_event) -> Result { + 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 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 for DqEventError { + fn from(error: Errno) -> Self { + match error { + Errno::ENOENT => Self::NotReady, + error => Self::IoctlError(error), + } + } +} + +impl From for Errno { + fn from(err: DqEventError) -> Self { + match err { + DqEventError::NotReady => Errno::ENOENT, + DqEventError::EventConversionError => Errno::EINVAL, + DqEventError::IoctlError(e) => e, + } + } +} + +pub fn dqevent>(fd: &impl AsRawFd) -> Result { + 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)), + } +} diff --git a/libs/v4l2r/src/lib.rs b/libs/v4l2r/src/lib.rs new file mode 100644 index 00000000..21c4f738 --- /dev/null +++ b/libs/v4l2r/src/lib.rs @@ -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 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 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 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::(); + 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, +} + +#[derive(Debug, Error, PartialEq)] +pub enum FormatConversionError { + #[error("too many planes ({0}) specified,")] + TooManyPlanes(usize), + #[error("invalid buffer type requested")] + InvalidBufferType(u32), +} + +impl TryFrom for Format { + type Error = FormatConversionError; + + fn try_from(fmt: bindings::v4l2_format) -> std::result::Result { + 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> 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 for Rect { + fn from(rect: bindings::v4l2_rect) -> Self { + Rect { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + } + } +} + +impl From for Rect { + fn from(selection: bindings::v4l2_selection) -> Self { + Self::from(selection.r) + } +} + +impl From 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, +} diff --git a/libs/v4l2r/src/memory.rs b/libs/v4l2r/src/memory.rs new file mode 100644 index 00000000..de156574 --- /dev/null +++ b/libs/v4l2r/src/memory.rs @@ -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` 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(device: &D, plane_info: &QueryBufPlane) -> Option; +} + +/// 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 + 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 BufferHandles for Q +where + P: PlaneHandle, + Q: Send + Debug + 'static + Deref, +{ + 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 PrimitiveBufferHandles for Q +where + P: PlaneHandle, + Q: Send + Debug + 'static + Deref, +{ + 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 + ); + } +} diff --git a/libs/v4l2r/src/memory/dmabuf.rs b/libs/v4l2r/src/memory/dmabuf.rs new file mode 100644 index 00000000..315b5c26 --- /dev/null +++ b/libs/v4l2r/src/memory/dmabuf.rs @@ -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 = Vec>; + +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(pub T); + +impl From for DmaBufHandle { + fn from(dmabuf: T) -> Self { + DmaBufHandle(dmabuf) + } +} + +impl PlaneHandle for DmaBufHandle { + 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 DmaBufHandle { + pub fn map(&self) -> Result { + let len = self.0.len(); + + ioctl::mmap(&self.0, 0, len as u32) + } +} diff --git a/libs/v4l2r/src/memory/mmap.rs b/libs/v4l2r/src/memory/mmap.rs new file mode 100644 index 00000000..0a24f1e4 --- /dev/null +++ b/libs/v4l2r/src/memory/mmap.rs @@ -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(device: &D, plane_info: &QueryBufPlane) -> Option { + ioctl::mmap(device, plane_info.mem_offset, plane_info.length).ok() + } +} diff --git a/libs/v4l2r/src/memory/userptr.rs b/libs/v4l2r/src/memory/userptr.rs new file mode 100644 index 00000000..d0958dc9 --- /dev/null +++ b/libs/v4l2r/src/memory/userptr.rs @@ -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 + Debug + Send + 'static>(pub T); + +impl + Debug + Send + Clone> Clone for UserPtrHandle { + fn clone(&self) -> Self { + UserPtrHandle(self.0.clone()) + } +} + +impl + Debug + Send + 'static> AsRef<[u8]> for UserPtrHandle { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl + Debug + Send> From for UserPtrHandle { + fn from(buffer: T) -> Self { + UserPtrHandle(buffer) + } +} + +impl + Debug + Send + 'static> PlaneHandle for UserPtrHandle { + 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; + } +} diff --git a/libs/v4l2r/v4l2r_wrapper.h b/libs/v4l2r/v4l2r_wrapper.h new file mode 100644 index 00000000..4fbf738f --- /dev/null +++ b/libs/v4l2r/v4l2r_wrapper.h @@ -0,0 +1,10 @@ +#ifdef __ANDROID__ +#include +#include +#include +#endif + +#include + +#define MARK_FIX_753(name) const unsigned long int Fix753_##name = name; +#include "fix753.h"