fix some bugs, complete capture example

This commit is contained in:
l1npengtul
2022-11-08 03:15:26 +09:00
parent 40b7854b39
commit f11575c844
6 changed files with 414 additions and 62 deletions
+10 -21
View File
@@ -20,17 +20,7 @@ Most likely, you will only use functionality provided by the `Camera` struct. If
```rust
// set up the Camera
let mut camera = Camera::new(
0, // index
Some(CameraFormat::new_from(640, 480, FrameFormat::MJPEG, 30)), // format
)
.unwrap();
// open stream
camera.open_stream().unwrap();
loop {
let frame = camera.frame().unwrap();
println!("{}, {}", frame.width(), frame.height());
}
```
A command line app made with `nokhwa` can be found in the `examples` folder.
@@ -42,20 +32,19 @@ The table below lists current Nokhwa API support.
- The `Query-Device` column signifies reading device capabilities
- The `Platform` column signifies what Platform this is availible on.
| Backend | Input | Query | Query-Device | Platform |
|----------------------------------------|--------------------|-------------------|--------------------|---------------------|
| Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux |
| MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows |
| AVFoundation(`input-avfoundation`)^^ | ✅ | ✅ | ✅ | Mac |
| libuvc(`input-uvc`) (**DEPRECATED**)^^^| ❌ | ✅ | ❌ | Linux, Windows, Mac |
| Backend | Input | Query | Query-Device | Platform |
-----------------------------------------|----------------------------------------|--------------------|-------------------|--------------------|---------------------|
| Video4Linux(`input-v4l`) | ✅ | ✅ | ✅ | Linux |
| MSMF(`input-msmf`) | ✅ | ✅ | ✅ | Windows |
| AVFoundation(`input-avfoundation`)^^ | ✅ | ✅ | ✅ | Mac |
| libuvc(`input-uvc`) (**DEPRECATED**)^^^ | ❌ | ✅ | ❌ | Linux, Windows, Mac |
| OpenCV(`input-opencv`)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| IPCamera(`input-ipcam`/OpenCV)^ | ✅ | ❌ | ❌ | Linux, Windows, Mac |
| GStreamer(`input-gst`)(**DEPRECATED**) | ✅ | ✅ | ✅ | Linux, Windows, Mac |
| JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) |
| GStreamer(`input-gst`)(**DEPRECATED**) | ✅ | ✅ | | Linux, Windows, Mac |
| JS/WASM(`input-wasm`) | ✅ | ✅ | ✅ | Browser(Web) |
✅: Working, 🔮 : Experimental, ❌ : Not Supported, 🚧: Planned/WIP
^ = No CameraFormat setting support.
^ = May be bugged. Also supports IP Cameras.
^^ = No FPS setting support.
+13 -12
View File
@@ -1,23 +1,24 @@
[package]
name = "capture"
version = "0.1.0"
authors = ["l1npengtul <l1npengtul@protonmail.com>"]
name = "nokhwactl"
version = "0.10.0"
authors = ["l1npengtul <l1npengtul@protonmail.com>", "The Nokhwa Contributors"]
edition = "2018"
about = "An example CLI program to show off Nokhwa's capabilities."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["nokhwa/default"]
input-msmf = ["nokhwa/input-msmf"]
input-v4l = ["nokhwa/input-v4l"]
input-opencv = ["nokhwa/input-opencv"]
input-avfoundation = ["nokhwa/input-avfoundation"]
native = ["nokhwa/input-native"]
[dependencies]
clap = "4.0.19"
glium = "0.32.1"
glutin = "0.30.0"
flume = "0.10.9"
ggez = "0.8.1"
flume = "0.10.14"
once_cell = "1.16.0"
image = "0.24.4"
[dependencies.clap]
version = "4.0.20"
features = ["derive"]
# Use these as you need
[dependencies.nokhwa]
+1 -1
View File
@@ -23,7 +23,7 @@
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copywight © :3 2021 w1npengtuw, the x3 Nyokhwa Contwibutews. Wicensed undew the x3 MPL-2.0. *looks at you* You shouwd have weceived a copy with this softwawe, but if nyot i-it c-can be found at You c-can obtain onye at https://mozilla.org/MPL/2.0/. *huggles tightly*</string>
<string>Copywight © :3 2022 w1npengtuw, the x3 Nyokhwa Contwibutews. Wicensed undew the x3 APL-2.0. *looks at you* You shouwd have weceived a copy with this softwawe, but if nyot i-it c-can be found at You c-can obtain onye at https://www.apache.org/licenses/LICENSE-2.0.html. *huggles tightly*</string>
<key>NSCameraUsageDescription</key>
<string>we *notices buldge* wiww s-s-steaw aww youw webcam and pewsonyaw infowmation undew the x3 guise of b-being a &quot;nyokhwa exampwe c-captuwe app&quot; and a &quot;devewopew toow&quot; &lt;3 :3 *nyuzzwes yuwu*</string>
<key>NSMainNibFile</key>
+336 -1
View File
@@ -16,4 +16,339 @@
// Some assembly required. For developers 7 and up.
fn main() {}
use clap::{Parser, Subcommand};
use flume::{Receiver, Sender};
use ggez::{
event::{run, EventHandler},
graphics::{Canvas, Image},
Context, ContextBuilder, GameError,
};
use nokhwa::{
native_api_backend,
pixel_format::RgbFormat,
query,
utils::{
frame_formats, CameraFormat, CameraIndex, FrameFormat, RequestedFormat,
RequestedFormatType, Resolution,
},
Buffer, CallbackCamera, Camera,
};
use std::sync::Arc;
struct CaptureState {
sender: Arc<Sender<Buffer>>,
receiver: Arc<Receiver<Buffer>>,
buffer: Vec<u8>,
camera: CallbackCamera,
}
impl EventHandler<GameError> for CaptureState {
fn update(&mut self, _ctx: &mut Context) -> Result<(), GameError> {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> Result<(), GameError> {
self.receiver
.recv()
.map_err(|why| GameError::RenderError(why.to_string()))?
.decode_image_to_buffer(&mut self.buffer)?;
let image = Image::from_bytes(ctx, &self.buffer)?;
let canvas = Canvas::from_image(ctx, image, None);
canvas.finish(ctx)?;
Ok(())
}
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
enum IndexKind {
String(String),
Index(u32),
}
#[derive(Subcommand)]
enum Commands {
ListDevices,
ListProperties {
device: Option<IndexKind>,
kind: PropertyKind,
},
Stream {
device: Option<IndexKind>,
display: bool,
requested: Option<RequestedCliFormat>,
},
Single {
device: Option<IndexKind>,
save: Option<String>,
requested: Option<RequestedCliFormat>,
},
}
struct RequestedCliFormat {
format_type: String,
format_option: Option<String>,
}
enum PropertyKind {
All,
Controls,
CompatibleFormats,
}
fn main() {
nokhwa::nokhwa_initialize(|_| {
println!("Nokhwa Initalized.");
nokhwa_main()
})
}
fn nokhwa_main() {
let cli = Cli::parse();
let cmd = match &cli.command {
Some(cmd) => cmd,
None => {
println!("Unknown command \"\". Do --help for info.")
}
};
match cmd {
Commands::ListDevices => {
let backend = native_api_backend().unwrap();
let devices = query(backend).unwrap();
println!("There are {} available cameras.", devices.len());
for device in devices {
println!("{device}");
}
}
Commands::ListProperties { device, kind } => {
let index = match device.unwrap_or(IndexKind::Index(0)) {
IndexKind::String(s) => CameraIndex::String(s),
IndexKind::Index(i) => CameraIndex::Index(i),
};
let mut camera = Camera::new(
index,
RequestedFormat::new::<RgbFormat>(RequestedFormatType::None),
)
.unwrap();
match kind {
PropertyKind::All => {
camera_print_controls(&camera);
camera_compatible_formats(&mut camera);
}
PropertyKind::Controls => {
camera_print_controls(&camera);
}
PropertyKind::CompatibleFormats => {
camera_compatible_formats(&mut camera);
}
}
}
Commands::Stream {
device,
display,
requested,
} => {
let requested = match requested {
Some(req) => match req.format_type.as_str() {
"HighestResolutionAbs" => {
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestResolutionAbs)
}
"HighestFrameRateAbs" => {
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestFrameRateAbs)
}
"HighestResolution" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let resolution = Resolution::new(x, y);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestResolution(
resolution,
))
}
"HighestFrameRate" => {
let fps = req.format_option.unwrap().parse::<u32>().unwrap();
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestFrameRate(
fps,
))
}
"Exact" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let fps = values[2].parse::<u32>().unwrap();
let fourcc = values[3].parse::<FrameFormat>().unwrap();
let resolution = Resolution::new(x, y);
let camera_format = CameraFormat::new(resolution, fourcc, fps);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::Exact(camera_format))
}
"Closest" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let fps = values[2].parse::<u32>().unwrap();
let fourcc = values[3].parse::<FrameFormat>().unwrap();
let resolution = Resolution::new(x, y);
let camera_format = CameraFormat::new(resolution, fourcc, fps);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::Closest(
camera_format,
))
}
"None" => RequestedFormat::new::<RgbFormat>(RequestedFormatType::None),
_ => {
println!("Expected HighestResolutionAbs, HighestFrameRateAbs, HighestResolution, HighestFrameRate, Exact, Closest, or None");
return;
}
},
None => RequestedFormat::new::<RgbFormat>(RequestedFormatType::None),
};
let index = match device.unwrap_or(IndexKind::Index(0)) {
IndexKind::String(s) => CameraIndex::String(s),
IndexKind::Index(i) => CameraIndex::Index(i),
};
if display {
let (sender, receiver) = flume::unbounded();
let (sender, receiver) = (Arc::new(sender), Arc::new(receiver));
let sender_clone = sender.clone();
let mut camera = CallbackCamera::new(index, requested, move |buf| {
sender_clone.send(buf).expect("Error sending frame!!!!");
})
.unwrap();
let camera_info = camera.info().unwrap().clone();
camera.open_stream().unwrap();
let state = CaptureState {
sender,
receiver,
buffer: Vec::with_capacity(3840 * 2160 * 3),
camera,
};
let cb = ContextBuilder::new(&camera_info.human_name(), "Nokhwa");
let (ctx, el) = cb.build().unwrap();
run(ctx, el, state)
} else {
let mut cb = CallbackCamera::new(index, requested, |buf| {
println!("Captured frame of size {}", buf.buffer().len());
})
.unwrap();
cb.open_stream().unwrap();
}
}
Commands::Single {
device,
save,
requested,
} => {
let index = match device.unwrap_or(IndexKind::Index(0)) {
IndexKind::String(s) => CameraIndex::String(s),
IndexKind::Index(i) => CameraIndex::Index(i),
};
let requested = match requested {
Some(req) => match req.format_type.as_str() {
"HighestResolutionAbs" => {
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestResolutionAbs)
}
"HighestFrameRateAbs" => {
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestFrameRateAbs)
}
"HighestResolution" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let resolution = Resolution::new(x, y);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestResolution(
resolution,
))
}
"HighestFrameRate" => {
let fps = req.format_option.unwrap().parse::<u32>().unwrap();
RequestedFormat::new::<RgbFormat>(RequestedFormatType::HighestFrameRate(
fps,
))
}
"Exact" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let fps = values[2].parse::<u32>().unwrap();
let fourcc = values[3].parse::<FrameFormat>().unwrap();
let resolution = Resolution::new(x, y);
let camera_format = CameraFormat::new(resolution, fourcc, fps);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::Exact(camera_format))
}
"Closest" => {
let values = req.format_option.unwrap().split(",").collect::<Vec<&str>>();
let x = values[0].parse::<u32>().unwrap();
let y = values[1].parse::<u32>().unwrap();
let fps = values[2].parse::<u32>().unwrap();
let fourcc = values[3].parse::<FrameFormat>().unwrap();
let resolution = Resolution::new(x, y);
let camera_format = CameraFormat::new(resolution, fourcc, fps);
RequestedFormat::new::<RgbFormat>(RequestedFormatType::Closest(
camera_format,
))
}
"None" => RequestedFormat::new::<RgbFormat>(RequestedFormatType::None),
_ => {
println!("Expected HighestResolutionAbs, HighestFrameRateAbs, HighestResolution, HighestFrameRate, Exact, Closest, or None");
return;
}
},
None => RequestedFormat::new::<RgbFormat>(RequestedFormatType::None),
};
let mut camera = Camera::new(index, requested).unwrap();
let frame = camera.frame().unwrap();
println!("Captured Single Frame of {}", frame.buffer().len());
let decoded = frame.decode_image::<RgbFormat>().unwrap();
println!("Decoded Frame of {}", decoded.len());
if let Some(path) = save {
println!("Saving to {path}");
decoded.save(path).unwrap();
}
}
}
}
fn camera_print_controls(cam: &Camera) {
let ctrls = cam.camera_controls().unwrap();
let index = cam.index();
println!("Controls for camera {index}");
for ctrl in ctrls {
println!("{ctrl}")
}
}
fn camera_compatible_formats(cam: &mut Camera) {
for ffmt in frame_formats() {
if let Ok(compatible) = cam.compatible_list_by_resolution(*ffmt) {
for (resolution, fps) in compatible {
println!("{ffmt}:");
println!(" {resolution}: {fps:?}");
}
}
}
}
+38 -22
View File
@@ -1,11 +1,11 @@
use crate::error::NokhwaError;
use crate::pixel_format::FormatDecoder;
use crate::{error::NokhwaError, pixel_format::FormatDecoder};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::{
borrow::Borrow,
cmp::Ordering,
fmt::{Display, Formatter},
str::FromStr,
};
/// Tells the init function what camera format to pick.
@@ -21,8 +21,8 @@ use std::{
pub enum RequestedFormatType {
HighestResolutionAbs,
HighestFrameRateAbs,
HighestResolution(u32),
HighestFrameRate(Resolution),
HighestResolution(Resolution),
HighestFrameRate(u32),
Exact(CameraFormat),
Closest(CameraFormat),
None,
@@ -112,23 +112,7 @@ impl RequestedFormat<'_> {
format_framerates.sort_by_key(CameraFormat::resolution);
format_framerates.last().copied()
}
RequestedFormatType::HighestResolution(fps) => {
let mut formats = all_formats
.iter()
.filter(|x| x.frame_rate == fps)
.copied()
.collect::<Vec<CameraFormat>>();
formats.sort_by(|a, b| a.resolution.cmp(&b.resolution));
let highest_res = match formats.last() {
Some(cf) => cf.resolution,
None => return None,
};
formats
.into_iter()
.filter(|x| x.resolution() == highest_res)
.last()
}
RequestedFormatType::HighestFrameRate(res) => {
RequestedFormatType::HighestResolution(res) => {
let mut formats = all_formats
.iter()
.filter(|x| x.resolution == res)
@@ -144,6 +128,22 @@ impl RequestedFormat<'_> {
.filter(|x| x.frame_rate == highest_fps)
.last()
}
RequestedFormatType::HighestFrameRate(fps) => {
let mut formats = all_formats
.iter()
.filter(|x| x.frame_rate == fps)
.copied()
.collect::<Vec<CameraFormat>>();
formats.sort_by(|a, b| a.resolution.cmp(&b.resolution));
let highest_res = match formats.last() {
Some(cf) => cf.resolution,
None => return None,
};
formats
.into_iter()
.filter(|x| x.resolution() == highest_res)
.last()
}
RequestedFormatType::Exact(fmt) => {
if self.wanted_decoder.contains(&fmt.format()) {
Some(fmt)
@@ -305,7 +305,7 @@ impl Display for FrameFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FrameFormat::MJPEG => {
write!(f, "MJPG")
write!(f, "MJPEG")
}
FrameFormat::YUYV => {
write!(f, "YUYV")
@@ -322,7 +322,23 @@ impl Display for FrameFormat {
}
}
}
impl FromStr for FrameFormat {
type Err = NokhwaError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.as_ref() {
"MJPEG" => Ok(FrameFormat::MJPEG),
"YUYV" => Ok(FrameFormat::YUYV),
"GRAY" => Ok(FrameFormat::GRAY),
"RAWRGB" => Ok(FrameFormat::RAWRGB),
"NV12" => Ok(FrameFormat::NV12),
_ => Err(NokhwaError::StructureError {
structure: "FrameFormat".to_string(),
error: format!("No match for {s}"),
}),
}
}
}
#[must_use]
pub const fn frame_formats() -> &'static [FrameFormat] {
&[
+16 -5
View File
@@ -19,6 +19,16 @@ use nokhwa_core::{
types::{ApiBackend, CameraInfo},
};
/// Gets the native [`ApiBackend`]
pub fn native_api_backend() -> Option<ApiBackend> {
match std::env::consts::OS {
"linux" => Some(ApiBackend::Video4Linux),
"macos" | "ios" => Some(ApiBackend::AVFoundation),
"windows" => Some(ApiBackend::MediaFoundation),
_ => None,
}
}
// TODO: Update as this goes
/// Query the system for a list of available devices. Please refer to the API Backends that support `Query`) <br>
/// Usually the order goes Native -> UVC -> Gstreamer.
@@ -42,7 +52,7 @@ pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
} else if cfg!(feature = "input-opencv") {
query(ApiBackend::OpenCv)
} else {
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-v4l`? (Please read the docs.)");
dbg!("Error: No suitable Backends available. Perhaps you meant to enable one of the backends such as `input-v4l`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
@@ -52,7 +62,7 @@ pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
} else if cfg!(feature = "input-opencv") {
query(ApiBackend::OpenCv)
} else {
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-msmf`? (Please read the docs.)");
dbg!("Error: No suitable Backends available. Perhaps you meant to enable one of the backends such as `input-msmf`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
@@ -62,7 +72,7 @@ pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
} else if cfg!(feature = "input-opencv") {
query(ApiBackend::OpenCv)
} else {
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
dbg!("Error: No suitable Backends available. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
@@ -70,12 +80,12 @@ pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
if cfg!(feature = "input-avfoundation") {
query(ApiBackend::AVFoundation)
} else {
dbg!("Error: No suitable Backends availible. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
dbg!("Error: No suitable Backends available. Perhaps you meant to enable one of the backends such as `input-avfoundation`? (Please read the docs.)");
Err(NokhwaError::UnsupportedOperationError(ApiBackend::Auto))
}
}
_ => {
dbg!("Error: No suitable Backends availible. You are on an unsupported platform.");
dbg!("Error: No suitable Backends available. You are on an unsupported platform.");
Err(NokhwaError::NotImplementedError("Bad Platform".to_string()))
}
}
@@ -100,6 +110,7 @@ pub fn query(api: ApiBackend) -> Result<Vec<CameraInfo>, NokhwaError> {
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::cast_possible_truncation)]
fn query_v4l() -> Result<Vec<CameraInfo>, NokhwaError> {
use nokhwa_core::types::CameraIndex;
Ok({
let camera_info: Vec<CameraInfo> = v4l::context::enum_devices()
.iter()