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