mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
chore: vendor trimmed v4l2r capture crate
This commit is contained in:
1
libs/v4l2r/.cargo-ok
Normal file
1
libs/v4l2r/.cargo-ok
Normal file
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
6
libs/v4l2r/.cargo_vcs_info.json
Normal file
6
libs/v4l2r/.cargo_vcs_info.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "7b441383125ae583017a1c18b3fc9ec6c88ddbe8"
|
||||
},
|
||||
"path_in_vcs": "lib"
|
||||
}
|
||||
52
libs/v4l2r/Android.bp
Normal file
52
libs/v4l2r/Android.bp
Normal file
@@ -0,0 +1,52 @@
|
||||
// This file is generated by cargo_embargo.
|
||||
// Do not modify this file because the changes will be overridden on upgrade.
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["external_rust_crates_v4l2r_license"],
|
||||
}
|
||||
|
||||
rust_library {
|
||||
name: "libv4l2r",
|
||||
crate_name: "v4l2r",
|
||||
cargo_env_compat: true,
|
||||
cargo_pkg_version: "0.0.7",
|
||||
crate_root: "src/lib.rs",
|
||||
edition: "2021",
|
||||
rustlibs: [
|
||||
"libbitflags",
|
||||
"liblog_rust",
|
||||
"libnix",
|
||||
"libthiserror",
|
||||
],
|
||||
proc_macros: ["libenumn"],
|
||||
apex_available: [
|
||||
"//apex_available:platform",
|
||||
"//apex_available:anyapex",
|
||||
],
|
||||
product_available: true,
|
||||
vendor_available: true,
|
||||
// Bindgen-generated bindings of our local videodev2.h.
|
||||
srcs: [":libv4l2r_bindgen"],
|
||||
|
||||
}
|
||||
|
||||
rust_test {
|
||||
name: "v4l2r_test_src_lib",
|
||||
crate_name: "v4l2r",
|
||||
cargo_env_compat: true,
|
||||
cargo_pkg_version: "0.0.7",
|
||||
crate_root: "src/lib.rs",
|
||||
test_suites: ["general-tests"],
|
||||
auto_gen_config: true,
|
||||
edition: "2021",
|
||||
rustlibs: [
|
||||
"libbitflags",
|
||||
"liblog_rust",
|
||||
"libnix",
|
||||
"libthiserror",
|
||||
],
|
||||
proc_macros: ["libenumn"],
|
||||
// Bindgen-generated bindings of our local videodev2.h.
|
||||
srcs: [":libv4l2r_bindgen"],
|
||||
|
||||
}
|
||||
65
libs/v4l2r/Cargo.toml
Normal file
65
libs/v4l2r/Cargo.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "v4l2r"
|
||||
version = "0.0.7"
|
||||
authors = ["Alexandre Courbot <gnurou@gmail.com>"]
|
||||
build = "build.rs"
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Safe and flexible abstraction over V4L2"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"v4l2",
|
||||
"video",
|
||||
"linux",
|
||||
]
|
||||
categories = ["os"]
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/Gnurou/v4l2r"
|
||||
|
||||
[features]
|
||||
arch32 = []
|
||||
arch64 = []
|
||||
|
||||
[lib]
|
||||
name = "v4l2r"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.bitflags]
|
||||
version = "2.4"
|
||||
|
||||
[dependencies.enumn]
|
||||
version = "0.1.6"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.14"
|
||||
|
||||
[dependencies.nix]
|
||||
version = "0.28"
|
||||
features = [
|
||||
"ioctl",
|
||||
"mman",
|
||||
"poll",
|
||||
"fs",
|
||||
"event",
|
||||
]
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1.0"
|
||||
|
||||
[build-dependencies.bindgen]
|
||||
version = "0.70.1"
|
||||
28
libs/v4l2r/Cargo.toml.orig
generated
Normal file
28
libs/v4l2r/Cargo.toml.orig
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "v4l2r"
|
||||
version = "0.0.7"
|
||||
authors = ["Alexandre Courbot <gnurou@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "Safe and flexible abstraction over V4L2"
|
||||
repository = "https://github.com/Gnurou/v4l2r"
|
||||
categories = ["os"]
|
||||
keywords = ["v4l2", "video", "linux"]
|
||||
|
||||
license-file.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[features]
|
||||
# Generate the bindings for 64-bit even if the host is 32-bit.
|
||||
arch64 = []
|
||||
# Generate the bindings for 32-bit even if the host is 64-bit.
|
||||
arch32 = []
|
||||
|
||||
[dependencies]
|
||||
nix = { version = "0.28", features = ["ioctl", "mman", "poll", "fs", "event"] }
|
||||
bitflags = "2.4"
|
||||
thiserror = "1.0"
|
||||
log = "0.4.14"
|
||||
enumn = "0.1.6"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.70.1"
|
||||
23
libs/v4l2r/LICENSE
Normal file
23
libs/v4l2r/LICENSE
Normal file
@@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
24
libs/v4l2r/README.md
Normal file
24
libs/v4l2r/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Rust bindings for V4L2
|
||||
|
||||
This is a vendored, One-KVM-specific subset of `v4l2r`.
|
||||
|
||||
It keeps only the pieces needed for video capture:
|
||||
|
||||
- generated Linux V4L2 bindings,
|
||||
- safe low-level ioctl wrappers used by capture and device probing,
|
||||
- memory handle helpers for `MMAP`, `USERPTR`, and `DMABUF`,
|
||||
- core V4L2 types such as `Format`, `PixelFormat`, and `QueueType`.
|
||||
|
||||
The upstream crate also contains high-level device abstractions, stateful
|
||||
decoder/encoder helpers, stateless codec controls, examples, and C FFI. Those
|
||||
parts are intentionally removed here so this dependency stays scoped to capture.
|
||||
|
||||
## Build options
|
||||
|
||||
`cargo build` generates V4L2 bindings from `/usr/include/linux/videodev2.h` by
|
||||
default. Set `V4L2R_VIDEODEV2_H_PATH` to a directory containing `videodev2.h` to
|
||||
generate bindings from a different header.
|
||||
|
||||
For Android targets, the build script uses the Android NDK sysroot. Set one of
|
||||
`ANDROID_NDK_HOME`, `ANDROID_NDK_ROOT`, `NDK_HOME`, `ANDROID_HOME`, or
|
||||
`ANDROID_SDK_ROOT` if the NDK cannot be found automatically.
|
||||
22
libs/v4l2r/bindgen.rs
Normal file
22
libs/v4l2r/bindgen.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// This file defines the customizations to the bindgen builder used to generate the v4l2r
|
||||
// bindings.
|
||||
//
|
||||
// It is meant to be included from `lib/build.rs` and `android/build.rs`.
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Workaround for https://github.com/rust-lang/rust-bindgen/issues/753.
|
||||
pub struct Fix753;
|
||||
|
||||
impl bindgen::callbacks::ParseCallbacks for Fix753 {
|
||||
fn item_name(&self, original_item_name: &str) -> Option<String> {
|
||||
Some(original_item_name.trim_start_matches("Fix753_").to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn v4l2r_bindgen_builder(builder: bindgen::Builder) -> bindgen::Builder {
|
||||
builder
|
||||
.parse_callbacks(Box::new(Fix753))
|
||||
.derive_partialeq(true)
|
||||
.derive_eq(true)
|
||||
.derive_default(true)
|
||||
}
|
||||
180
libs/v4l2r/build.rs
Normal file
180
libs/v4l2r/build.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use std::env::{self, VarError};
|
||||
use std::path::PathBuf;
|
||||
|
||||
include!("bindgen.rs");
|
||||
|
||||
/// Environment variable that can be set to point to the directory containing the `videodev2.h`
|
||||
/// file to use to generate the bindings.
|
||||
const V4L2R_VIDEODEV_ENV: &str = "V4L2R_VIDEODEV2_H_PATH";
|
||||
|
||||
/// Default header file to parse if the `V4L2R_VIDEODEV2_H_PATH` environment variable is not set.
|
||||
const DEFAULT_VIDEODEV2_H_PATH: &str = "/usr/include/linux";
|
||||
|
||||
/// Wrapper file to use as input of bindgen.
|
||||
const WRAPPER_H: &str = "v4l2r_wrapper.h";
|
||||
|
||||
// Fix for https://github.com/rust-lang/rust-bindgen/issues/753
|
||||
const FIX753_H: &str = "fix753.h";
|
||||
|
||||
fn main() {
|
||||
let target = env::var("TARGET").unwrap_or_default();
|
||||
let is_android = target.contains("android");
|
||||
|
||||
let default_videodev2_h_path = if is_android {
|
||||
android_sysroot().join("usr/include").display().to_string()
|
||||
} else {
|
||||
DEFAULT_VIDEODEV2_H_PATH.to_string()
|
||||
};
|
||||
|
||||
let videodev2_h_path = env::var(V4L2R_VIDEODEV_ENV)
|
||||
.or_else(|e| {
|
||||
if let VarError::NotPresent = e {
|
||||
Ok(default_videodev2_h_path.clone())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
.expect("invalid `V4L2R_VIDEODEV2_H_PATH` environment variable");
|
||||
|
||||
let videodev2_h = PathBuf::from(videodev2_h_path.clone()).join(if is_android {
|
||||
"linux/videodev2.h"
|
||||
} else {
|
||||
"videodev2.h"
|
||||
});
|
||||
|
||||
println!("cargo::rerun-if-env-changed={}", V4L2R_VIDEODEV_ENV);
|
||||
println!("cargo::rerun-if-env-changed=ANDROID_NDK_HOME");
|
||||
println!("cargo::rerun-if-env-changed=ANDROID_NDK_ROOT");
|
||||
println!("cargo::rerun-if-env-changed=NDK_HOME");
|
||||
println!("cargo::rerun-if-env-changed=ANDROID_HOME");
|
||||
println!("cargo::rerun-if-env-changed=ANDROID_SDK_ROOT");
|
||||
println!("cargo::rerun-if-env-changed=CARGO_NDK_PLATFORM");
|
||||
println!("cargo::rerun-if-changed={}", videodev2_h.display());
|
||||
println!("cargo::rerun-if-changed={}", FIX753_H);
|
||||
println!("cargo::rerun-if-changed={}", WRAPPER_H);
|
||||
|
||||
let mut clang_args = vec![
|
||||
format!("-I{videodev2_h_path}"),
|
||||
#[cfg(all(feature = "arch64", not(feature = "arch32")))]
|
||||
"--target=x86_64-linux-gnu".into(),
|
||||
#[cfg(all(feature = "arch32", not(feature = "arch64")))]
|
||||
"--target=i686-linux-gnu".into(),
|
||||
];
|
||||
|
||||
if is_android {
|
||||
clang_args.extend(android_clang_args(&target));
|
||||
}
|
||||
|
||||
let bindings = v4l2r_bindgen_builder(bindgen::Builder::default())
|
||||
.header(WRAPPER_H)
|
||||
.clang_args(clang_args)
|
||||
.generate()
|
||||
.expect("unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
}
|
||||
|
||||
fn android_clang_args(target: &str) -> Vec<String> {
|
||||
let ndk = android_ndk_home();
|
||||
let toolchain = ndk.join("toolchains/llvm/prebuilt").join(host_tag());
|
||||
let sysroot = toolchain.join("sysroot");
|
||||
let clang_include = toolchain
|
||||
.join("lib/clang")
|
||||
.join(clang_version(&toolchain))
|
||||
.join("include");
|
||||
let api = env::var("CARGO_NDK_PLATFORM")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u32>().ok())
|
||||
.unwrap_or(21);
|
||||
let clang_target = android_clang_target(target);
|
||||
|
||||
vec![
|
||||
format!("--target={clang_target}"),
|
||||
format!("--sysroot={}", sysroot.display()),
|
||||
format!("-D__ANDROID_API__={api}"),
|
||||
format!("-isystem{}", clang_include.display()),
|
||||
format!("-isystem{}", sysroot.join("usr/include").display()),
|
||||
format!(
|
||||
"-isystem{}",
|
||||
sysroot.join("usr/include").join(clang_target).display()
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn android_clang_target(target: &str) -> &'static str {
|
||||
match target {
|
||||
"aarch64-linux-android" => "aarch64-linux-android",
|
||||
"armv7-linux-androideabi" => "armv7a-linux-androideabi",
|
||||
"i686-linux-android" => "i686-linux-android",
|
||||
"x86_64-linux-android" => "x86_64-linux-android",
|
||||
other => panic!("unsupported Android target for v4l2r bindgen: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn android_sysroot() -> PathBuf {
|
||||
android_ndk_home()
|
||||
.join("toolchains/llvm/prebuilt")
|
||||
.join(host_tag())
|
||||
.join("sysroot")
|
||||
}
|
||||
|
||||
fn android_ndk_home() -> PathBuf {
|
||||
for key in ["ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "NDK_HOME"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
return PathBuf::from(value);
|
||||
}
|
||||
}
|
||||
|
||||
for key in ["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
let ndk_dir = PathBuf::from(value).join("ndk");
|
||||
if let Some(newest) = newest_child_dir(&ndk_dir) {
|
||||
return newest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"v4l2r Android bindgen requires ANDROID_NDK_HOME, ANDROID_NDK_ROOT, NDK_HOME, \
|
||||
or ANDROID_HOME/ANDROID_SDK_ROOT with an ndk directory"
|
||||
);
|
||||
}
|
||||
|
||||
fn newest_child_dir(path: &PathBuf) -> Option<PathBuf> {
|
||||
let mut entries = std::fs::read_dir(path)
|
||||
.ok()?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| path.is_dir())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries.pop()
|
||||
}
|
||||
|
||||
fn host_tag() -> &'static str {
|
||||
if cfg!(target_os = "linux") {
|
||||
"linux-x86_64"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin-x86_64"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows-x86_64"
|
||||
} else {
|
||||
panic!("unsupported host OS for Android NDK");
|
||||
}
|
||||
}
|
||||
|
||||
fn clang_version(toolchain: &PathBuf) -> String {
|
||||
let clang_dir = toolchain.join("lib/clang");
|
||||
let mut entries = std::fs::read_dir(&clang_dir)
|
||||
.unwrap_or_else(|err| panic!("failed to read {}: {err}", clang_dir.display()))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.file_name().to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("no clang resource directory in {}", clang_dir.display()))
|
||||
}
|
||||
55
libs/v4l2r/fix753.h
Normal file
55
libs/v4l2r/fix753.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#undef V4L2_FWHT_FL_COMPONENTS_NUM_MSK
|
||||
#undef V4L2_FWHT_FL_PIXENC_MSK
|
||||
|
||||
#ifdef V4L2_FWHT_FL_IS_INTERLACED
|
||||
MARK_FIX_753(V4L2_FWHT_FL_IS_INTERLACED);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_IS_BOTTOM_FIRST
|
||||
MARK_FIX_753(V4L2_FWHT_FL_IS_BOTTOM_FIRST);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_IS_ALTERNATE
|
||||
MARK_FIX_753(V4L2_FWHT_FL_IS_ALTERNATE);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_IS_BOTTOM_FIELD
|
||||
MARK_FIX_753(V4L2_FWHT_FL_IS_BOTTOM_FIELD);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED
|
||||
MARK_FIX_753(V4L2_FWHT_FL_LUMA_IS_UNCOMPRESSED);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_CB_IS_UNCOMPRESSED
|
||||
MARK_FIX_753(V4L2_FWHT_FL_CB_IS_UNCOMPRESSED);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_CR_IS_UNCOMPRESSED
|
||||
MARK_FIX_753(V4L2_FWHT_FL_CR_IS_UNCOMPRESSED);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_CHROMA_FULL_HEIGHT
|
||||
MARK_FIX_753(V4L2_FWHT_FL_CHROMA_FULL_HEIGHT);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_CHROMA_FULL_WIDTH
|
||||
MARK_FIX_753(V4L2_FWHT_FL_CHROMA_FULL_WIDTH);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED
|
||||
MARK_FIX_753(V4L2_FWHT_FL_ALPHA_IS_UNCOMPRESSED);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_I_FRAME
|
||||
MARK_FIX_753(V4L2_FWHT_FL_I_FRAME);
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_COMPONENTS_NUM_OFFSET
|
||||
#define V4L2_FWHT_FL_COMPONENTS_NUM_MSK \
|
||||
(7 << V4L2_FWHT_FL_COMPONENTS_NUM_OFFSET)
|
||||
#endif
|
||||
|
||||
#ifdef V4L2_FWHT_FL_PIXENC_OFFSET
|
||||
#define V4L2_FWHT_FL_PIXENC_MSK (3 << V4L2_FWHT_FL_PIXENC_OFFSET)
|
||||
#endif
|
||||
8
libs/v4l2r/src/bindings.rs
Normal file
8
libs/v4l2r/src/bindings.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
1177
libs/v4l2r/src/ioctl.rs
Normal file
1177
libs/v4l2r/src/ioctl.rs
Normal file
File diff suppressed because it is too large
Load Diff
66
libs/v4l2r/src/ioctl/dqbuf.rs
Normal file
66
libs/v4l2r/src/ioctl/dqbuf.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::QueueType;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DqBufIoctlError {
|
||||
#[error("end-of-stream reached")]
|
||||
Eos,
|
||||
#[error("no buffer ready for dequeue")]
|
||||
NotReady,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for DqBufIoctlError {
|
||||
fn from(error: Errno) -> Self {
|
||||
match error {
|
||||
Errno::EAGAIN => Self::NotReady,
|
||||
Errno::EPIPE => Self::Eos,
|
||||
error => Self::Other(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DqBufIoctlError> for Errno {
|
||||
fn from(err: DqBufIoctlError) -> Self {
|
||||
match err {
|
||||
DqBufIoctlError::Eos => Errno::EPIPE,
|
||||
DqBufIoctlError::NotReady => Errno::EAGAIN,
|
||||
DqBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DqBufError<CE> = IoctlConvertError<DqBufIoctlError, CE>;
|
||||
pub type DqBufResult<O, CE> = IoctlConvertResult<O, DqBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_DQBUF` ioctl.
|
||||
pub fn dqbuf<O>(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult<O, O::Error>
|
||||
where
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, None);
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_dqbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
137
libs/v4l2r/src/ioctl/enum_fmt.rs
Normal file
137
libs/v4l2r/src/ioctl/enum_fmt.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Safe wrapper for the `VIDIOC_ENUM_FMT` ioctl.
|
||||
use super::string_from_cstr;
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_fmtdesc;
|
||||
use crate::{PixelFormat, QueueType};
|
||||
use bitflags::bitflags;
|
||||
use log::error;
|
||||
use nix::errno::Errno;
|
||||
use std::fmt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_ENUM_FMT` ioctl into the `flags` field of
|
||||
/// `struct v4l2_fmtdesc`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FormatFlags: u32 {
|
||||
const COMPRESSED = bindings::V4L2_FMT_FLAG_COMPRESSED;
|
||||
const EMULATED = bindings::V4L2_FMT_FLAG_EMULATED;
|
||||
}
|
||||
}
|
||||
/// Quickly get the Fourcc code of a format.
|
||||
impl From<v4l2_fmtdesc> for PixelFormat {
|
||||
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
|
||||
fmtdesc.pixelformat.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe variant of the `v4l2_fmtdesc` struct, to be used with `enum_fmt`.
|
||||
#[derive(Debug)]
|
||||
pub struct FmtDesc {
|
||||
pub flags: FormatFlags,
|
||||
pub description: String,
|
||||
pub pixelformat: PixelFormat,
|
||||
}
|
||||
|
||||
impl fmt::Display for FmtDesc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {} {}",
|
||||
self.pixelformat,
|
||||
self.description,
|
||||
if self.flags.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!("({:?})", self.flags)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_fmtdesc> for FmtDesc {
|
||||
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
|
||||
FmtDesc {
|
||||
flags: FormatFlags::from_bits_truncate(fmtdesc.flags),
|
||||
description: string_from_cstr(&fmtdesc.description).unwrap_or_else(|_| "".into()),
|
||||
pixelformat: fmtdesc.pixelformat.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_fmtdesc;
|
||||
nix::ioctl_readwrite!(vidioc_enum_fmt, b'V', 2, v4l2_fmtdesc);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumFmtError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] nix::Error),
|
||||
}
|
||||
|
||||
impl From<EnumFmtError> for Errno {
|
||||
fn from(err: EnumFmtError) -> Self {
|
||||
match err {
|
||||
EnumFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FMT` ioctl.
|
||||
pub fn enum_fmt<T: From<v4l2_fmtdesc>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
index: u32,
|
||||
) -> Result<T, EnumFmtError> {
|
||||
let mut fmtdesc = v4l2_fmtdesc {
|
||||
type_: queue as u32,
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
unsafe { ioctl::vidioc_enum_fmt(fd.as_raw_fd(), &mut fmtdesc) }?;
|
||||
|
||||
Ok(T::from(fmtdesc))
|
||||
}
|
||||
|
||||
/// Iterator over the formats of the given queue. This takes a reference to the
|
||||
/// device's file descriptor so no operation that could affect the format
|
||||
/// enumeration can take place while the iterator exists.
|
||||
pub struct FormatIterator<'a, F: AsRawFd> {
|
||||
fd: &'a F,
|
||||
queue: QueueType,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl<'a, F: AsRawFd> FormatIterator<'a, F> {
|
||||
/// Create a new iterator listing all the currently valid formats on
|
||||
/// `queue`.
|
||||
pub fn new(fd: &'a F, queue: QueueType) -> Self {
|
||||
FormatIterator {
|
||||
fd,
|
||||
queue,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: AsRawFd> Iterator for FormatIterator<'a, F> {
|
||||
type Item = FmtDesc;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match enum_fmt(self.fd, self.queue, self.index) {
|
||||
Ok(fmtdesc) => {
|
||||
self.index += 1;
|
||||
Some(fmtdesc)
|
||||
}
|
||||
// EINVAL means we have reached the last format.
|
||||
Err(EnumFmtError::IoctlError(Errno::EINVAL)) => None,
|
||||
_ => {
|
||||
error!("Unexpected return value for VIDIOC_ENUM_FMT!");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
libs/v4l2r/src/ioctl/expbuf.rs
Normal file
61
libs/v4l2r/src/ioctl/expbuf.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
//! Safe wrapper for the `VIDIOC_EXPBUF` ioctl.
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::OFlag;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings::v4l2_exportbuffer;
|
||||
use crate::QueueType;
|
||||
|
||||
bitflags! {
|
||||
/// Flags that can be passed when exporting the buffer.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ExpbufFlags: u32 {
|
||||
const CLOEXEC = OFlag::O_CLOEXEC.bits() as u32;
|
||||
const RDONLY = OFlag::O_RDONLY.bits() as u32;
|
||||
const WRONLY = OFlag::O_WRONLY.bits() as u32;
|
||||
const RDWR = OFlag::O_RDWR.bits() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_exportbuffer;
|
||||
nix::ioctl_readwrite!(vidioc_expbuf, b'V', 16, v4l2_exportbuffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExpbufError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<ExpbufError> for Errno {
|
||||
fn from(err: ExpbufError) -> Self {
|
||||
match err {
|
||||
ExpbufError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_EXPBUF` ioctl.
|
||||
pub fn expbuf<R: FromRawFd>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
index: usize,
|
||||
plane: usize,
|
||||
flags: ExpbufFlags,
|
||||
) -> Result<R, ExpbufError> {
|
||||
let mut v4l2_expbuf = v4l2_exportbuffer {
|
||||
type_: queue as u32,
|
||||
index: index as u32,
|
||||
plane: plane as u32,
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe { ioctl::vidioc_expbuf(fd.as_raw_fd(), &mut v4l2_expbuf) }?;
|
||||
|
||||
Ok(unsafe { R::from_raw_fd(v4l2_expbuf.fd) })
|
||||
}
|
||||
82
libs/v4l2r/src/ioctl/frameintervals.rs
Normal file
82
libs/v4l2r/src/ioctl/frameintervals.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_frmivalenum;
|
||||
use crate::PixelFormat;
|
||||
|
||||
/// A wrapper for the 'v4l2_frmivalenum' union member types
|
||||
#[derive(Debug)]
|
||||
pub enum FrmIvalTypes<'a> {
|
||||
Discrete(&'a bindings::v4l2_fract),
|
||||
StepWise(&'a bindings::v4l2_frmival_stepwise),
|
||||
}
|
||||
|
||||
impl v4l2_frmivalenum {
|
||||
/// Safely access the intervals member of the struct based on the
|
||||
/// returned type.
|
||||
pub fn intervals(&self) -> Option<FrmIvalTypes<'_>> {
|
||||
match self.type_ {
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_DISCRETE => {
|
||||
Some(FrmIvalTypes::Discrete(unsafe {
|
||||
&self.__bindgen_anon_1.discrete
|
||||
}))
|
||||
}
|
||||
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_CONTINUOUS
|
||||
| bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_STEPWISE => {
|
||||
Some(FrmIvalTypes::StepWise(unsafe {
|
||||
&self.__bindgen_anon_1.stepwise
|
||||
}))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_frmivalenum;
|
||||
nix::ioctl_readwrite!(vidioc_enum_frameintervals, b'V', 75, v4l2_frmivalenum);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FrameIntervalsError {
|
||||
#[error("Unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<FrameIntervalsError> for Errno {
|
||||
fn from(err: FrameIntervalsError) -> Self {
|
||||
match err {
|
||||
FrameIntervalsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FRAMEINTERVALS` ioctl.
|
||||
pub fn enum_frame_intervals<O: From<v4l2_frmivalenum>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
pixel_format: PixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<O, FrameIntervalsError> {
|
||||
let mut frame_interval = v4l2_frmivalenum {
|
||||
index,
|
||||
pixel_format: pixel_format.into(),
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_frameintervals(fd.as_raw_fd(), &mut frame_interval) } {
|
||||
Ok(_) => Ok(O::from(frame_interval)),
|
||||
Err(e) => Err(FrameIntervalsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
79
libs/v4l2r/src/ioctl/framesizes.rs
Normal file
79
libs/v4l2r/src/ioctl/framesizes.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_frmsizeenum;
|
||||
use crate::PixelFormat;
|
||||
|
||||
/// A wrapper for the 'v4l2_frmsizeenum' union member types
|
||||
#[derive(Debug)]
|
||||
pub enum FrmSizeTypes<'a> {
|
||||
Discrete(&'a bindings::v4l2_frmsize_discrete),
|
||||
StepWise(&'a bindings::v4l2_frmsize_stepwise),
|
||||
}
|
||||
|
||||
impl v4l2_frmsizeenum {
|
||||
/// Safely access the size member of the struct based on the
|
||||
/// returned type.
|
||||
pub fn size(&self) -> Option<FrmSizeTypes<'_>> {
|
||||
match self.type_ {
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE => {
|
||||
Some(FrmSizeTypes::Discrete(unsafe {
|
||||
&self.__bindgen_anon_1.discrete
|
||||
}))
|
||||
}
|
||||
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_CONTINUOUS
|
||||
| bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_STEPWISE => {
|
||||
Some(FrmSizeTypes::StepWise(unsafe {
|
||||
&self.__bindgen_anon_1.stepwise
|
||||
}))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_frmsizeenum;
|
||||
nix::ioctl_readwrite!(vidioc_enum_framesizes, b'V', 74, v4l2_frmsizeenum);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FrameSizeError {
|
||||
#[error("Unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<FrameSizeError> for Errno {
|
||||
fn from(err: FrameSizeError) -> Self {
|
||||
match err {
|
||||
FrameSizeError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FRAMESIZES` ioctl.
|
||||
pub fn enum_frame_sizes<O: From<v4l2_frmsizeenum>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
pixel_format: PixelFormat,
|
||||
) -> Result<O, FrameSizeError> {
|
||||
let mut frame_size = v4l2_frmsizeenum {
|
||||
index,
|
||||
pixel_format: pixel_format.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_framesizes(fd.as_raw_fd(), &mut frame_size) } {
|
||||
Ok(_) => Ok(O::from(frame_size)),
|
||||
Err(e) => Err(FrameSizeError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
189
libs/v4l2r/src/ioctl/g_dv_timings.rs
Normal file
189
libs/v4l2r/src/ioctl/g_dv_timings.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use enumn::N;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_dv_timings;
|
||||
use crate::bindings::v4l2_dv_timings_cap;
|
||||
use crate::bindings::v4l2_enum_dv_timings;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_dv_timings;
|
||||
use crate::bindings::v4l2_dv_timings_cap;
|
||||
use crate::bindings::v4l2_enum_dv_timings;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_s_dv_timings, b'V', 87, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_g_dv_timings, b'V', 88, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_enum_dv_timings, b'V', 98, v4l2_enum_dv_timings);
|
||||
nix::ioctl_read!(vidioc_query_dv_timings, b'V', 99, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_dv_timings_cap, b'V', 100, v4l2_dv_timings_cap);
|
||||
}
|
||||
|
||||
#[derive(Debug, N)]
|
||||
#[repr(u32)]
|
||||
pub enum DvTimingsType {
|
||||
Bt6561120 = bindings::V4L2_DV_BT_656_1120,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GDvTimingsError {
|
||||
#[error("ioctl not supported or invalid parameters")]
|
||||
Invalid,
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("Device is busy and cannot change timings")]
|
||||
Busy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GDvTimingsError> for Errno {
|
||||
fn from(err: GDvTimingsError) -> Self {
|
||||
match err {
|
||||
GDvTimingsError::Invalid => Errno::EINVAL,
|
||||
GDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
GDvTimingsError::Busy => Errno::EBUSY,
|
||||
GDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_DV_TIMINGS` ioctl.
|
||||
pub fn s_dv_timings<I: Into<v4l2_dv_timings>, O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
timings: I,
|
||||
) -> Result<O, GDvTimingsError> {
|
||||
let mut timings: v4l2_dv_timings = timings.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
|
||||
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
|
||||
Err(e) => Err(GDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_DV_TIMINGS` ioctl.
|
||||
pub fn g_dv_timings<O: From<v4l2_dv_timings>>(fd: &impl AsRawFd) -> Result<O, GDvTimingsError> {
|
||||
let mut timings = v4l2_dv_timings {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
|
||||
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
|
||||
Err(e) => Err(GDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumDvTimingsError {
|
||||
#[error("timing index is out of bounds")]
|
||||
Invalid,
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<EnumDvTimingsError> for Errno {
|
||||
fn from(err: EnumDvTimingsError) -> Self {
|
||||
match err {
|
||||
EnumDvTimingsError::Invalid => Errno::EINVAL,
|
||||
EnumDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
EnumDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_DV_TIMINGS` ioctl.
|
||||
pub fn enum_dv_timings<O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
) -> Result<O, EnumDvTimingsError> {
|
||||
let mut timings = v4l2_enum_dv_timings {
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings.timings)),
|
||||
Err(Errno::EINVAL) => Err(EnumDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(EnumDvTimingsError::Unsupported),
|
||||
Err(e) => Err(EnumDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryDvTimingsError {
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("No timings could be detected because no signal was found")]
|
||||
NoLink,
|
||||
#[error("Unstable signal")]
|
||||
UnstableSignal,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<QueryDvTimingsError> for Errno {
|
||||
fn from(err: QueryDvTimingsError) -> Self {
|
||||
match err {
|
||||
QueryDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
QueryDvTimingsError::NoLink => Errno::ENOLINK,
|
||||
QueryDvTimingsError::UnstableSignal => Errno::ENOLCK,
|
||||
QueryDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERY_DV_TIMINGS` ioctl.
|
||||
pub fn query_dv_timings<O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
) -> Result<O, QueryDvTimingsError> {
|
||||
let mut timings = v4l2_dv_timings {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_query_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::ENODATA) => Err(QueryDvTimingsError::Unsupported),
|
||||
Err(Errno::ENOLINK) => Err(QueryDvTimingsError::NoLink),
|
||||
Err(Errno::ENOLCK) => Err(QueryDvTimingsError::UnstableSignal),
|
||||
Err(e) => Err(QueryDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DvTimingsCapError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<DvTimingsCapError> for Errno {
|
||||
fn from(err: DvTimingsCapError) -> Self {
|
||||
match err {
|
||||
DvTimingsCapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_DV_TIMINGS_CAP` ioctl.
|
||||
pub fn dv_timings_cap<O: From<v4l2_dv_timings_cap>>(
|
||||
fd: &impl AsRawFd,
|
||||
) -> Result<O, DvTimingsCapError> {
|
||||
let mut caps = v4l2_dv_timings_cap {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_dv_timings_cap(fd.as_raw_fd(), &mut caps) } {
|
||||
Ok(_) => Ok(O::from(caps)),
|
||||
Err(e) => Err(DvTimingsCapError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
300
libs/v4l2r/src/ioctl/g_fmt.rs
Normal file
300
libs/v4l2r/src/ioctl/g_fmt.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
//! Safe wrapper for the `VIDIOC_(G|S|TRY)_FMT` ioctls.
|
||||
use nix::errno::Errno;
|
||||
use std::convert::{From, Into, TryFrom, TryInto};
|
||||
use std::default::Default;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_format;
|
||||
use crate::Format;
|
||||
use crate::FormatConversionError;
|
||||
use crate::PlaneLayout;
|
||||
use crate::QueueType;
|
||||
|
||||
impl TryFrom<(QueueType, &Format)> for v4l2_format {
|
||||
type Error = FormatConversionError;
|
||||
|
||||
fn try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error> {
|
||||
Ok(v4l2_format {
|
||||
type_: queue as u32,
|
||||
fmt: match queue {
|
||||
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => {
|
||||
bindings::v4l2_format__bindgen_ty_1 {
|
||||
pix_mp: {
|
||||
if format.plane_fmt.len() > bindings::VIDEO_MAX_PLANES as usize {
|
||||
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
|
||||
}
|
||||
|
||||
let mut pix_mp = bindings::v4l2_pix_format_mplane {
|
||||
width: format.width,
|
||||
height: format.height,
|
||||
pixelformat: format.pixelformat.into(),
|
||||
num_planes: format.plane_fmt.len() as u8,
|
||||
plane_fmt: Default::default(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for (plane, v4l2_plane) in
|
||||
format.plane_fmt.iter().zip(pix_mp.plane_fmt.iter_mut())
|
||||
{
|
||||
*v4l2_plane = plane.into();
|
||||
}
|
||||
|
||||
pix_mp
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => bindings::v4l2_format__bindgen_ty_1 {
|
||||
pix: {
|
||||
if format.plane_fmt.len() > 1 {
|
||||
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
|
||||
}
|
||||
|
||||
let (bytesperline, sizeimage) = if !format.plane_fmt.is_empty() {
|
||||
(
|
||||
format.plane_fmt[0].bytesperline,
|
||||
format.plane_fmt[0].sizeimage,
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
bindings::v4l2_pix_format {
|
||||
width: format.width,
|
||||
height: format.height,
|
||||
pixelformat: format.pixelformat.into(),
|
||||
bytesperline,
|
||||
sizeimage,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PlaneLayout> for bindings::v4l2_plane_pix_format {
|
||||
fn from(plane: &PlaneLayout) -> Self {
|
||||
bindings::v4l2_plane_pix_format {
|
||||
sizeimage: plane.sizeimage,
|
||||
bytesperline: plane.bytesperline,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_format;
|
||||
nix::ioctl_readwrite!(vidioc_g_fmt, b'V', 4, v4l2_format);
|
||||
nix::ioctl_readwrite!(vidioc_s_fmt, b'V', 5, v4l2_format);
|
||||
nix::ioctl_readwrite!(vidioc_try_fmt, b'V', 64, v4l2_format);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<GFmtError> for Errno {
|
||||
fn from(err: GFmtError) -> Self {
|
||||
match err {
|
||||
GFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
GFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
GFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_FMT` ioctl.
|
||||
pub fn g_fmt<O: TryFrom<v4l2_format>>(fd: &impl AsRawFd, queue: QueueType) -> Result<O, GFmtError> {
|
||||
let mut fmt = v4l2_format {
|
||||
type_: queue as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| GFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(GFmtError::InvalidBufferType),
|
||||
Err(e) => Err(GFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("error while converting to V4L2 format")]
|
||||
ToV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("device currently busy")]
|
||||
DeviceBusy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<SFmtError> for Errno {
|
||||
fn from(err: SFmtError) -> Self {
|
||||
match err {
|
||||
SFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
SFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
|
||||
SFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
SFmtError::DeviceBusy => Errno::EBUSY,
|
||||
SFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_FMT` ioctl.
|
||||
pub fn s_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
|
||||
fd: &mut impl AsRawFd,
|
||||
format: I,
|
||||
) -> Result<O, SFmtError> {
|
||||
let mut fmt: v4l2_format = format
|
||||
.try_into()
|
||||
.map_err(|_| SFmtError::ToV4L2FormatConversionError)?;
|
||||
|
||||
match unsafe { ioctl::vidioc_s_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| SFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(SFmtError::InvalidBufferType),
|
||||
Err(Errno::EBUSY) => Err(SFmtError::DeviceBusy),
|
||||
Err(e) => Err(SFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TryFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("error while converting to V4L2 format")]
|
||||
ToV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<TryFmtError> for Errno {
|
||||
fn from(err: TryFmtError) -> Self {
|
||||
match err {
|
||||
TryFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
TryFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
|
||||
TryFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
TryFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_TRY_FMT` ioctl.
|
||||
pub fn try_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
|
||||
fd: &impl AsRawFd,
|
||||
format: I,
|
||||
) -> Result<O, TryFmtError> {
|
||||
let mut fmt: v4l2_format = format
|
||||
.try_into()
|
||||
.map_err(|_| TryFmtError::ToV4L2FormatConversionError)?;
|
||||
|
||||
match unsafe { ioctl::vidioc_try_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| TryFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(TryFmtError::InvalidBufferType),
|
||||
Err(e) => Err(TryFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
// Convert from Format to multi-planar v4l2_format and back.
|
||||
fn mplane_to_v4l2_format() {
|
||||
// This is not a real format but let us use unique values per field.
|
||||
let mplane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NM12".into(),
|
||||
plane_fmt: vec![
|
||||
PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 153600,
|
||||
bytesperline: 320,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 76800,
|
||||
bytesperline: 160,
|
||||
},
|
||||
],
|
||||
};
|
||||
let v4l2_format = v4l2_format {
|
||||
..(QueueType::VideoCaptureMplane, &mplane).try_into().unwrap()
|
||||
};
|
||||
let mplane2: Format = v4l2_format.try_into().unwrap();
|
||||
assert_eq!(mplane, mplane2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Convert from Format to single-planar v4l2_format and back.
|
||||
fn splane_to_v4l2_format() {
|
||||
// This is not a real format but let us use unique values per field.
|
||||
let splane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NV12".into(),
|
||||
plane_fmt: vec![PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
}],
|
||||
};
|
||||
// Conversion to/from single-planar format.
|
||||
let v4l2_format = v4l2_format {
|
||||
..(QueueType::VideoCapture, &splane).try_into().unwrap()
|
||||
};
|
||||
let splane2: Format = v4l2_format.try_into().unwrap();
|
||||
assert_eq!(splane, splane2);
|
||||
|
||||
// Trying to use a multi-planar format with the single-planar API should
|
||||
// fail.
|
||||
let mplane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NM12".into(),
|
||||
// This is not a real format but let us use unique values per field.
|
||||
plane_fmt: vec![
|
||||
PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 153600,
|
||||
bytesperline: 320,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 76800,
|
||||
bytesperline: 160,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
TryInto::<v4l2_format>::try_into((QueueType::VideoCapture, &mplane)).err(),
|
||||
Some(FormatConversionError::TooManyPlanes(3))
|
||||
);
|
||||
}
|
||||
}
|
||||
149
libs/v4l2r/src/ioctl/g_parm.rs
Normal file
149
libs/v4l2r/src/ioctl/g_parm.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings::v4l2_standard;
|
||||
use crate::bindings::v4l2_std_id;
|
||||
use crate::bindings::v4l2_streamparm;
|
||||
use crate::QueueType;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_standard;
|
||||
use crate::bindings::v4l2_std_id;
|
||||
use crate::bindings::v4l2_streamparm;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_g_parm, b'V', 21, v4l2_streamparm);
|
||||
nix::ioctl_readwrite!(vidioc_s_parm, b'V', 22, v4l2_streamparm);
|
||||
nix::ioctl_read!(vidioc_g_std, b'V', 23, v4l2_std_id);
|
||||
nix::ioctl_write_ptr!(vidioc_s_std, b'V', 24, v4l2_std_id);
|
||||
nix::ioctl_readwrite!(vidioc_enumstd, b'V', 25, v4l2_standard);
|
||||
nix::ioctl_read!(vidioc_querystd, b'V', 63, v4l2_std_id);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GParmError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GParmError> for Errno {
|
||||
fn from(err: GParmError) -> Self {
|
||||
match err {
|
||||
GParmError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_PARM` ioctl.
|
||||
pub fn g_parm<O: From<v4l2_streamparm>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
) -> Result<O, GParmError> {
|
||||
let mut parm = v4l2_streamparm {
|
||||
type_: queue as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_parm(fd.as_raw_fd(), &mut parm) } {
|
||||
Ok(_) => Ok(O::from(parm)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_PARM` ioctl.
|
||||
pub fn s_parm<I: Into<v4l2_streamparm>, O: From<v4l2_streamparm>>(
|
||||
fd: &impl AsRawFd,
|
||||
parm: I,
|
||||
) -> Result<O, GParmError> {
|
||||
let mut parm = parm.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_parm(fd.as_raw_fd(), &mut parm) } {
|
||||
Ok(_) => Ok(O::from(parm)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_STD` ioctl.
|
||||
pub fn g_std<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
|
||||
let mut std_id: v4l2_std_id = 0;
|
||||
|
||||
match unsafe { ioctl::vidioc_g_std(fd.as_raw_fd(), &mut std_id) } {
|
||||
Ok(_) => Ok(O::from(std_id)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SStdError {
|
||||
#[error("unsupported standard requested")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<SStdError> for Errno {
|
||||
fn from(err: SStdError) -> Self {
|
||||
match err {
|
||||
SStdError::Unsupported => Errno::EINVAL,
|
||||
SStdError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_STD` ioctl.
|
||||
pub fn s_std<I: Into<v4l2_std_id>>(fd: &impl AsRawFd, std_id: I) -> Result<(), SStdError> {
|
||||
let std_id = std_id.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_std(fd.as_raw_fd(), &std_id) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(SStdError::Unsupported),
|
||||
Err(e) => Err(SStdError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumStdError {
|
||||
#[error("requested index is out of bounds")]
|
||||
OutOfBounds,
|
||||
#[error("standard video timings are not supported for this input or output")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<EnumStdError> for Errno {
|
||||
fn from(err: EnumStdError) -> Self {
|
||||
match err {
|
||||
EnumStdError::OutOfBounds => Errno::EINVAL,
|
||||
EnumStdError::Unsupported => Errno::ENODATA,
|
||||
EnumStdError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUMSTD` ioctl.
|
||||
pub fn enumstd<O: From<v4l2_standard>>(fd: &impl AsRawFd, index: u32) -> Result<O, EnumStdError> {
|
||||
let mut standard = v4l2_standard {
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enumstd(fd.as_raw_fd(), &mut standard) } {
|
||||
Ok(_) => Ok(O::from(standard)),
|
||||
Err(Errno::EINVAL) => Err(EnumStdError::OutOfBounds),
|
||||
Err(Errno::ENODATA) => Err(EnumStdError::Unsupported),
|
||||
Err(e) => Err(EnumStdError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYSTD` ioctl.
|
||||
pub fn querystd<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
|
||||
let mut std_id: v4l2_std_id = 0;
|
||||
|
||||
match unsafe { ioctl::vidioc_querystd(fd.as_raw_fd(), &mut std_id) } {
|
||||
Ok(_) => Ok(O::from(std_id)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
130
libs/v4l2r/src/ioctl/g_selection.rs
Normal file
130
libs/v4l2r/src/ioctl/g_selection.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use enumn::N;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_rect;
|
||||
use crate::bindings::v4l2_selection;
|
||||
|
||||
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum SelectionType {
|
||||
Capture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
Output = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
|
||||
}
|
||||
|
||||
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum SelectionTarget {
|
||||
Crop = bindings::V4L2_SEL_TGT_CROP,
|
||||
CropDefault = bindings::V4L2_SEL_TGT_CROP_DEFAULT,
|
||||
CropBounds = bindings::V4L2_SEL_TGT_CROP_BOUNDS,
|
||||
NativeSize = bindings::V4L2_SEL_TGT_NATIVE_SIZE,
|
||||
Compose = bindings::V4L2_SEL_TGT_COMPOSE,
|
||||
ComposeDefault = bindings::V4L2_SEL_TGT_COMPOSE_DEFAULT,
|
||||
ComposeBounds = bindings::V4L2_SEL_TGT_COMPOSE_BOUNDS,
|
||||
ComposePadded = bindings::V4L2_SEL_TGT_COMPOSE_PADDED,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SelectionFlags: u32 {
|
||||
const GE = bindings::V4L2_SEL_FLAG_GE;
|
||||
const LE = bindings::V4L2_SEL_FLAG_LE;
|
||||
const KEEP_CONFIG = bindings::V4L2_SEL_FLAG_KEEP_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_selection;
|
||||
nix::ioctl_readwrite!(vidioc_g_selection, b'V', 94, v4l2_selection);
|
||||
nix::ioctl_readwrite!(vidioc_s_selection, b'V', 95, v4l2_selection);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GSelectionError {
|
||||
#[error("invalid type or target requested")]
|
||||
Invalid,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GSelectionError> for Errno {
|
||||
fn from(err: GSelectionError) -> Self {
|
||||
match err {
|
||||
GSelectionError::Invalid => Errno::EINVAL,
|
||||
GSelectionError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_SELECTION` ioctl.
|
||||
pub fn g_selection<R: From<v4l2_rect>>(
|
||||
fd: &impl AsRawFd,
|
||||
selection: SelectionType,
|
||||
target: SelectionTarget,
|
||||
) -> Result<R, GSelectionError> {
|
||||
let mut sel = v4l2_selection {
|
||||
type_: selection as u32,
|
||||
target: target as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_selection(fd.as_raw_fd(), &mut sel) } {
|
||||
Ok(_) => Ok(R::from(sel.r)),
|
||||
Err(Errno::EINVAL) => Err(GSelectionError::Invalid),
|
||||
Err(e) => Err(GSelectionError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SSelectionError {
|
||||
#[error("invalid type or target requested")]
|
||||
Invalid,
|
||||
#[error("invalid range requested")]
|
||||
InvalidRange,
|
||||
#[error("cannot change selection rectangle currently")]
|
||||
Busy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<SSelectionError> for Errno {
|
||||
fn from(err: SSelectionError) -> Self {
|
||||
match err {
|
||||
SSelectionError::Invalid => Errno::EINVAL,
|
||||
SSelectionError::InvalidRange => Errno::ERANGE,
|
||||
SSelectionError::Busy => Errno::EBUSY,
|
||||
SSelectionError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_SELECTION` ioctl.
|
||||
pub fn s_selection<RI: Into<v4l2_rect>, RO: From<v4l2_rect>>(
|
||||
fd: &impl AsRawFd,
|
||||
selection: SelectionType,
|
||||
target: SelectionTarget,
|
||||
rect: RI,
|
||||
flags: SelectionFlags,
|
||||
) -> Result<RO, SSelectionError> {
|
||||
let mut sel = v4l2_selection {
|
||||
type_: selection as u32,
|
||||
target: target as u32,
|
||||
flags: flags.bits(),
|
||||
r: rect.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_s_selection(fd.as_raw_fd(), &mut sel) } {
|
||||
Ok(_) => Ok(RO::from(sel.r)),
|
||||
Err(Errno::EINVAL) => Err(SSelectionError::Invalid),
|
||||
Err(Errno::ERANGE) => Err(SSelectionError::InvalidRange),
|
||||
Err(Errno::EBUSY) => Err(SSelectionError::Busy),
|
||||
Err(e) => Err(SSelectionError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
117
libs/v4l2r/src/ioctl/mmap.rs
Normal file
117
libs/v4l2r/src/ioctl/mmap.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use core::num::NonZeroUsize;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
ops::Deref,
|
||||
ptr::NonNull,
|
||||
slice,
|
||||
};
|
||||
use std::{ops::DerefMut, os::unix::io::AsFd};
|
||||
|
||||
use log::error;
|
||||
use nix::{errno::Errno, libc::off_t, sys::mman};
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct PlaneMapping {
|
||||
// A mapping remains valid until we munmap it, that is, until the
|
||||
// PlaneMapping object is deleted. Hence the static lifetime.
|
||||
pub data: &'static mut [u8],
|
||||
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl PlaneMapping {
|
||||
pub fn size(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
pub fn restrict(mut self, start: usize, end: usize) -> Self {
|
||||
self.start = max(self.start, start);
|
||||
self.end = min(self.end, end);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PlaneMapping {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for PlaneMapping {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PlaneMapping {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for PlaneMapping {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PlaneMapping {
|
||||
fn drop(&mut self) {
|
||||
// Safe because the pointer and length were constructed in mmap() and
|
||||
// are always valid.
|
||||
unsafe {
|
||||
mman::munmap(
|
||||
NonNull::new_unchecked(self.data.as_mut_ptr().cast()),
|
||||
self.data.len(),
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Error while unmapping plane: {}", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MmapError {
|
||||
#[error("provided length was 0")]
|
||||
ZeroLength,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<MmapError> for Errno {
|
||||
fn from(err: MmapError) -> Self {
|
||||
match err {
|
||||
MmapError::ZeroLength => Errno::EINVAL,
|
||||
MmapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO should be unsafe because the mapping can be used after a buffer is queued?
|
||||
// Or not, since this cannot cause a crash...
|
||||
pub fn mmap(fd: &impl AsFd, mem_offset: u32, length: u32) -> Result<PlaneMapping, MmapError> {
|
||||
let non_zero_length = NonZeroUsize::new(length as usize).ok_or(MmapError::ZeroLength)?;
|
||||
let data = unsafe {
|
||||
mman::mmap(
|
||||
None,
|
||||
non_zero_length,
|
||||
mman::ProtFlags::PROT_READ | mman::ProtFlags::PROT_WRITE,
|
||||
mman::MapFlags::MAP_SHARED,
|
||||
fd,
|
||||
mem_offset as off_t,
|
||||
)
|
||||
}?;
|
||||
|
||||
Ok(PlaneMapping {
|
||||
// Safe because we know the pointer is valid and has enough data mapped
|
||||
// to cover the length.
|
||||
data: unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), length as usize) },
|
||||
start: 0,
|
||||
end: length as usize,
|
||||
})
|
||||
}
|
||||
197
libs/v4l2r/src/ioctl/qbuf.rs
Normal file
197
libs/v4l2r/src/ioctl/qbuf.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Safe wrapper for the VIDIOC_(D)QBUF and VIDIOC_QUERYBUF ioctls.
|
||||
use nix::errno::Errno;
|
||||
use nix::libc::{suseconds_t, time_t};
|
||||
use nix::sys::time::{TimeVal, TimeValLike};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::BufferFlags;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::memory::Memory;
|
||||
use crate::memory::PlaneHandle;
|
||||
use crate::QueueType;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QBufIoctlError {
|
||||
#[error("invalid number of planes specified for the buffer: got {0}, expected {1}")]
|
||||
NumPlanesMismatch(usize, usize),
|
||||
#[error("data offset specified while using the single-planar API")]
|
||||
DataOffsetNotSupported,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for QBufIoctlError {
|
||||
fn from(errno: Errno) -> Self {
|
||||
Self::Other(errno)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QBufIoctlError> for Errno {
|
||||
fn from(err: QBufIoctlError) -> Self {
|
||||
match err {
|
||||
QBufIoctlError::NumPlanesMismatch(_, _) => Errno::EINVAL,
|
||||
QBufIoctlError::DataOffsetNotSupported => Errno::EINVAL,
|
||||
QBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a single plane of a V4L2 buffer.
|
||||
pub struct QBufPlane(pub bindings::v4l2_plane);
|
||||
|
||||
impl QBufPlane {
|
||||
// TODO remove as this is not safe - we should always specify a handle.
|
||||
pub fn new(bytes_used: usize) -> Self {
|
||||
QBufPlane(bindings::v4l2_plane {
|
||||
bytesused: bytes_used as u32,
|
||||
data_offset: 0,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_from_handle<H: PlaneHandle>(handle: &H, bytes_used: usize) -> Self {
|
||||
let mut plane = Self::new(bytes_used);
|
||||
handle.fill_v4l2_plane(&mut plane.0);
|
||||
plane
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for QBufPlane {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("QBufPlane")
|
||||
.field("bytesused", &self.0.bytesused)
|
||||
.field("data_offset", &self.0.data_offset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all the information that can be passed to the `qbuf` ioctl.
|
||||
// TODO Change this to contain a v4l2_buffer, and create constructors/methods
|
||||
// to change it? Then during qbuf we just need to set m.planes to planes
|
||||
// (after resizing it to 8) and we are good to use it as-is.
|
||||
// We could even turn the trait into AsRef<v4l2_buffer> for good measure.
|
||||
#[derive(Debug)]
|
||||
pub struct QBuffer<H: PlaneHandle> {
|
||||
index: u32,
|
||||
queue: QueueType,
|
||||
pub flags: BufferFlags,
|
||||
pub field: u32,
|
||||
pub sequence: u32,
|
||||
pub timestamp: TimeVal,
|
||||
pub planes: Vec<QBufPlane>,
|
||||
pub _h: std::marker::PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> QBuffer<H> {
|
||||
pub fn new(queue: QueueType, index: u32) -> Self {
|
||||
QBuffer {
|
||||
index,
|
||||
queue,
|
||||
flags: Default::default(),
|
||||
field: Default::default(),
|
||||
sequence: Default::default(),
|
||||
timestamp: TimeVal::zero(),
|
||||
planes: Vec::new(),
|
||||
_h: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> QBuffer<H> {
|
||||
pub fn set_timestamp(mut self, sec: time_t, usec: suseconds_t) -> Self {
|
||||
self.timestamp = TimeVal::new(sec, usec);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> From<QBuffer<H>> for UncheckedV4l2Buffer {
|
||||
fn from(qbuf: QBuffer<H>) -> Self {
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(qbuf.queue, Some(qbuf.index));
|
||||
v4l2_buf.0.index = qbuf.index;
|
||||
v4l2_buf.0.type_ = qbuf.queue as u32;
|
||||
v4l2_buf.0.memory = H::Memory::MEMORY_TYPE as u32;
|
||||
v4l2_buf.0.flags = qbuf.flags.bits();
|
||||
v4l2_buf.0.field = qbuf.field;
|
||||
v4l2_buf.0.sequence = qbuf.sequence;
|
||||
v4l2_buf.0.timestamp.tv_sec = qbuf.timestamp.tv_sec();
|
||||
v4l2_buf.0.timestamp.tv_usec = qbuf.timestamp.tv_usec();
|
||||
if let Some(planes) = &mut v4l2_buf.1 {
|
||||
for (dst_plane, src_plane) in planes.iter_mut().zip(qbuf.planes.into_iter()) {
|
||||
*dst_plane = src_plane.0;
|
||||
}
|
||||
} else {
|
||||
let plane = &qbuf.planes[0];
|
||||
|
||||
v4l2_buf.0.length = plane.0.length;
|
||||
v4l2_buf.0.bytesused = plane.0.bytesused;
|
||||
v4l2_buf.0.m = (&plane.0.m, H::Memory::MEMORY_TYPE).into();
|
||||
}
|
||||
|
||||
v4l2_buf
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_qbuf, b'V', 15, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_prepare_buf, b'V', 93, v4l2_buffer);
|
||||
}
|
||||
|
||||
pub type QBufError<CE> = IoctlConvertError<QBufIoctlError, CE>;
|
||||
pub type QBufResult<O, CE> = IoctlConvertResult<O, QBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QBUF` ioctl.
|
||||
///
|
||||
/// TODO: `qbuf` should be unsafe! The following invariants need to be guaranteed
|
||||
/// by the caller:
|
||||
///
|
||||
/// For MMAP buffers, any mapping must not be accessed by the caller (or any
|
||||
/// mapping must be unmapped before queueing?). Also if the buffer has been
|
||||
/// DMABUF-exported, its consumers must likewise not access it.
|
||||
///
|
||||
/// For DMABUF buffers, the FD must not be duplicated and accessed anywhere else.
|
||||
///
|
||||
/// For USERPTR buffers, things are most tricky. Not only must the data not be
|
||||
/// accessed by anyone else, the caller also needs to guarantee that the backing
|
||||
/// memory won't be freed until the corresponding buffer is returned by either
|
||||
/// `dqbuf` or `streamoff`.
|
||||
pub fn qbuf<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
|
||||
where
|
||||
I: Into<UncheckedV4l2Buffer>,
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_PREPARE_BUF` ioctl.
|
||||
pub fn prepare_buf<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
|
||||
where
|
||||
I: Into<UncheckedV4l2Buffer>,
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_prepare_buf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
113
libs/v4l2r/src/ioctl/querybuf.rs
Normal file
113
libs/v4l2r/src/ioctl/querybuf.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::convert::Infallible;
|
||||
use std::convert::TryFrom;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::BufferFlags;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::QueueType;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueryBufPlane {
|
||||
/// Offset to pass to `mmap()` in order to obtain a mapping for this plane.
|
||||
pub mem_offset: u32,
|
||||
/// Length of this plane.
|
||||
pub length: u32,
|
||||
}
|
||||
|
||||
/// Contains information about a buffer's layout, as obtained from [`crate::ioctl::querybuf`].
|
||||
///
|
||||
/// It is a subset of [`crate::ioctl::V4l2Buffer`], only more convenient on occasion because its
|
||||
/// conversion from an unchecked v4l2_buffer cannot fail.
|
||||
///
|
||||
/// Single-planar buffers have one entry in [`planes`] representing the layout of their unique
|
||||
/// plane.
|
||||
#[derive(Debug)]
|
||||
pub struct QueryBuffer {
|
||||
pub index: usize,
|
||||
pub flags: BufferFlags,
|
||||
pub planes: Vec<QueryBufPlane>,
|
||||
}
|
||||
|
||||
impl TryFrom<UncheckedV4l2Buffer> for QueryBuffer {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(buffer: UncheckedV4l2Buffer) -> Result<Self, Self::Error> {
|
||||
let v4l2_buf = buffer.0;
|
||||
let planes = match buffer.1 {
|
||||
None => vec![QueryBufPlane {
|
||||
mem_offset: unsafe { v4l2_buf.m.offset },
|
||||
length: v4l2_buf.length,
|
||||
}],
|
||||
Some(v4l2_planes) => v4l2_planes
|
||||
.iter()
|
||||
.take(v4l2_buf.length as usize)
|
||||
.map(|v4l2_plane| QueryBufPlane {
|
||||
mem_offset: unsafe { v4l2_plane.m.mem_offset },
|
||||
length: v4l2_plane.length,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(QueryBuffer {
|
||||
index: v4l2_buf.index as usize,
|
||||
flags: BufferFlags::from_bits_truncate(v4l2_buf.flags),
|
||||
planes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryBufIoctlError {
|
||||
#[error("unsupported queue or out-of-bounds index")]
|
||||
InvalidInput,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for QueryBufIoctlError {
|
||||
fn from(err: Errno) -> Self {
|
||||
match err {
|
||||
Errno::EINVAL => QueryBufIoctlError::InvalidInput,
|
||||
e => QueryBufIoctlError::Other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryBufIoctlError> for Errno {
|
||||
fn from(err: QueryBufIoctlError) -> Self {
|
||||
match err {
|
||||
QueryBufIoctlError::InvalidInput => Errno::EINVAL,
|
||||
QueryBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type QueryBufError<CE> = IoctlConvertError<QueryBufIoctlError, CE>;
|
||||
pub type QueryBufResult<O, CE> = IoctlConvertResult<O, QueryBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYBUF` ioctl.
|
||||
pub fn querybuf<O>(fd: &impl AsRawFd, queue: QueueType, index: usize) -> QueryBufResult<O, O::Error>
|
||||
where
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, Some(index as u32));
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_querybuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
136
libs/v4l2r/src/ioctl/querycap.rs
Normal file
136
libs/v4l2r/src/ioctl/querycap.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
//! Safe wrapper for the `VIDIOC_QUERYCAP` ioctl.
|
||||
use super::string_from_cstr;
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_capability;
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use std::fmt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_QUERYCAP` ioctl into the `capabilities`
|
||||
/// or `device_capabilities` field of `v4l2_capability`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Capabilities: u32 {
|
||||
const VIDEO_CAPTURE = bindings::V4L2_CAP_VIDEO_CAPTURE;
|
||||
const VIDEO_OUTPUT = bindings::V4L2_CAP_VIDEO_OUTPUT;
|
||||
const VIDEO_OVERLAY = bindings::V4L2_CAP_VIDEO_OVERLAY;
|
||||
const VBI_CAPTURE = bindings::V4L2_CAP_VBI_CAPTURE;
|
||||
const VBI_OUTPUT = bindings::V4L2_CAP_VBI_OUTPUT;
|
||||
const SLICED_VBI_CAPTURE = bindings::V4L2_CAP_SLICED_VBI_CAPTURE;
|
||||
const SLICED_VBI_OUTPUT = bindings::V4L2_CAP_SLICED_VBI_OUTPUT;
|
||||
const RDS_CAPTURE = bindings::V4L2_CAP_RDS_CAPTURE;
|
||||
const VIDEO_OUTPUT_OVERLAY = bindings::V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
|
||||
const HW_FREQ_SEEK = bindings::V4L2_CAP_HW_FREQ_SEEK;
|
||||
const RDS_OUTPUT = bindings::V4L2_CAP_RDS_OUTPUT;
|
||||
|
||||
const VIDEO_CAPTURE_MPLANE = bindings::V4L2_CAP_VIDEO_CAPTURE_MPLANE;
|
||||
const VIDEO_OUTPUT_MPLANE = bindings::V4L2_CAP_VIDEO_OUTPUT_MPLANE;
|
||||
const VIDEO_M2M_MPLANE = bindings::V4L2_CAP_VIDEO_M2M_MPLANE;
|
||||
const VIDEO_M2M = bindings::V4L2_CAP_VIDEO_M2M;
|
||||
|
||||
const TUNER = bindings::V4L2_CAP_TUNER;
|
||||
const AUDIO = bindings::V4L2_CAP_AUDIO;
|
||||
const RADIO = bindings::V4L2_CAP_RADIO;
|
||||
const MODULATOR = bindings::V4L2_CAP_MODULATOR;
|
||||
|
||||
const SDR_CAPTURE = bindings::V4L2_CAP_SDR_CAPTURE;
|
||||
const EXT_PIX_FORMAT = bindings::V4L2_CAP_EXT_PIX_FORMAT;
|
||||
const SDR_OUTPUT = bindings::V4L2_CAP_SDR_OUTPUT;
|
||||
const META_CAPTURE = bindings::V4L2_CAP_META_CAPTURE;
|
||||
|
||||
const READWRITE = bindings::V4L2_CAP_READWRITE;
|
||||
const ASYNCIO = bindings::V4L2_CAP_ASYNCIO;
|
||||
const STREAMING = bindings::V4L2_CAP_STREAMING;
|
||||
const META_OUTPUT = bindings::V4L2_CAP_META_OUTPUT;
|
||||
|
||||
const TOUCH = bindings::V4L2_CAP_TOUCH;
|
||||
|
||||
const DEVICE_CAPS = bindings::V4L2_CAP_DEVICE_CAPS;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Capabilities {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to get the capability flags from a `VIDIOC_QUERYCAP` ioctl.
|
||||
impl From<v4l2_capability> for Capabilities {
|
||||
fn from(qcap: v4l2_capability) -> Self {
|
||||
Capabilities::from_bits_truncate(qcap.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe variant of the `v4l2_capability` struct, to be used with `querycap`.
|
||||
#[derive(Debug)]
|
||||
pub struct Capability {
|
||||
pub driver: String,
|
||||
pub card: String,
|
||||
pub bus_info: String,
|
||||
pub version: u32,
|
||||
pub capabilities: Capabilities,
|
||||
pub device_caps: Option<Capabilities>,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Returns the set of capabilities of the hardware as a whole.
|
||||
pub fn capabilities(&self) -> Capabilities {
|
||||
self.capabilities
|
||||
}
|
||||
|
||||
/// Returns the capabilities that apply to the currently opened V4L2 node.
|
||||
pub fn device_caps(&self) -> Capabilities {
|
||||
self.device_caps
|
||||
.unwrap_or_else(|| self.capabilities.difference(Capabilities::DEVICE_CAPS))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_capability> for Capability {
|
||||
fn from(qcap: v4l2_capability) -> Self {
|
||||
Capability {
|
||||
driver: string_from_cstr(&qcap.driver).unwrap_or_else(|_| "".into()),
|
||||
card: string_from_cstr(&qcap.card).unwrap_or_else(|_| "".into()),
|
||||
bus_info: string_from_cstr(&qcap.bus_info).unwrap_or_else(|_| "".into()),
|
||||
version: qcap.version,
|
||||
capabilities: Capabilities::from_bits_truncate(qcap.capabilities),
|
||||
device_caps: if qcap.capabilities & bindings::V4L2_CAP_DEVICE_CAPS != 0 {
|
||||
Some(Capabilities::from_bits_truncate(qcap.device_caps))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_capability;
|
||||
nix::ioctl_read!(vidioc_querycap, b'V', 0, v4l2_capability);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryCapError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<QueryCapError> for Errno {
|
||||
fn from(err: QueryCapError) -> Self {
|
||||
match err {
|
||||
QueryCapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYCAP` ioctl.
|
||||
pub fn querycap<T: From<v4l2_capability>>(fd: &impl AsRawFd) -> Result<T, QueryCapError> {
|
||||
let mut qcap: v4l2_capability = Default::default();
|
||||
|
||||
match unsafe { ioctl::vidioc_querycap(fd.as_raw_fd(), &mut qcap) } {
|
||||
Ok(_) => Ok(T::from(qcap)),
|
||||
Err(e) => Err(QueryCapError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
162
libs/v4l2r/src/ioctl/reqbufs.rs
Normal file
162
libs/v4l2r/src/ioctl/reqbufs.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! Safe wrapper for the `VIDIOC_REQBUFS` ioctl.
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_create_buffers;
|
||||
use crate::bindings::v4l2_format;
|
||||
use crate::bindings::v4l2_requestbuffers;
|
||||
use crate::memory::MemoryType;
|
||||
use crate::QueueType;
|
||||
use bitflags::bitflags;
|
||||
use nix::{self, errno::Errno};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_REQBUFS` ioctl into the `capabilities`
|
||||
/// field of `struct v4l2_requestbuffers`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BufferCapabilities: u32 {
|
||||
const SUPPORTS_MMAP = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP;
|
||||
const SUPPORTS_USERPTR = bindings::V4L2_BUF_CAP_SUPPORTS_USERPTR;
|
||||
const SUPPORTS_DMABUF = bindings::V4L2_BUF_CAP_SUPPORTS_DMABUF;
|
||||
const SUPPORTS_REQUESTS = bindings::V4L2_BUF_CAP_SUPPORTS_REQUESTS;
|
||||
const SUPPORTS_ORPHANED_BUFS = bindings::V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS;
|
||||
const SUPPORTS_M2M_HOLD_CAPTURE_BUF = bindings::V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
|
||||
const SUPPORTS_MMAP_CACHE_HINTS = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Memory Consistency Flags passed to the `VIDIOC_REQBUFS` ioctl in the `flags`
|
||||
/// field of `struct v4l2_requestbuffers`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MemoryConsistency: u8 {
|
||||
const MEMORY_FLAG_NON_COHERENT = bindings::V4L2_MEMORY_FLAG_NON_COHERENT as u8;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_requestbuffers> for () {
|
||||
fn from(_reqbufs: v4l2_requestbuffers) -> Self {}
|
||||
}
|
||||
|
||||
/// In case we are just interested in the number of buffers that `reqbufs`
|
||||
/// created.
|
||||
impl From<v4l2_requestbuffers> for usize {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
reqbufs.count as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// If we just want to query the buffer capabilities.
|
||||
impl From<v4l2_requestbuffers> for BufferCapabilities {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
BufferCapabilities::from_bits_truncate(reqbufs.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Full result of the `reqbufs` ioctl.
|
||||
pub struct RequestBuffers {
|
||||
pub count: u32,
|
||||
pub capabilities: BufferCapabilities,
|
||||
pub flags: MemoryConsistency,
|
||||
}
|
||||
|
||||
impl From<v4l2_requestbuffers> for RequestBuffers {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
RequestBuffers {
|
||||
count: reqbufs.count,
|
||||
capabilities: BufferCapabilities::from_bits_truncate(reqbufs.capabilities),
|
||||
flags: MemoryConsistency::from_bits_truncate(reqbufs.flags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_create_buffers;
|
||||
use crate::bindings::v4l2_requestbuffers;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_reqbufs, b'V', 8, v4l2_requestbuffers);
|
||||
nix::ioctl_readwrite!(vidioc_create_bufs, b'V', 92, v4l2_create_buffers);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ReqbufsError {
|
||||
#[error("invalid buffer ({0}) or memory type ({1:?}) requested")]
|
||||
InvalidBufferType(QueueType, MemoryType),
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<ReqbufsError> for Errno {
|
||||
fn from(err: ReqbufsError) -> Self {
|
||||
match err {
|
||||
ReqbufsError::InvalidBufferType(_, _) => Errno::EINVAL,
|
||||
ReqbufsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_REQBUFS` ioctl.
|
||||
pub fn reqbufs<O: From<v4l2_requestbuffers>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
memory: MemoryType,
|
||||
count: u32,
|
||||
flags: MemoryConsistency,
|
||||
) -> Result<O, ReqbufsError> {
|
||||
let mut reqbufs = v4l2_requestbuffers {
|
||||
count,
|
||||
type_: queue as u32,
|
||||
memory: memory as u32,
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_reqbufs(fd.as_raw_fd(), &mut reqbufs) } {
|
||||
Ok(_) => Ok(O::from(reqbufs)),
|
||||
Err(Errno::EINVAL) => Err(ReqbufsError::InvalidBufferType(queue, memory)),
|
||||
Err(e) => Err(ReqbufsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CreateBufsError {
|
||||
#[error("no memory available to allocate MMAP buffers")]
|
||||
NoMem,
|
||||
#[error("invalid format or memory type requested")]
|
||||
Invalid,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<CreateBufsError> for Errno {
|
||||
fn from(err: CreateBufsError) -> Self {
|
||||
match err {
|
||||
CreateBufsError::NoMem => Errno::ENOMEM,
|
||||
CreateBufsError::Invalid => Errno::EINVAL,
|
||||
CreateBufsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_CREATE_BUFS` ioctl.
|
||||
pub fn create_bufs<F: Into<v4l2_format>, O: From<v4l2_create_buffers>>(
|
||||
fd: &impl AsRawFd,
|
||||
count: u32,
|
||||
memory: MemoryType,
|
||||
format: F,
|
||||
) -> Result<O, CreateBufsError> {
|
||||
let mut create_bufs = v4l2_create_buffers {
|
||||
count,
|
||||
memory: memory as u32,
|
||||
format: format.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_create_bufs(fd.as_raw_fd(), &mut create_bufs) } {
|
||||
Ok(_) => Ok(O::from(create_bufs)),
|
||||
Err(Errno::ENOMEM) => Err(CreateBufsError::NoMem),
|
||||
Err(Errno::EINVAL) => Err(CreateBufsError::Invalid),
|
||||
Err(e) => Err(CreateBufsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
71
libs/v4l2r/src/ioctl/streamon.rs
Normal file
71
libs/v4l2r/src/ioctl/streamon.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Safe wrapper for the `VIDIOC_STREAM(ON|OFF)` ioctls.
|
||||
use crate::QueueType;
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
nix::ioctl_write_ptr!(vidioc_streamon, b'V', 18, u32);
|
||||
nix::ioctl_write_ptr!(vidioc_streamoff, b'V', 19, u32);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StreamOnError {
|
||||
#[error("queue type ({0}) not supported, or no buffers allocated or enqueued")]
|
||||
InvalidQueue(QueueType),
|
||||
#[error("invalid pad configuration")]
|
||||
InvalidPadConfig,
|
||||
#[error("invalid pipeline link configuration")]
|
||||
InvalidPipelineConfig,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<StreamOnError> for Errno {
|
||||
fn from(err: StreamOnError) -> Self {
|
||||
match err {
|
||||
StreamOnError::InvalidQueue(_) => Errno::EINVAL,
|
||||
StreamOnError::InvalidPadConfig => Errno::EPIPE,
|
||||
StreamOnError::InvalidPipelineConfig => Errno::ENOLINK,
|
||||
StreamOnError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_STREAMON` ioctl.
|
||||
pub fn streamon(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOnError> {
|
||||
match unsafe { ioctl::vidioc_streamon(fd.as_raw_fd(), &(queue as u32)) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(StreamOnError::InvalidQueue(queue)),
|
||||
Err(Errno::EPIPE) => Err(StreamOnError::InvalidPadConfig),
|
||||
Err(Errno::ENOLINK) => Err(StreamOnError::InvalidPipelineConfig),
|
||||
Err(e) => Err(StreamOnError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StreamOffError {
|
||||
#[error("queue type not supported")]
|
||||
InvalidQueue,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<StreamOffError> for Errno {
|
||||
fn from(err: StreamOffError) -> Self {
|
||||
match err {
|
||||
StreamOffError::InvalidQueue => Errno::EINVAL,
|
||||
StreamOffError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_STREAMOFF` ioctl.
|
||||
pub fn streamoff(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOffError> {
|
||||
match unsafe { ioctl::vidioc_streamoff(fd.as_raw_fd(), &(queue as u32)) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(StreamOffError::InvalidQueue),
|
||||
Err(e) => Err(StreamOffError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
210
libs/v4l2r/src/ioctl/subscribe_event.rs
Normal file
210
libs/v4l2r/src/ioctl/subscribe_event.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! Safe wrapper for the `VIDIOC_SUBSCRIBE_EVENT` and `VIDIOC_UNSUBSCRIBE_EVENT
|
||||
//! ioctls.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_event;
|
||||
use crate::bindings::v4l2_event_subscription;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SubscribeEventFlags: u32 {
|
||||
const SEND_INITIAL = bindings::V4L2_EVENT_SUB_FL_SEND_INITIAL;
|
||||
const ALLOW_FEEDBACK = bindings::V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventType {
|
||||
VSync,
|
||||
Eos,
|
||||
Ctrl(u32),
|
||||
FrameSync,
|
||||
SourceChange(u32),
|
||||
MotionDet,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EventConversionError {
|
||||
#[error("unrecognized event {0}")]
|
||||
UnrecognizedEvent(u32),
|
||||
#[error("unrecognized source change {0}")]
|
||||
UnrecognizedSourceChange(u32),
|
||||
}
|
||||
|
||||
impl TryFrom<&v4l2_event_subscription> for EventType {
|
||||
type Error = EventConversionError;
|
||||
|
||||
fn try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error> {
|
||||
Ok(match event.type_ {
|
||||
bindings::V4L2_EVENT_VSYNC => EventType::VSync,
|
||||
bindings::V4L2_EVENT_EOS => EventType::Eos,
|
||||
bindings::V4L2_EVENT_CTRL => EventType::Ctrl(event.id),
|
||||
bindings::V4L2_EVENT_FRAME_SYNC => EventType::FrameSync,
|
||||
bindings::V4L2_EVENT_SOURCE_CHANGE => EventType::SourceChange(event.id),
|
||||
bindings::V4L2_EVENT_MOTION_DET => EventType::MotionDet,
|
||||
e => return Err(EventConversionError::UnrecognizedEvent(e)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SrcChanges: u32 {
|
||||
const RESOLUTION = bindings::V4L2_EVENT_SRC_CH_RESOLUTION;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
SrcChangeEvent(SrcChanges),
|
||||
Eos,
|
||||
}
|
||||
|
||||
impl TryFrom<v4l2_event> for Event {
|
||||
type Error = EventConversionError;
|
||||
|
||||
fn try_from(value: v4l2_event) -> Result<Self, Self::Error> {
|
||||
Ok(match value.type_ {
|
||||
bindings::V4L2_EVENT_VSYNC => todo!(),
|
||||
bindings::V4L2_EVENT_EOS => Event::Eos,
|
||||
bindings::V4L2_EVENT_CTRL => todo!(),
|
||||
bindings::V4L2_EVENT_FRAME_SYNC => todo!(),
|
||||
bindings::V4L2_EVENT_SOURCE_CHANGE => {
|
||||
let changes = unsafe { value.u.src_change.changes };
|
||||
Event::SrcChangeEvent(
|
||||
SrcChanges::from_bits(changes)
|
||||
.ok_or(EventConversionError::UnrecognizedSourceChange(changes))?,
|
||||
)
|
||||
}
|
||||
bindings::V4L2_EVENT_MOTION_DET => todo!(),
|
||||
t => return Err(EventConversionError::UnrecognizedEvent(t)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn build_v4l2_event_subscription(
|
||||
event: EventType,
|
||||
flags: SubscribeEventFlags,
|
||||
) -> v4l2_event_subscription {
|
||||
v4l2_event_subscription {
|
||||
type_: match event {
|
||||
EventType::VSync => bindings::V4L2_EVENT_VSYNC,
|
||||
EventType::Eos => bindings::V4L2_EVENT_EOS,
|
||||
EventType::Ctrl(_) => bindings::V4L2_EVENT_CTRL,
|
||||
EventType::FrameSync => bindings::V4L2_EVENT_FRAME_SYNC,
|
||||
EventType::SourceChange(_) => bindings::V4L2_EVENT_SOURCE_CHANGE,
|
||||
EventType::MotionDet => bindings::V4L2_EVENT_MOTION_DET,
|
||||
},
|
||||
id: match event {
|
||||
EventType::Ctrl(id) => id,
|
||||
EventType::SourceChange(id) => id,
|
||||
_ => 0,
|
||||
},
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::{v4l2_event, v4l2_event_subscription};
|
||||
|
||||
nix::ioctl_read!(vidioc_dqevent, b'V', 89, v4l2_event);
|
||||
nix::ioctl_write_ptr!(vidioc_subscribe_event, b'V', 90, v4l2_event_subscription);
|
||||
nix::ioctl_write_ptr!(vidioc_unsubscribe_event, b'V', 91, v4l2_event_subscription);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SubscribeEventError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<SubscribeEventError> for Errno {
|
||||
fn from(err: SubscribeEventError) -> Self {
|
||||
match err {
|
||||
SubscribeEventError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_SUBSCRIBE_EVENT` ioctl.
|
||||
pub fn subscribe_event(
|
||||
fd: &impl AsRawFd,
|
||||
event: EventType,
|
||||
flags: SubscribeEventFlags,
|
||||
) -> Result<(), SubscribeEventError> {
|
||||
let subscription = build_v4l2_event_subscription(event, flags);
|
||||
|
||||
unsafe { ioctl::vidioc_subscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl.
|
||||
pub fn unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError> {
|
||||
let subscription = build_v4l2_event_subscription(event, SubscribeEventFlags::empty());
|
||||
|
||||
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl to unsubscribe from all events.
|
||||
pub fn unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError> {
|
||||
let subscription = v4l2_event_subscription {
|
||||
type_: bindings::V4L2_EVENT_ALL,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DqEventError {
|
||||
#[error("no event ready for dequeue")]
|
||||
NotReady,
|
||||
#[error("error while converting event")]
|
||||
EventConversionError,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for DqEventError {
|
||||
fn from(error: Errno) -> Self {
|
||||
match error {
|
||||
Errno::ENOENT => Self::NotReady,
|
||||
error => Self::IoctlError(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DqEventError> for Errno {
|
||||
fn from(err: DqEventError) -> Self {
|
||||
match err {
|
||||
DqEventError::NotReady => Errno::ENOENT,
|
||||
DqEventError::EventConversionError => Errno::EINVAL,
|
||||
DqEventError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError> {
|
||||
let mut event: v4l2_event = Default::default();
|
||||
|
||||
match unsafe { ioctl::vidioc_dqevent(fd.as_raw_fd(), &mut event) } {
|
||||
Ok(_) => Ok(event
|
||||
.try_into()
|
||||
.map_err(|_| DqEventError::EventConversionError)?),
|
||||
Err(Errno::ENOENT) => Err(DqEventError::NotReady),
|
||||
Err(e) => Err(DqEventError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
496
libs/v4l2r/src/lib.rs
Normal file
496
libs/v4l2r/src/lib.rs
Normal file
@@ -0,0 +1,496 @@
|
||||
//! This library provides the V4L2 pieces One-KVM needs for video capture:
|
||||
//!
|
||||
//! * The `ioctl` module provides direct, thin wrappers over the V4L2 ioctls
|
||||
//! with added safety. Note that "safety" here is in terms of memory safety:
|
||||
//! this layer won't guard against passing invalid data that the ioctls will
|
||||
//! reject - it just makes sure that data passed from and to the kernel can
|
||||
//! be accessed safely. Since this is a 1:1 mapping over the V4L2 ioctls,
|
||||
//! working at this level is a bit laborious, although more comfortable than
|
||||
//! doing the same in C.
|
||||
//!
|
||||
//! The upstream v4l2r crate also contains high-level decoder/encoder and C FFI
|
||||
//! layers. This vendored copy intentionally excludes those pieces and keeps the
|
||||
//! capture-oriented ioctl/memory surface only.
|
||||
//!
|
||||
#[doc(hidden)]
|
||||
pub mod bindings;
|
||||
pub mod ioctl;
|
||||
pub mod memory;
|
||||
|
||||
// This can be needed to match nix errors that we expose.
|
||||
pub use nix;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use enumn::N;
|
||||
use thiserror::Error;
|
||||
|
||||
// The goal of this library is to provide two layers of abstraction:
|
||||
// ioctl: direct, safe counterparts of the V4L2 ioctls.
|
||||
// device/queue/buffer: higher abstraction, still mapping to core V4L2 mechanics.
|
||||
|
||||
/// Possible directions for the queue
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum QueueDirection {
|
||||
Output,
|
||||
Capture,
|
||||
}
|
||||
|
||||
/// Possible classes for this queue.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum QueueClass {
|
||||
Video,
|
||||
Vbi,
|
||||
SlicedVbi,
|
||||
VideoOverlay,
|
||||
VideoMplane,
|
||||
Sdr,
|
||||
Meta,
|
||||
}
|
||||
|
||||
/// Types of queues currently supported by this library.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, N)]
|
||||
#[repr(u32)]
|
||||
pub enum QueueType {
|
||||
VideoCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
VideoOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
|
||||
VideoOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OVERLAY,
|
||||
VbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_CAPTURE,
|
||||
VbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VBI_OUTPUT,
|
||||
SlicedVbiCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_CAPTURE,
|
||||
SlicedVbiOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
|
||||
VideoOutputOverlay = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
|
||||
VideoCaptureMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
VideoOutputMplane = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
SdrCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_CAPTURE,
|
||||
SdrOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_SDR_OUTPUT,
|
||||
MetaCapture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_CAPTURE,
|
||||
MetaOutput = bindings::v4l2_buf_type_V4L2_BUF_TYPE_META_OUTPUT,
|
||||
}
|
||||
|
||||
impl QueueType {
|
||||
/// Returns the queue corresponding to the passed `direction` and `class`.
|
||||
pub fn from_dir_and_class(direction: QueueDirection, class: QueueClass) -> Self {
|
||||
match (direction, class) {
|
||||
(QueueDirection::Capture, QueueClass::Video) => Self::VideoCapture,
|
||||
(QueueDirection::Output, QueueClass::Video) => Self::VideoOutput,
|
||||
(QueueDirection::Capture, QueueClass::VideoOverlay) => Self::VideoOverlay,
|
||||
(QueueDirection::Output, QueueClass::VideoOverlay) => Self::VideoOutputOverlay,
|
||||
(QueueDirection::Capture, QueueClass::Vbi) => Self::VbiCapture,
|
||||
(QueueDirection::Output, QueueClass::Vbi) => Self::VbiOutput,
|
||||
(QueueDirection::Capture, QueueClass::SlicedVbi) => Self::SlicedVbiCapture,
|
||||
(QueueDirection::Output, QueueClass::SlicedVbi) => Self::SlicedVbiOutput,
|
||||
(QueueDirection::Capture, QueueClass::VideoMplane) => Self::VideoCaptureMplane,
|
||||
(QueueDirection::Output, QueueClass::VideoMplane) => Self::VideoOutputMplane,
|
||||
(QueueDirection::Capture, QueueClass::Sdr) => Self::SdrCapture,
|
||||
(QueueDirection::Output, QueueClass::Sdr) => Self::SdrOutput,
|
||||
(QueueDirection::Capture, QueueClass::Meta) => Self::MetaCapture,
|
||||
(QueueDirection::Output, QueueClass::Meta) => Self::MetaOutput,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the queue type is multiplanar.
|
||||
pub fn is_multiplanar(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the direction of the queue type (Output or Capture).
|
||||
pub fn direction(&self) -> QueueDirection {
|
||||
match self {
|
||||
QueueType::VideoOutput
|
||||
| QueueType::VideoOutputMplane
|
||||
| QueueType::VideoOverlay
|
||||
| QueueType::VideoOutputOverlay
|
||||
| QueueType::VbiOutput
|
||||
| QueueType::SlicedVbiOutput
|
||||
| QueueType::SdrOutput
|
||||
| QueueType::MetaOutput => QueueDirection::Output,
|
||||
|
||||
QueueType::VideoCapture
|
||||
| QueueType::VbiCapture
|
||||
| QueueType::SlicedVbiCapture
|
||||
| QueueType::VideoCaptureMplane
|
||||
| QueueType::SdrCapture
|
||||
| QueueType::MetaCapture => QueueDirection::Capture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class(&self) -> QueueClass {
|
||||
match self {
|
||||
QueueType::VideoCapture | QueueType::VideoOutput => QueueClass::Video,
|
||||
QueueType::VideoOverlay | QueueType::VideoOutputOverlay => QueueClass::VideoOverlay,
|
||||
QueueType::VbiCapture | QueueType::VbiOutput => QueueClass::Vbi,
|
||||
QueueType::SlicedVbiCapture | QueueType::SlicedVbiOutput => QueueClass::SlicedVbi,
|
||||
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => QueueClass::VideoMplane,
|
||||
QueueType::SdrCapture | QueueType::SdrOutput => QueueClass::Sdr,
|
||||
QueueType::MetaCapture | QueueType::MetaOutput => QueueClass::Meta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QueueType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Fourcc pixel format, used to pass formats to V4L2. It can be converted
|
||||
/// back and forth from a 32-bit integer, or a 4-bytes string.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub struct PixelFormat(u32);
|
||||
|
||||
impl PixelFormat {
|
||||
pub const fn from_u32(v: u32) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
pub const fn to_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn from_fourcc(n: &[u8; 4]) -> Self {
|
||||
Self(n[0] as u32 | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24)
|
||||
}
|
||||
|
||||
pub const fn to_fourcc(self) -> [u8; 4] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Fourcc in 32-bit integer format (like the ones passed in V4L2
|
||||
/// structures) into the matching pixel format.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// // Fourcc representation of NV12.
|
||||
/// let nv12 = u32::from_le(0x3231564e);
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(u32::from(f), nv12);
|
||||
/// ```
|
||||
impl From<u32> for PixelFormat {
|
||||
fn from(i: u32) -> Self {
|
||||
Self::from_u32(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a pixel format back to its 32-bit representation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// // Fourcc representation of NV12.
|
||||
/// let nv12 = u32::from_le(0x3231564e);
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(u32::from(f), nv12);
|
||||
/// ```
|
||||
impl From<PixelFormat> for u32 {
|
||||
fn from(format: PixelFormat) -> Self {
|
||||
format.to_u32()
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple way to convert a string litteral (e.g. b"NV12") into a pixel
|
||||
/// format that can be passed to V4L2.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// let nv12 = b"NV12";
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(&<[u8; 4]>::from(f), nv12);
|
||||
/// ```
|
||||
impl From<&[u8; 4]> for PixelFormat {
|
||||
fn from(n: &[u8; 4]) -> Self {
|
||||
Self::from_fourcc(n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a pixel format back to its 4-character representation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// let nv12 = b"NV12";
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(&<[u8; 4]>::from(f), nv12);
|
||||
/// ```
|
||||
impl From<PixelFormat> for [u8; 4] {
|
||||
fn from(format: PixelFormat) -> Self {
|
||||
format.to_fourcc()
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a debug string for this PixelFormat, including its hexadecimal
|
||||
/// and string representation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// // Fourcc representation of NV12.
|
||||
/// let nv12 = u32::from_le(0x3231564e);
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(format!("{:?}", f), "0x3231564e (NV12)");
|
||||
/// ```
|
||||
impl fmt::Debug for PixelFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_fmt(format_args!("0x{:08x} ({})", self.0, self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a displayable form of this PixelFormat.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::PixelFormat;
|
||||
/// // Fourcc representation of NV12.
|
||||
/// let nv12 = u32::from_le(0x3231564e);
|
||||
/// let f = PixelFormat::from(nv12);
|
||||
/// assert_eq!(f.to_string(), "NV12");
|
||||
/// ```
|
||||
impl fmt::Display for PixelFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc = self
|
||||
.0
|
||||
.to_le_bytes()
|
||||
.iter()
|
||||
.map(|&x| x as char)
|
||||
.collect::<String>();
|
||||
f.write_str(fourcc.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of a single plane in a format.
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct PlaneLayout {
|
||||
/// Useful size of the plane ; the backing memory must be at least that large.
|
||||
pub sizeimage: u32,
|
||||
/// Bytes per line of data. Only meaningful for image formats.
|
||||
pub bytesperline: u32,
|
||||
}
|
||||
|
||||
/// Unified representation of a V4L2 format capable of handling both single
|
||||
/// and multi-planar formats. When the single-planar API is used, only
|
||||
/// one plane shall be used - attempts to have more will be rejected by the
|
||||
/// ioctl wrappers.
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct Format {
|
||||
/// Width of the image in pixels.
|
||||
pub width: u32,
|
||||
/// Height of the image in pixels.
|
||||
pub height: u32,
|
||||
/// Format each pixel is encoded in.
|
||||
pub pixelformat: PixelFormat,
|
||||
/// Individual layout of each plane in this format. The exact number of planes
|
||||
/// is defined by `pixelformat`.
|
||||
pub plane_fmt: Vec<PlaneLayout>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum FormatConversionError {
|
||||
#[error("too many planes ({0}) specified,")]
|
||||
TooManyPlanes(usize),
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType(u32),
|
||||
}
|
||||
|
||||
impl TryFrom<bindings::v4l2_format> for Format {
|
||||
type Error = FormatConversionError;
|
||||
|
||||
fn try_from(fmt: bindings::v4l2_format) -> std::result::Result<Self, Self::Error> {
|
||||
match fmt.type_ {
|
||||
bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE
|
||||
| bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT => {
|
||||
let pix = unsafe { &fmt.fmt.pix };
|
||||
Ok(Format {
|
||||
width: pix.width,
|
||||
height: pix.height,
|
||||
pixelformat: PixelFormat::from(pix.pixelformat),
|
||||
plane_fmt: vec![PlaneLayout {
|
||||
bytesperline: pix.bytesperline,
|
||||
sizeimage: pix.sizeimage,
|
||||
}],
|
||||
})
|
||||
}
|
||||
bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
|
||||
| bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE => {
|
||||
let pix_mp = unsafe { &fmt.fmt.pix_mp };
|
||||
|
||||
// Can only happen if we passed a malformed v4l2_format.
|
||||
if pix_mp.num_planes as usize > pix_mp.plane_fmt.len() {
|
||||
return Err(Self::Error::TooManyPlanes(pix_mp.num_planes as usize));
|
||||
}
|
||||
|
||||
let mut plane_fmt = Vec::new();
|
||||
for i in 0..pix_mp.num_planes as usize {
|
||||
let plane = &pix_mp.plane_fmt[i];
|
||||
plane_fmt.push(PlaneLayout {
|
||||
sizeimage: plane.sizeimage,
|
||||
bytesperline: plane.bytesperline,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Format {
|
||||
width: pix_mp.width,
|
||||
height: pix_mp.height,
|
||||
pixelformat: PixelFormat::from(pix_mp.pixelformat),
|
||||
plane_fmt,
|
||||
})
|
||||
}
|
||||
t => Err(Self::Error::InvalidBufferType(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Quickly build a usable `Format` from a pixel format and resolution.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use v4l2r::Format;
|
||||
/// let f = Format::from((b"NV12", (640, 480)));
|
||||
/// assert_eq!(f.width, 640);
|
||||
/// assert_eq!(f.height, 480);
|
||||
/// assert_eq!(f.pixelformat.to_string(), "NV12");
|
||||
/// assert_eq!(f.plane_fmt.len(), 0);
|
||||
/// ```
|
||||
impl<T: Into<PixelFormat>> From<(T, (usize, usize))> for Format {
|
||||
fn from((pixel_format, (width, height)): (T, (usize, usize))) -> Self {
|
||||
Format {
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
pixelformat: pixel_format.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A more elegant representation for `v4l2_rect`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(left: i32, top: i32, width: u32, height: u32) -> Rect {
|
||||
Rect {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bindings::v4l2_rect> for Rect {
|
||||
fn from(rect: bindings::v4l2_rect) -> Self {
|
||||
Rect {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bindings::v4l2_selection> for Rect {
|
||||
fn from(selection: bindings::v4l2_selection) -> Self {
|
||||
Self::from(selection.r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rect> for bindings::v4l2_rect {
|
||||
fn from(rect: Rect) -> Self {
|
||||
bindings::v4l2_rect {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rect {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({}, {}), {}x{}",
|
||||
self.left, self.top, self.width, self.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent of `enum v4l2_colorspace`.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
|
||||
pub enum Colorspace {
|
||||
#[default]
|
||||
Default = bindings::v4l2_colorspace_V4L2_COLORSPACE_DEFAULT,
|
||||
Smpte170M = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE170M,
|
||||
Smpte240M = bindings::v4l2_colorspace_V4L2_COLORSPACE_SMPTE240M,
|
||||
Rec709 = bindings::v4l2_colorspace_V4L2_COLORSPACE_REC709,
|
||||
Bt878 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT878,
|
||||
SystemM470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_M,
|
||||
SystemBG470 = bindings::v4l2_colorspace_V4L2_COLORSPACE_470_SYSTEM_BG,
|
||||
Jpeg = bindings::v4l2_colorspace_V4L2_COLORSPACE_JPEG,
|
||||
Srgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB,
|
||||
OpRgb = bindings::v4l2_colorspace_V4L2_COLORSPACE_OPRGB,
|
||||
Bt2020 = bindings::v4l2_colorspace_V4L2_COLORSPACE_BT2020,
|
||||
Raw = bindings::v4l2_colorspace_V4L2_COLORSPACE_RAW,
|
||||
DciP3 = bindings::v4l2_colorspace_V4L2_COLORSPACE_DCI_P3,
|
||||
}
|
||||
|
||||
/// Equivalent of `enum v4l2_xfer_func`.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
|
||||
pub enum XferFunc {
|
||||
#[default]
|
||||
Default = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_DEFAULT,
|
||||
F709 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_709,
|
||||
Srgb = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SRGB,
|
||||
OpRgb = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_OPRGB,
|
||||
Smpte240M = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SMPTE240M,
|
||||
None = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_NONE,
|
||||
DciP3 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_DCI_P3,
|
||||
Smpte2084 = bindings::v4l2_xfer_func_V4L2_XFER_FUNC_SMPTE2084,
|
||||
}
|
||||
|
||||
/// Equivalent of `enum v4l2_ycbcr_encoding`.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
|
||||
pub enum YCbCrEncoding {
|
||||
#[default]
|
||||
Default = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_DEFAULT,
|
||||
E601 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_601,
|
||||
E709 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_709,
|
||||
Xv601 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_XV601,
|
||||
Xv709 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_XV709,
|
||||
Sycc = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_SYCC,
|
||||
Bt2020 = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_BT2020,
|
||||
Bt2020ConstLum = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_BT2020_CONST_LUM,
|
||||
Smpte240M = bindings::v4l2_ycbcr_encoding_V4L2_YCBCR_ENC_SMPTE240M,
|
||||
}
|
||||
|
||||
/// Equivalent of `enum v4l2_quantization`.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, N)]
|
||||
pub enum Quantization {
|
||||
#[default]
|
||||
Default = bindings::v4l2_quantization_V4L2_QUANTIZATION_DEFAULT,
|
||||
FullRange = bindings::v4l2_quantization_V4L2_QUANTIZATION_FULL_RANGE,
|
||||
LimRange = bindings::v4l2_quantization_V4L2_QUANTIZATION_LIM_RANGE,
|
||||
}
|
||||
279
libs/v4l2r/src/memory.rs
Normal file
279
libs/v4l2r/src/memory.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
//! Abstracts the different kinds of backing memory (`MMAP`, `USERPTR`,
|
||||
//! `DMABUF`) supported by V4L2.
|
||||
//!
|
||||
//! V4L2 allows to use either memory that is provided by the device itself
|
||||
//! (MMAP) or memory imported via user allocation (USERPTR) or the dma-buf
|
||||
//! subsystem (DMABUF). This results in 2 very different behaviors and 3 memory
|
||||
//! types that we need to model.
|
||||
//!
|
||||
//! The `Memory` trait represents these memory types and is thus implemented
|
||||
//! by exacly 3 types: `MMAP`, `UserPtr`, and `DMABuf`. These types do very
|
||||
//! little apart from providing a constant with the corresponding V4L2 memory
|
||||
//! type they model, and implement the `SelfBacked` (for MMAP) or `Imported`
|
||||
//! (for `UserPtr` and `DMABuf`) traits to indicate where their memory comes
|
||||
//! from.
|
||||
//!
|
||||
//! The `PlaneHandle` trait is used by types which can bind to one of these
|
||||
//! memory types, i.e. a type that can represent a single memory plane of a
|
||||
//! buffer. For `MMAP` memory this is a void type (since `MMAP` provides its
|
||||
//! own memory). `UserPtr`, a `Vec<u8>` can adequately be used as backing
|
||||
//! memory, and for `DMABuf` we will use a file descriptor. For handles that
|
||||
//! can be mapped into the user address-space (and indeed for `MMAP` this is
|
||||
//! the only way to access the memory), the `Mappable` trait can be implemented.
|
||||
//!
|
||||
//! The set of handles that make all the planes for a given buffer is
|
||||
//! represented by the `BufferHandles` trait. This trait is more abstract since
|
||||
//! we may want to decide at runtime the kind of memory we want to use ;
|
||||
//! therefore this trait does not have any particular kind of memory attached to
|
||||
//! it. `PrimitiveBufferHandles` is used to represent plane handles which memory
|
||||
//! type is known at compilation time, and thus includes a reference to a
|
||||
//! `PlaneHandle` type and by transition its `Memory` type.
|
||||
mod dmabuf;
|
||||
mod mmap;
|
||||
mod userptr;
|
||||
|
||||
pub use dmabuf::*;
|
||||
pub use mmap::*;
|
||||
pub use userptr::*;
|
||||
|
||||
use crate::{
|
||||
bindings::{self, v4l2_buffer__bindgen_ty_1, v4l2_plane__bindgen_ty_1},
|
||||
ioctl::{PlaneMapping, QueryBufPlane},
|
||||
};
|
||||
use enumn::N;
|
||||
use std::os::unix::io::AsFd;
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
|
||||
/// All the supported V4L2 memory types.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, N)]
|
||||
#[repr(u32)]
|
||||
pub enum MemoryType {
|
||||
Mmap = bindings::v4l2_memory_V4L2_MEMORY_MMAP,
|
||||
UserPtr = bindings::v4l2_memory_V4L2_MEMORY_USERPTR,
|
||||
Overlay = bindings::v4l2_memory_V4L2_MEMORY_OVERLAY,
|
||||
DmaBuf = bindings::v4l2_memory_V4L2_MEMORY_DMABUF,
|
||||
}
|
||||
|
||||
/// Trait describing a memory type that can be used to back V4L2 buffers.
|
||||
pub trait Memory: 'static {
|
||||
/// The memory type represented.
|
||||
const MEMORY_TYPE: MemoryType;
|
||||
/// The final type of the memory backing information in `struct v4l2_buffer` or `struct
|
||||
/// v4l2_plane`.
|
||||
type RawBacking;
|
||||
|
||||
/// Returns a reference to the memory backing information for `m` that is relevant for this
|
||||
/// memory type.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
|
||||
unsafe fn get_plane_buffer_backing(m: &bindings::v4l2_plane__bindgen_ty_1)
|
||||
-> &Self::RawBacking;
|
||||
|
||||
/// Returns a reference to the memory backing information for `m` that is relevant for this memory type.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
|
||||
unsafe fn get_single_planar_buffer_backing(
|
||||
m: &bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &Self::RawBacking;
|
||||
|
||||
/// Returns a mutable reference to the memory backing information for `m` that is relevant for
|
||||
/// this memory type.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
|
||||
unsafe fn get_plane_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking;
|
||||
|
||||
/// Returns a mutable reference to the memory backing information for `m` that is relevant for
|
||||
/// this memory type.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `m` indeed belongs to a buffer of this memory type.
|
||||
unsafe fn get_single_planar_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking;
|
||||
}
|
||||
|
||||
/// Trait for memory types that provide their own memory, i.e. MMAP.
|
||||
pub trait SelfBacked: Memory + Default {}
|
||||
|
||||
/// Trait for memory types to which external memory must be attached to, i.e. UserPtr and
|
||||
/// DMABuf.
|
||||
pub trait Imported: Memory {}
|
||||
|
||||
/// Trait for a handle that represents actual data for a single place. A buffer
|
||||
/// will have as many of these as it has planes.
|
||||
pub trait PlaneHandle: Debug + Send + 'static {
|
||||
/// The kind of memory the handle attaches to.
|
||||
type Memory: Memory;
|
||||
|
||||
/// Fill a plane of a multi-planar V4L2 buffer with the handle's information.
|
||||
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane);
|
||||
}
|
||||
|
||||
// Trait for plane handles that provide access to their content through a map()
|
||||
// method (typically, MMAP buffers).
|
||||
pub trait Mappable: PlaneHandle {
|
||||
/// Return a `PlaneMapping` enabling access to the memory of this handle.
|
||||
fn map<D: AsFd>(device: &D, plane_info: &QueryBufPlane) -> Option<PlaneMapping>;
|
||||
}
|
||||
|
||||
/// Trait for structures providing all the handles of a single buffer.
|
||||
pub trait BufferHandles: Send + Debug + 'static {
|
||||
/// Enumeration of all the `MemoryType` supported by this type. Typically
|
||||
/// a subset of `MemoryType` or `MemoryType` itself.
|
||||
type SupportedMemoryType: Into<MemoryType> + Send + Clone + Copy;
|
||||
|
||||
/// Number of planes.
|
||||
fn len(&self) -> usize;
|
||||
/// Fill a plane of a multi-planar V4L2 buffer with the `index` handle's information.
|
||||
fn fill_v4l2_plane(&self, index: usize, plane: &mut bindings::v4l2_plane);
|
||||
|
||||
/// Returns true if there are no handles here (unlikely).
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `BufferHandles` for all indexables of `PlaneHandle` (e.g. [`std::vec::Vec`]).
|
||||
///
|
||||
/// This is The simplest way to use primitive handles.
|
||||
impl<P, Q> BufferHandles for Q
|
||||
where
|
||||
P: PlaneHandle,
|
||||
Q: Send + Debug + 'static + Deref<Target = [P]>,
|
||||
{
|
||||
type SupportedMemoryType = MemoryType;
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.deref().len()
|
||||
}
|
||||
|
||||
fn fill_v4l2_plane(&self, index: usize, plane: &mut bindings::v4l2_plane) {
|
||||
self.deref()[index].fill_v4l2_plane(plane);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for plane handles for which the final memory type is known at compile
|
||||
/// time.
|
||||
pub trait PrimitiveBufferHandles: BufferHandles {
|
||||
type HandleType: PlaneHandle;
|
||||
const MEMORY_TYPE: Self::SupportedMemoryType;
|
||||
}
|
||||
|
||||
/// Implementation of `PrimitiveBufferHandles` for all indexables of `PlaneHandle` (e.g.
|
||||
/// [`std::vec::Vec`]).
|
||||
impl<P, Q> PrimitiveBufferHandles for Q
|
||||
where
|
||||
P: PlaneHandle,
|
||||
Q: Send + Debug + 'static + Deref<Target = [P]>,
|
||||
{
|
||||
type HandleType = P;
|
||||
const MEMORY_TYPE: Self::SupportedMemoryType = P::Memory::MEMORY_TYPE;
|
||||
}
|
||||
|
||||
/// Conversion from `v4l2_buffer`'s backing information to `v4l2_plane`'s.
|
||||
impl From<(&v4l2_buffer__bindgen_ty_1, MemoryType)> for v4l2_plane__bindgen_ty_1 {
|
||||
fn from((m, memory): (&v4l2_buffer__bindgen_ty_1, MemoryType)) -> Self {
|
||||
match memory {
|
||||
MemoryType::Mmap => v4l2_plane__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be MMAP.
|
||||
mem_offset: unsafe { m.offset },
|
||||
},
|
||||
MemoryType::UserPtr => v4l2_plane__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be USERPTR.
|
||||
userptr: unsafe { m.userptr },
|
||||
},
|
||||
MemoryType::DmaBuf => v4l2_plane__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be DMABUF.
|
||||
fd: unsafe { m.fd },
|
||||
},
|
||||
MemoryType::Overlay => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion from `v4l2_plane`'s backing information to `v4l2_buffer`'s.
|
||||
impl From<(&v4l2_plane__bindgen_ty_1, MemoryType)> for v4l2_buffer__bindgen_ty_1 {
|
||||
fn from((m, memory): (&v4l2_plane__bindgen_ty_1, MemoryType)) -> Self {
|
||||
match memory {
|
||||
MemoryType::Mmap => v4l2_buffer__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be MMAP.
|
||||
offset: unsafe { m.mem_offset },
|
||||
},
|
||||
MemoryType::UserPtr => v4l2_buffer__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be USERPTR.
|
||||
userptr: unsafe { m.userptr },
|
||||
},
|
||||
MemoryType::DmaBuf => v4l2_buffer__bindgen_ty_1 {
|
||||
// Safe because the buffer type is determined to be DMABUF.
|
||||
fd: unsafe { m.fd },
|
||||
},
|
||||
MemoryType::Overlay => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bindings::v4l2_buffer__bindgen_ty_1;
|
||||
use crate::bindings::v4l2_plane__bindgen_ty_1;
|
||||
use crate::memory::MemoryType;
|
||||
|
||||
#[test]
|
||||
// Purpose of this test is dubious as the members are overlapping anyway.
|
||||
fn plane_m_to_buffer_m() {
|
||||
let plane_m = v4l2_plane__bindgen_ty_1 {
|
||||
mem_offset: 0xfeedc0fe,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::Mmap)).offset },
|
||||
0xfeedc0fe
|
||||
);
|
||||
|
||||
let plane_m = v4l2_plane__bindgen_ty_1 {
|
||||
userptr: 0xfeedc0fe,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::UserPtr)).userptr },
|
||||
0xfeedc0fe
|
||||
);
|
||||
|
||||
let plane_m = v4l2_plane__bindgen_ty_1 { fd: 0x76543210 };
|
||||
assert_eq!(
|
||||
unsafe { v4l2_buffer__bindgen_ty_1::from((&plane_m, MemoryType::DmaBuf)).fd },
|
||||
0x76543210
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Purpose of this test is dubious as the members are overlapping anyway.
|
||||
fn buffer_m_to_plane_m() {
|
||||
let buffer_m = v4l2_buffer__bindgen_ty_1 { offset: 0xfeedc0fe };
|
||||
assert_eq!(
|
||||
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::Mmap)).mem_offset },
|
||||
0xfeedc0fe
|
||||
);
|
||||
|
||||
let buffer_m = v4l2_buffer__bindgen_ty_1 {
|
||||
userptr: 0xfeedc0fe,
|
||||
};
|
||||
assert_eq!(
|
||||
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::UserPtr)).userptr },
|
||||
0xfeedc0fe
|
||||
);
|
||||
|
||||
let buffer_m = v4l2_buffer__bindgen_ty_1 { fd: 0x76543210 };
|
||||
assert_eq!(
|
||||
unsafe { v4l2_plane__bindgen_ty_1::from((&buffer_m, MemoryType::DmaBuf)).fd },
|
||||
0x76543210
|
||||
);
|
||||
}
|
||||
}
|
||||
91
libs/v4l2r/src/memory/dmabuf.rs
Normal file
91
libs/v4l2r/src/memory/dmabuf.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
//! Operations specific to DMABuf-type buffers.
|
||||
use log::warn;
|
||||
|
||||
use super::*;
|
||||
use crate::{bindings, ioctl};
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::io::{AsFd, AsRawFd};
|
||||
|
||||
pub struct DmaBuf;
|
||||
|
||||
pub type DmaBufferHandles<T> = Vec<DmaBufHandle<T>>;
|
||||
|
||||
impl Memory for DmaBuf {
|
||||
const MEMORY_TYPE: MemoryType = MemoryType::DmaBuf;
|
||||
type RawBacking = RawFd;
|
||||
|
||||
unsafe fn get_plane_buffer_backing(
|
||||
m: &bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.fd
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing(
|
||||
m: &bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.fd
|
||||
}
|
||||
|
||||
unsafe fn get_plane_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.fd
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.fd
|
||||
}
|
||||
}
|
||||
|
||||
impl Imported for DmaBuf {}
|
||||
|
||||
pub trait DmaBufSource: AsRawFd + AsFd + Debug + Send {
|
||||
fn len(&self) -> u64;
|
||||
|
||||
/// Make Clippy happy.
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaBufSource for std::fs::File {
|
||||
fn len(&self) -> u64 {
|
||||
match self.metadata() {
|
||||
Err(_) => {
|
||||
warn!("Failed to compute File size for use as DMABuf, using 0...");
|
||||
0
|
||||
}
|
||||
Ok(m) => m.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle for a DMABUF plane. Any type that can provide a file descriptor is
|
||||
/// valid.
|
||||
#[derive(Debug)]
|
||||
pub struct DmaBufHandle<T: DmaBufSource>(pub T);
|
||||
|
||||
impl<T: DmaBufSource> From<T> for DmaBufHandle<T> {
|
||||
fn from(dmabuf: T) -> Self {
|
||||
DmaBufHandle(dmabuf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DmaBufSource + 'static> PlaneHandle for DmaBufHandle<T> {
|
||||
type Memory = DmaBuf;
|
||||
|
||||
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane) {
|
||||
plane.m.fd = self.0.as_raw_fd();
|
||||
plane.length = self.0.len() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DmaBufSource> DmaBufHandle<T> {
|
||||
pub fn map(&self) -> Result<PlaneMapping, ioctl::MmapError> {
|
||||
let len = self.0.len();
|
||||
|
||||
ioctl::mmap(&self.0, 0, len as u32)
|
||||
}
|
||||
}
|
||||
58
libs/v4l2r/src/memory/mmap.rs
Normal file
58
libs/v4l2r/src/memory/mmap.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! Operations specific to MMAP-type buffers.
|
||||
use super::*;
|
||||
use crate::{bindings, ioctl};
|
||||
use std::fmt::Debug;
|
||||
use std::os::fd::AsFd;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Mmap;
|
||||
|
||||
impl Memory for Mmap {
|
||||
const MEMORY_TYPE: MemoryType = MemoryType::Mmap;
|
||||
type RawBacking = u32;
|
||||
|
||||
unsafe fn get_plane_buffer_backing(
|
||||
m: &bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.mem_offset
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing(
|
||||
m: &bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.offset
|
||||
}
|
||||
|
||||
unsafe fn get_plane_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.mem_offset
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl SelfBacked for Mmap {}
|
||||
|
||||
/// Dummy handle for a MMAP plane, to use with APIs that require handles. MMAP
|
||||
/// buffers are backed by the device, and thus we don't need to attach any extra
|
||||
/// information to them.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MmapHandle;
|
||||
|
||||
// There is no information to fill with MMAP buffers ; the index is enough.
|
||||
impl PlaneHandle for MmapHandle {
|
||||
type Memory = Mmap;
|
||||
|
||||
fn fill_v4l2_plane(&self, _plane: &mut bindings::v4l2_plane) {}
|
||||
}
|
||||
|
||||
impl Mappable for MmapHandle {
|
||||
fn map<D: AsFd>(device: &D, plane_info: &QueryBufPlane) -> Option<PlaneMapping> {
|
||||
ioctl::mmap(device, plane_info.mem_offset, plane_info.length).ok()
|
||||
}
|
||||
}
|
||||
77
libs/v4l2r/src/memory/userptr.rs
Normal file
77
libs/v4l2r/src/memory/userptr.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
//! Operations specific to UserPtr-type buffers.
|
||||
|
||||
use super::*;
|
||||
use crate::bindings;
|
||||
|
||||
pub struct UserPtr;
|
||||
|
||||
impl Memory for UserPtr {
|
||||
const MEMORY_TYPE: MemoryType = MemoryType::UserPtr;
|
||||
type RawBacking = core::ffi::c_ulong;
|
||||
|
||||
unsafe fn get_plane_buffer_backing(
|
||||
m: &bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.userptr
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing(
|
||||
m: &bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &Self::RawBacking {
|
||||
&m.userptr
|
||||
}
|
||||
|
||||
unsafe fn get_plane_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_plane__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.userptr
|
||||
}
|
||||
|
||||
unsafe fn get_single_planar_buffer_backing_mut(
|
||||
m: &mut bindings::v4l2_buffer__bindgen_ty_1,
|
||||
) -> &mut Self::RawBacking {
|
||||
&mut m.userptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Imported for UserPtr {}
|
||||
|
||||
/// Handle for a USERPTR plane. These buffers are backed by userspace-allocated
|
||||
/// memory, which translates well into Rust's slice of `u8`s. Since slices also
|
||||
/// carry size information, we know that we are not passing unallocated areas
|
||||
/// of the address-space to the kernel.
|
||||
///
|
||||
/// USERPTR buffers have the particularity that the `length` field of `struct
|
||||
/// v4l2_buffer` must be set before doing a `QBUF` ioctl. This handle struct
|
||||
/// also takes care of that.
|
||||
#[derive(Debug)]
|
||||
pub struct UserPtrHandle<T: AsRef<[u8]> + Debug + Send + 'static>(pub T);
|
||||
|
||||
impl<T: AsRef<[u8]> + Debug + Send + Clone> Clone for UserPtrHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
UserPtrHandle(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Debug + Send + 'static> AsRef<[u8]> for UserPtrHandle<T> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Debug + Send> From<T> for UserPtrHandle<T> {
|
||||
fn from(buffer: T) -> Self {
|
||||
UserPtrHandle(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Debug + Send + 'static> PlaneHandle for UserPtrHandle<T> {
|
||||
type Memory = UserPtr;
|
||||
|
||||
fn fill_v4l2_plane(&self, plane: &mut bindings::v4l2_plane) {
|
||||
let slice = AsRef::<[u8]>::as_ref(&self.0);
|
||||
|
||||
plane.m.userptr = slice.as_ptr() as std::os::raw::c_ulong;
|
||||
plane.length = slice.len() as u32;
|
||||
}
|
||||
}
|
||||
10
libs/v4l2r/v4l2r_wrapper.h
Normal file
10
libs/v4l2r/v4l2r_wrapper.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifdef __ANDROID__
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#define MARK_FIX_753(name) const unsigned long int Fix753_##name = name;
|
||||
#include "fix753.h"
|
||||
Reference in New Issue
Block a user