mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
fix gstreamer streaming, impl gstreamer query-device capabilities, fix lints, attempt fix openCV
This commit is contained in:
+12
-8
@@ -18,10 +18,9 @@ input-v4l = ["v4l"]
|
||||
input-opencv = ["opencv", "opencv/clang-runtime"]
|
||||
input-ipcam = ["input-opencv"]
|
||||
input-msmf = ["nokhwa-bindings-windows"]
|
||||
input-gst = ["gstreamer", "glib", "gstreamer-app"]
|
||||
input-gst = ["gstreamer", "glib", "gstreamer-app", "gstreamer-video", "regex"]
|
||||
output-wgpu = ["wgpu"]
|
||||
output-threaded = ["parking_lot"]
|
||||
docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "output-wgpu", "output-threaded"]
|
||||
docs-only = ["input-uvc", "input-v4l", "input-opencv", "input-ipcam", "input-gst", "output-wgpu"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.24"
|
||||
@@ -44,7 +43,7 @@ version = "0.2.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.wgpu]
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.opencv]
|
||||
@@ -57,21 +56,26 @@ version = "0.1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.gstreamer]
|
||||
version = "0.16.7"
|
||||
version = "0.17.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.gstreamer-app]
|
||||
version = "0.16.5"
|
||||
version = "0.17.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.gstreamer-video]
|
||||
version = "0.17.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.glib]
|
||||
version = "0.14.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.parking_lot]
|
||||
version = "0.11.1"
|
||||
[dependencies.regex]
|
||||
version = "1.4.6"
|
||||
optional = true
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
features = ["docs-only"]
|
||||
@@ -42,19 +42,21 @@ The table below lists current Nokhwa API support.
|
||||
- The `Query-Device` column signifies reading device capabilities
|
||||
- The `OS` column signifies what OS this is availible on.
|
||||
|
||||
| Backend | Input | Query | Query-Device | OS |
|
||||
|--------------------------------|--------------------|--------------------|--------------------|---------------------|
|
||||
| Video4Linux(`input-v4l`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux |
|
||||
| libuvc(`input-uvc`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
| OpenCV(`input-opencv`) | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
| IPCamera(`input-ipcam`/OpenCV) | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
| GStreamer(`input-gst`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
| FFMpeg | * | * | * | Linux, Windows, Mac |
|
||||
| AVFoundation | * | * | * | Mac |
|
||||
| MSMF | * | * | * | Windows |
|
||||
| JS/WASM | * | * | * | Web |
|
||||
| Backend | Input | Query | Query-Device | OS |
|
||||
|---------------------------------|--------------------|--------------------|--------------------|---------------------|
|
||||
| Video4Linux(`input-v4l`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux |
|
||||
| libuvc(`input-uvc`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
| OpenCV(`input-opencv`)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
| IPCamera(`input-ipcam`/OpenCV)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
| GStreamer(`input-gst`)^ | :white_check_mark: | :x: | :white_check_mark: | Linux, Windows, Mac |
|
||||
| FFMpeg | * | * | * | Linux, Windows, Mac |
|
||||
| AVFoundation | * | * | * | Mac |
|
||||
| MSMF | * | * | * | Windows |
|
||||
| JS/WASM | * | * | * | Web |
|
||||
|
||||
:white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned
|
||||
:white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned
|
||||
|
||||
^ = No CameraFormat setting support.
|
||||
|
||||
## Feature
|
||||
The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend.
|
||||
|
||||
@@ -15,4 +15,4 @@ flume = "0.10.7"
|
||||
# Use these as you need
|
||||
[dependencies.nokhwa]
|
||||
path = "../../../nokhwa"
|
||||
features = ["input-uvc", "input-v4l", "input-ipcam", "input-opencv", "input-gst"]
|
||||
features = ["docs-only"]
|
||||
|
||||
@@ -4,7 +4,7 @@ use glium::{
|
||||
IndexBuffer, Surface, Texture2d, VertexBuffer,
|
||||
};
|
||||
use glutin::{event_loop::EventLoop, window::WindowBuilder, ContextBuilder};
|
||||
use nokhwa::{query_devices, Camera, CaptureAPIBackend, FrameFormat, NetworkCamera};
|
||||
use nokhwa::{query_devices, Camera, CaptureAPIBackend, FrameFormat, NetworkCamera, Resolution};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -24,6 +24,7 @@ fn main() {
|
||||
.value_name("BACKEND")
|
||||
// TODO: Update as new backends are added!
|
||||
.help("Query the system? Pass AUTO for automatic backend, UVC to query using UVC, V4L to query using Video4Linux, GST to query using Gstreamer.. Will post the list of availible devices.")
|
||||
.default_value("AUTO")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("capture")
|
||||
.short("c")
|
||||
@@ -32,6 +33,11 @@ fn main() {
|
||||
.help("Capture from device? Pass the device index or string. Defaults to 0. If the input is not a number, it will be assumed an IPCamera")
|
||||
.default_value("0")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("query-device")
|
||||
.short("s")
|
||||
.long("query-device")
|
||||
.help("Show device queries from `compatible_fourcc` and `compatible_list_by_resolution`. Requires -c to be passed to work.")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("width")
|
||||
.short("w")
|
||||
.long("width")
|
||||
@@ -149,10 +155,41 @@ fn main() {
|
||||
{
|
||||
let mut camera =
|
||||
Camera::new_with(index, width, height, fps, format, backend_value).unwrap();
|
||||
|
||||
if matches_clone.is_present("query-device") {
|
||||
match camera.compatible_fourcc() {
|
||||
Ok(fcc) => {
|
||||
for ff in fcc {
|
||||
match camera.compatible_list_by_resolution(ff) {
|
||||
Ok(compat) => {
|
||||
println!("For FourCC {}", ff);
|
||||
for (res, fps) in compat {
|
||||
println!("{}x{}: {:?}", res.width(), res.height(), fps);
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
println!("Failed to get compatible resolution/FPS list for FrameFormat {}: {}", ff, why.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
println!("Failed to get compatible FourCC: {}", why.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// open stream
|
||||
camera.open_stream().unwrap();
|
||||
loop {
|
||||
let frame = camera.get_frame().unwrap();
|
||||
let frame = camera.frame().unwrap();
|
||||
println!(
|
||||
"Captured frame {}x{} @ {}FPS size {}",
|
||||
frame.width(),
|
||||
frame.height(),
|
||||
fps,
|
||||
frame.len()
|
||||
);
|
||||
send.send(frame).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -163,7 +200,14 @@ fn main() {
|
||||
.expect("Invalid IP!");
|
||||
ip_camera.open_stream().unwrap();
|
||||
loop {
|
||||
let frame = ip_camera.get_frame().unwrap();
|
||||
let frame = ip_camera.frame().unwrap();
|
||||
println!(
|
||||
"Captured frame {}x{} @ {}FPS size {}",
|
||||
frame.width(),
|
||||
frame.height(),
|
||||
fps,
|
||||
frame.len()
|
||||
);
|
||||
send.send(frame).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+570
-183
@@ -1,28 +1,45 @@
|
||||
use crate::{
|
||||
CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat, NokhwaError,
|
||||
Resolution,
|
||||
mjpeg_to_rgb888, yuyv422_to_rgb888, CameraFormat, CameraInfo, CaptureAPIBackend,
|
||||
CaptureBackendTrait, FrameFormat, NokhwaError, Resolution,
|
||||
};
|
||||
use flume::Receiver;
|
||||
use glib::Quark;
|
||||
use gstreamer::{
|
||||
glib::Cast, Caps, ClockTime, DeviceExt, DeviceMonitor, DeviceMonitorExt,
|
||||
DeviceMonitorExtManual, Element, ElementExtManual, GstBinExt, State,
|
||||
element_error,
|
||||
glib::Cast,
|
||||
prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual, ElementExt, GstBinExt},
|
||||
Bin, Caps, ClockTime, DeviceMonitor, Element, FlowError, FlowSuccess, MessageView,
|
||||
ResourceError, State,
|
||||
};
|
||||
use gstreamer_app::AppSink;
|
||||
use gstreamer_app::{AppSink, AppSinkCallbacks};
|
||||
use gstreamer_video::{VideoFormat, VideoInfo};
|
||||
use image::{ImageBuffer, Rgb};
|
||||
use regex::Regex;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
type PipelineGenRet = (Element, AppSink, Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>);
|
||||
|
||||
/// The backend struct that interfaces with GStreamer.
|
||||
/// To see what this does, please see [`CaptureBackendTrait`].
|
||||
/// # Quirks
|
||||
/// - [`FrameFormat`]s are **not** respected.
|
||||
/// - `Drop`-ing this may cause a `panic`.
|
||||
pub struct GStreamerCaptureDevice {
|
||||
pipeline: Element,
|
||||
app_sink: AppSink,
|
||||
camera_format: CameraFormat,
|
||||
camera_info: CameraInfo,
|
||||
receiver: Receiver<ImageBuffer<Rgb<u8>, Vec<u8>>>,
|
||||
caps: Option<Caps>,
|
||||
}
|
||||
|
||||
impl GStreamerCaptureDevice {
|
||||
/// Creates a new capture device using the `GStreamer` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
|
||||
///
|
||||
/// GStreamer uses `v4l2src` on linux, `ksvideosrc` on windows, and `autovideosrc` on mac.
|
||||
///
|
||||
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
|
||||
/// # Errors
|
||||
/// This function will error if the camera is currently busy or if GStreamer can't read device information.
|
||||
pub fn new(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
|
||||
let camera_format = match cam_fmt {
|
||||
Some(fmt) => fmt,
|
||||
@@ -36,7 +53,7 @@ impl GStreamerCaptureDevice {
|
||||
)));
|
||||
}
|
||||
|
||||
let camera_info = {
|
||||
let (camera_info, caps) = {
|
||||
let device_monitor = DeviceMonitor::new();
|
||||
let video_caps = match Caps::from_str("video/x-raw") {
|
||||
Ok(cap) => cap,
|
||||
@@ -62,7 +79,7 @@ impl GStreamerCaptureDevice {
|
||||
why.to_string()
|
||||
)));
|
||||
}
|
||||
let device = match device_monitor.get_devices().get(index) {
|
||||
let device = match device_monitor.devices().get(index) {
|
||||
Some(dev) => dev.clone(),
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
@@ -72,138 +89,39 @@ impl GStreamerCaptureDevice {
|
||||
}
|
||||
};
|
||||
device_monitor.stop();
|
||||
|
||||
CameraInfo::new(
|
||||
DeviceExt::get_display_name(&device).to_string(),
|
||||
DeviceExt::get_device_class(&device).to_string(),
|
||||
"".to_string(),
|
||||
index,
|
||||
let caps = device.caps();
|
||||
(
|
||||
CameraInfo::new(
|
||||
DeviceExt::display_name(&device).to_string(),
|
||||
DeviceExt::device_class(&device).to_string(),
|
||||
"".to_string(),
|
||||
index,
|
||||
),
|
||||
caps,
|
||||
)
|
||||
};
|
||||
|
||||
let pipeline = match gstreamer::parse_launch(&*webcam_pipeline(
|
||||
camera_format.width(),
|
||||
camera_format.height(),
|
||||
camera_format.framerate(),
|
||||
format!("{}", index).as_str(),
|
||||
)) {
|
||||
Ok(pl) => pl,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to initialize GStreamer Pipeline with string {} : {}",
|
||||
webcam_pipeline(
|
||||
camera_format.width(),
|
||||
camera_format.height(),
|
||||
camera_format.framerate(),
|
||||
format!("{}", index).as_str()
|
||||
),
|
||||
why.to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let sink = {
|
||||
let bin = match pipeline.clone().dynamic_cast::<gstreamer::Bin>() {
|
||||
Ok(bn) => bn,
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast Element to Bin".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let snk = match bin.get_by_name("appsink") {
|
||||
Some(sk) => sk,
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast get appsink from pipeline!".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
snk
|
||||
};
|
||||
let app_sink = match sink.clone().dynamic_cast::<gstreamer_app::AppSink>() {
|
||||
Ok(ap_sk) => ap_sk,
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast Element to AppSink".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let (pipeline, app_sink, receiver) = generate_pipeline(camera_format, index)?;
|
||||
|
||||
Ok(GStreamerCaptureDevice {
|
||||
pipeline,
|
||||
app_sink,
|
||||
camera_format,
|
||||
camera_info,
|
||||
receiver,
|
||||
caps,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new capture device using the `GStreamer` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
|
||||
///
|
||||
/// GStreamer uses `v4l2src` on linux, `ksvideosrc` on windows, and `autovideosrc` on mac.
|
||||
/// # Errors
|
||||
/// This function will error if the camera is currently busy or if GStreamer can't read device information.
|
||||
pub fn new_with(index: usize, width: u32, height: u32, fps: u32) -> Result<Self, NokhwaError> {
|
||||
let cam_fmt = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps);
|
||||
GStreamerCaptureDevice::new(index, Some(cam_fmt))
|
||||
}
|
||||
|
||||
/// Regenerates the GStreamer Pipeline. Mostly used by internal functions, although made available for your convenience.
|
||||
/// Equivalent to calling [`GStreamerCaptureDevice::new`] but it sets on the current backend object.
|
||||
/// # Errors
|
||||
/// If the GStreamer fails to capture the object or
|
||||
pub fn regenerate_pipeline(&mut self) -> Result<(), NokhwaError> {
|
||||
let pipeline = match gstreamer::parse_launch(&*webcam_pipeline(
|
||||
self.camera_format.width(),
|
||||
self.camera_format.height(),
|
||||
self.camera_format.framerate(),
|
||||
format!("{}", self.camera_info.index()).as_str(),
|
||||
)) {
|
||||
Ok(pl) => pl,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to initialize GStreamer Pipeline with string {} : {}",
|
||||
webcam_pipeline(
|
||||
self.camera_format.width(),
|
||||
self.camera_format.height(),
|
||||
self.camera_format.framerate(),
|
||||
format!("{}", self.camera_info.index()).as_str()
|
||||
),
|
||||
why.to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let sink = {
|
||||
let bin = match pipeline.clone().dynamic_cast::<gstreamer::Bin>() {
|
||||
Ok(bn) => bn,
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast Element to Bin".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let snk = match bin.get_by_name("appsink") {
|
||||
Some(sk) => sk,
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast get appsink from pipeline!".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
snk
|
||||
};
|
||||
let app_sink = match sink.clone().dynamic_cast::<gstreamer_app::AppSink>() {
|
||||
Ok(ap_sk) => ap_sk,
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to cast Element to AppSink".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.app_sink = app_sink;
|
||||
self.pipeline = pipeline;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CaptureBackendTrait for GStreamerCaptureDevice {
|
||||
@@ -216,27 +134,248 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
|
||||
}
|
||||
|
||||
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
|
||||
self.camera_format = new_fmt;
|
||||
self.regenerate_pipeline()?;
|
||||
let mut reopen = false;
|
||||
if self.is_stream_open() {
|
||||
self.stop_stream()?;
|
||||
reopen = true;
|
||||
}
|
||||
let (pipeline, app_sink, receiver) = generate_pipeline(new_fmt, *self.camera_info.index())?;
|
||||
self.pipeline = pipeline;
|
||||
self.app_sink = app_sink;
|
||||
self.receiver = receiver;
|
||||
if reopen {
|
||||
self.open_stream()?;
|
||||
}
|
||||
self.camera_format = new_fmt;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_compatible_list_by_resolution(
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
fn compatible_list_by_resolution(
|
||||
&self,
|
||||
_fourcc: FrameFormat,
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
||||
Err(NokhwaError::UnsupportedOperation(
|
||||
CaptureAPIBackend::GStreamer,
|
||||
))
|
||||
let mut resolution_map = HashMap::new();
|
||||
|
||||
let frame_regex = Regex::new(r"(\d+/1)|((\d+/\d)+(\d/1)*)").unwrap();
|
||||
|
||||
match self.caps.clone() {
|
||||
Some(c) => {
|
||||
for capability in c.iter() {
|
||||
match fourcc {
|
||||
FrameFormat::MJPEG => {
|
||||
if capability.name() == "image/jpeg" {
|
||||
let mut fps_vec = vec![];
|
||||
|
||||
let width = match capability.get::<i32>("width") {
|
||||
Ok(w) => w,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Capibilities by Resolution: Width"
|
||||
.to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let height = match capability.get::<i32>("height") {
|
||||
Ok(w) => w,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Capibilities by Resolution: Height"
|
||||
.to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let value = match capability
|
||||
.value_by_quark(Quark::from_string("framerate"))
|
||||
{
|
||||
Ok(v) => match v.transform::<String>() {
|
||||
Ok(s) => {
|
||||
format!("{:?}", s)
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: format!(
|
||||
"Failed to make framerates into string: {}",
|
||||
why.to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: "Failed to get framerates: doesnt exist!"
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
for m in frame_regex.find_iter(&value) {
|
||||
let fraction_string: Vec<&str> =
|
||||
m.as_str().split('/').collect();
|
||||
if fraction_string.len() != 2 {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: format!("Fraction framerate had more than one demoninator: {:?}", fraction_string) });
|
||||
}
|
||||
|
||||
if let Some(v) = fraction_string.get(1) {
|
||||
if *v != "1" {
|
||||
continue; // swallow error
|
||||
}
|
||||
} else {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: "No framerate denominator? Shouldn't happen, please report!".to_string() });
|
||||
}
|
||||
|
||||
if let Some(numerator) = fraction_string.get(0) {
|
||||
match numerator.parse::<u32>() {
|
||||
Ok(fps) => fps_vec.push(fps),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: format!(
|
||||
"Failed to parse numerator: {}",
|
||||
why.to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: "No framerate numerator? Shouldn't happen, please report!".to_string() });
|
||||
}
|
||||
}
|
||||
resolution_map
|
||||
.insert(Resolution::new(width as u32, height as u32), fps_vec);
|
||||
}
|
||||
}
|
||||
FrameFormat::YUYV => {
|
||||
if capability.name() == "video/x-raw"
|
||||
&& capability.get::<String>("format").unwrap_or_default() == *"YUY2"
|
||||
{
|
||||
let mut fps_vec = vec![];
|
||||
|
||||
let width = match capability.get::<i32>("width") {
|
||||
Ok(w) => w,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Capibilities by Resolution: Width"
|
||||
.to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let height = match capability.get::<i32>("height") {
|
||||
Ok(w) => w,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Capibilities by Resolution: Height"
|
||||
.to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let value = match capability
|
||||
.value_by_quark(Quark::from_string("framerate"))
|
||||
{
|
||||
Ok(v) => match v.transform::<String>() {
|
||||
Ok(s) => {
|
||||
format!("{:?}", s)
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: format!(
|
||||
"Failed to make framerates into string: {}",
|
||||
why.to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: "Failed to get framerates: doesnt exist!"
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
for m in frame_regex.find_iter(&value) {
|
||||
let fraction_string: Vec<&str> =
|
||||
m.as_str().split('/').collect();
|
||||
if fraction_string.len() != 2 {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: format!("Fraction framerate had more than one demoninator: {:?}", fraction_string) });
|
||||
}
|
||||
|
||||
if let Some(v) = fraction_string.get(1) {
|
||||
if *v != "1" {
|
||||
continue; // swallow error
|
||||
}
|
||||
} else {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: "No framerate denominator? Shouldn't happen, please report!".to_string() });
|
||||
}
|
||||
|
||||
if let Some(numerator) = fraction_string.get(0) {
|
||||
match numerator.parse::<u32>() {
|
||||
Ok(fps) => fps_vec.push(fps),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Framerates".to_string(),
|
||||
error: format!(
|
||||
"Failed to parse numerator: {}",
|
||||
why.to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(NokhwaError::CouldntQueryDevice { property: "Framerates".to_string(), error: "No framerate numerator? Shouldn't happen, please report!".to_string() });
|
||||
}
|
||||
}
|
||||
resolution_map
|
||||
.insert(Resolution::new(width as u32, height as u32), fps_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Device Caps".to_string(),
|
||||
error: "No device caps!".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resolution_map)
|
||||
}
|
||||
|
||||
fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
Err(NokhwaError::UnsupportedOperation(
|
||||
CaptureAPIBackend::GStreamer,
|
||||
))
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
let mut format_vec = vec![];
|
||||
match self.caps.clone() {
|
||||
Some(c) => {
|
||||
for capability in c.iter() {
|
||||
if capability.name() == "image/jpeg" {
|
||||
format_vec.push(FrameFormat::MJPEG)
|
||||
} else if capability.name() == "video/x-raw"
|
||||
&& capability.get::<String>("format").unwrap_or_default() == *"YUY2"
|
||||
{
|
||||
format_vec.push(FrameFormat::YUYV)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Device Caps".to_string(),
|
||||
error: "No device caps!".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
format_vec.sort();
|
||||
format_vec.dedup();
|
||||
Ok(format_vec)
|
||||
}
|
||||
|
||||
fn resolution(&self) -> Resolution {
|
||||
@@ -281,25 +420,22 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
|
||||
|
||||
// TODO: someone validate this
|
||||
fn is_stream_open(&self) -> bool {
|
||||
let (res, state_from, state_to) = self.pipeline.get_state(ClockTime::none());
|
||||
return match res {
|
||||
Ok(_) => {
|
||||
if state_to == State::Playing {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
let (res, state_from, state_to) = self.pipeline.state(ClockTime::from_mseconds(16));
|
||||
if res.is_ok() {
|
||||
if state_to == State::Playing {
|
||||
return true;
|
||||
}
|
||||
Err(_) => {
|
||||
if state_from == State::Playing {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
false
|
||||
} else {
|
||||
if state_from == State::Playing {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let image_data = self.get_frame_raw()?;
|
||||
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let image_data = self.frame_raw()?;
|
||||
let cam_fmt = self.camera_format;
|
||||
let imagebuf =
|
||||
match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), image_data) {
|
||||
@@ -315,38 +451,40 @@ impl CaptureBackendTrait for GStreamerCaptureDevice {
|
||||
Ok(imagebuf)
|
||||
}
|
||||
|
||||
fn get_frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
if self.is_stream_open() {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Please call `open_stream` first!".to_string(),
|
||||
));
|
||||
}
|
||||
if self.app_sink.is_eos() {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Stream is EOS!".to_string(),
|
||||
));
|
||||
}
|
||||
match self.app_sink.pull_sample() {
|
||||
Ok(sample) => match sample.get_buffer_owned() {
|
||||
Some(buf) => match buf.into_mapped_buffer_readable() {
|
||||
Ok(readable) => Ok(readable.as_slice().to_vec()),
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Sample Buffer get mapped readable fail!".to_string(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
fn frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
let bus = match self.pipeline.bus() {
|
||||
Some(bus) => bus,
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"The pipeline has no bus!".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(message) = bus.timed_pop(ClockTime::from_seconds(0)) {
|
||||
match message.view() {
|
||||
MessageView::Eos(..) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Sample Buffer get fail!".to_string(),
|
||||
"Stream is ended!".to_string(),
|
||||
))
|
||||
}
|
||||
},
|
||||
MessageView::Error(err) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Bus error: {}",
|
||||
err.error().to_string()
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match self.receiver.recv() {
|
||||
Ok(msg) => Ok(msg.to_vec()),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to pull sample from appsink: {}",
|
||||
"Receiver Error: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,17 +506,266 @@ impl Drop for GStreamerCaptureDevice {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
|
||||
match camera_format.format() {
|
||||
FrameFormat::MJPEG => {
|
||||
format!("autovideosrc location=/dev/video{} ! image/jpeg,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
FrameFormat::YUYV => {
|
||||
format!("autovideosrc location=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn webcam_pipeline(width: u32, height: u32, fps: u32, camera_location: &str) -> String {
|
||||
format!("v4l2src device=/dev/video{} ! video/x-raw,width={},height={},format=RGB,framerate={}/1 ! appsink name=appsink async=false sync=false", camera_location, width, height, fps)
|
||||
fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
|
||||
match camera_format.format() {
|
||||
FrameFormat::MJPEG => {
|
||||
format!("v4l2src device=/dev/video{} ! image/jpeg, width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
FrameFormat::YUYV => {
|
||||
format!("v4l2src device=/dev/video{} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn webcam_pipeline(width: u32, height: u32, fps: u32, camera_location: &str) -> String {
|
||||
format!("ksvideosrc device_index={} ! video/x-raw,width={},height={},format=RGB,framerate={}/1 ! appsink name=appsink async=false sync=false", camera_location, width, height, fps)
|
||||
fn webcam_pipeline(device: &str, camera_format: CameraFormat) -> String {
|
||||
match camera_format.format() {
|
||||
FrameFormat::MJPEG => {
|
||||
format!("ksvideosrc device_index={} ! image/jpeg, width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
FrameFormat::YUYV => {
|
||||
format!("ksvideosrc device_index={} ! video/x-raw,format=YUY2,width={},height={},framerate={}/1 ! appsink name=appsink async=false sync=false", device, camera_format.width(), camera_format.height(), camera_format.framerate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn webcam_pipeline(width: u32, height: u32, fps: u32, camera_location: &str) -> String {
|
||||
format!("autovideosrc location=/dev/video{} ! video/x-raw,width={},height={},format=RGB,framerate={}/1 ! appsink name=appsink async=false sync=false", camera_location, width, height, fps)
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::let_and_return)]
|
||||
fn generate_pipeline(fmt: CameraFormat, index: usize) -> Result<PipelineGenRet, NokhwaError> {
|
||||
let pipeline =
|
||||
match gstreamer::parse_launch(webcam_pipeline(format!("{}", index).as_str(), fmt).as_str())
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to open pipeline with args {}: {}",
|
||||
webcam_pipeline(format!("{}", index).as_str(), fmt),
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let sink = match pipeline
|
||||
.clone()
|
||||
.dynamic_cast::<Bin>()
|
||||
.unwrap()
|
||||
.by_name("appsink")
|
||||
{
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to get sink element!".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let appsink = match sink.dynamic_cast::<AppSink>() {
|
||||
Ok(aps) => aps,
|
||||
Err(_) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to get sink element as appsink".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
pipeline.set_state(State::Playing).unwrap();
|
||||
|
||||
let (sender, receiver) = flume::unbounded();
|
||||
|
||||
appsink.set_callbacks(
|
||||
AppSinkCallbacks::builder()
|
||||
.new_sample(move |appsink| {
|
||||
let sample = appsink.pull_sample().map_err(|_| FlowError::Eos)?;
|
||||
let sample_caps = if let Some(c) = sample.caps() {
|
||||
c
|
||||
} else {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Failed to get caps of sample")
|
||||
);
|
||||
return Err(FlowError::Error);
|
||||
};
|
||||
|
||||
let video_info = match VideoInfo::from_caps(sample_caps) {
|
||||
Ok(vi) => vi, // help let me outtttttt
|
||||
Err(why) => {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
(format!("Failed to get videoinfo from caps: {}", why.to_string())
|
||||
.as_str())
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
let buffer = if let Some(buf) = sample.buffer() {
|
||||
buf
|
||||
} else {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Failed to get buffer from sample")
|
||||
);
|
||||
return Err(FlowError::Error);
|
||||
};
|
||||
|
||||
let buffer_map = match buffer.map_readable() {
|
||||
Ok(m) => m,
|
||||
Err(why) => {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
(format!("Failed to map buffer to readablemap: {}", why.to_string())
|
||||
.as_str())
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
let channels = if video_info.has_alpha() { 4 } else { 3 };
|
||||
|
||||
let image_buffer = match video_info.format() {
|
||||
VideoFormat::Yuy2 => {
|
||||
let mut decoded_buffer = match yuyv422_to_rgb888(&buffer_map.as_slice()) {
|
||||
Ok(buf) => buf,
|
||||
Err(why) => {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
(format!(
|
||||
"Failed to make yuy2 into rgb888: {}",
|
||||
why.to_string()
|
||||
)
|
||||
.as_str())
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
decoded_buffer.resize(
|
||||
(video_info.width() * video_info.height() * channels) as usize,
|
||||
0_u8,
|
||||
);
|
||||
|
||||
let image = if let Some(i) = ImageBuffer::from_vec(
|
||||
video_info.width(),
|
||||
video_info.height(),
|
||||
decoded_buffer,
|
||||
) {
|
||||
let rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = i;
|
||||
rgb
|
||||
} else {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Failed to make rgb buffer into imagebuffer")
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
};
|
||||
image
|
||||
}
|
||||
VideoFormat::Rgb => {
|
||||
let mut decoded_buffer = buffer_map.as_slice().to_vec();
|
||||
decoded_buffer.resize(
|
||||
(video_info.width() * video_info.height() * channels) as usize,
|
||||
0_u8,
|
||||
);
|
||||
let image = if let Some(i) = ImageBuffer::from_vec(
|
||||
video_info.width(),
|
||||
video_info.height(),
|
||||
decoded_buffer,
|
||||
) {
|
||||
let rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = i;
|
||||
rgb
|
||||
} else {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Failed to make rgb buffer into imagebuffer")
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
};
|
||||
image
|
||||
}
|
||||
// MJPEG
|
||||
VideoFormat::Encoded => {
|
||||
let mut decoded_buffer = match mjpeg_to_rgb888(&buffer_map.as_slice()) {
|
||||
Ok(buf) => buf,
|
||||
Err(why) => {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
(format!(
|
||||
"Failed to make yuy2 into rgb888: {}",
|
||||
why.to_string()
|
||||
)
|
||||
.as_str())
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
decoded_buffer.resize(
|
||||
(video_info.width() * video_info.height() * channels) as usize,
|
||||
0_u8,
|
||||
);
|
||||
|
||||
let image = if let Some(i) = ImageBuffer::from_vec(
|
||||
video_info.width(),
|
||||
video_info.height(),
|
||||
decoded_buffer,
|
||||
) {
|
||||
let rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = i;
|
||||
rgb
|
||||
} else {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Failed to make rgb buffer into imagebuffer")
|
||||
);
|
||||
|
||||
return Err(FlowError::Error);
|
||||
};
|
||||
image
|
||||
}
|
||||
_ => {
|
||||
element_error!(
|
||||
appsink,
|
||||
ResourceError::Failed,
|
||||
("Unsupported video format")
|
||||
);
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
if sender.send(image_buffer).is_err() {
|
||||
return Err(FlowError::Error);
|
||||
}
|
||||
|
||||
Ok(FlowSuccess::Ok)
|
||||
})
|
||||
.build(),
|
||||
);
|
||||
Ok((pipeline, appsink, receiver))
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
use crate::{
|
||||
tryinto_num, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait, FrameFormat,
|
||||
NokhwaError, Resolution,
|
||||
tryinto_num, vector, CameraFormat, CameraInfo, CaptureAPIBackend, CaptureBackendTrait,
|
||||
FrameFormat, NokhwaError, Resolution,
|
||||
};
|
||||
use image::{ImageBuffer, Rgb};
|
||||
|
||||
use opencv::core::Vector;
|
||||
use opencv::{
|
||||
core::{ToInputArray, ToOutputArray, Vector},
|
||||
imgproc::{cvt_color, ColorConversionCodes},
|
||||
types::VectorOfu8,
|
||||
core::{Mat, MatTrait, MatTraitManual, Vec3b},
|
||||
videoio::{
|
||||
VideoCapture, VideoCaptureTrait, VideoWriter, CAP_ANY, CAP_AVFOUNDATION, CAP_DSHOW,
|
||||
CAP_PROP_FOURCC, CAP_V4L2,
|
||||
CAP_PROP_FOURCC, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
|
||||
},
|
||||
videoio::{CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -25,6 +22,7 @@ use std::{
|
||||
///
|
||||
/// To see what this does, please see [`CaptureBackendTrait`]
|
||||
/// # Quirks
|
||||
/// - **Some features don't work properly on this backend (yet)! Setting Resolution, FPS, FourCC does not work and will default to 640x480 30FPS. This is being worked on.**
|
||||
/// - This is a **cross-platform** backend. This means that it will work on most platforms given that `OpenCV` is present.
|
||||
/// - This backend can also do IP Camera input.
|
||||
/// - The backend's backend will default to system level APIs on Linux(V4L2), Mac(AVFoundation), and Windows(Media Foundation). Otherwise, it will decide for itself.
|
||||
@@ -59,7 +57,7 @@ impl OpenCvCaptureDevice {
|
||||
/// # Errors
|
||||
/// If the backend fails to open the camera (e.g. Device does not exist at specified index/ip), Camera does not support specified [`CameraFormat`], and/or other `OpenCV` Error, this will error.
|
||||
/// # Panics
|
||||
/// If the API u32 -> i32
|
||||
/// If the API u32 -> i32 fails this will error
|
||||
pub fn new(
|
||||
camera_location: CameraIndexType,
|
||||
cfmt: Option<CameraFormat>,
|
||||
@@ -78,16 +76,15 @@ impl OpenCvCaptureDevice {
|
||||
None => CameraFormat::default(),
|
||||
};
|
||||
|
||||
let video_capture = match camera_location.clone() {
|
||||
let mut video_capture = match camera_location.clone() {
|
||||
CameraIndexType::Index(idx) => {
|
||||
let mut vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
|
||||
let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
|
||||
Ok(vc) => {
|
||||
index = idx;
|
||||
vc
|
||||
}
|
||||
Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())),
|
||||
};
|
||||
set_properties(&mut vid_cap, camera_format)?;
|
||||
vid_cap
|
||||
}
|
||||
CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) {
|
||||
@@ -96,6 +93,8 @@ impl OpenCvCaptureDevice {
|
||||
},
|
||||
};
|
||||
|
||||
set_properties(&mut video_capture, camera_format, &camera_location)?;
|
||||
|
||||
let camera_info = CameraInfo::new(
|
||||
format!("OpenCV Capture Device {}", camera_location),
|
||||
camera_location.to_string(),
|
||||
@@ -180,27 +179,98 @@ impl OpenCvCaptureDevice {
|
||||
self.api_preference
|
||||
}
|
||||
|
||||
/// Gets the BGR24 frame directly read from `OpenCV` without any additional processing
|
||||
/// Gets the RGB24 frame directly read from `OpenCV` without any additional processing.
|
||||
/// # Errors
|
||||
/// If the frame is failed to be read, this will error.
|
||||
pub fn get_raw_frame_vector(&mut self) -> Result<Vector<u8>, NokhwaError> {
|
||||
let mut image_read_vector = VectorOfu8::new();
|
||||
let image_out_arr = &mut match image_read_vector.output_array() {
|
||||
Ok(out) => out,
|
||||
Err(why) => return Err(NokhwaError::CouldntCaptureFrame(why.to_string())),
|
||||
};
|
||||
match self.video_capture.read(image_out_arr) {
|
||||
Ok(read) => {
|
||||
if !read {
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
pub fn raw_frame_vec(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
if !self.is_stream_open() {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Stream is not open!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut frame = Mat::default();
|
||||
match self.video_capture.read(&mut frame) {
|
||||
Ok(a) => {
|
||||
if !a {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"OpenCV failed to read frame, returned false".to_string(),
|
||||
"Failed to read frame from videocapture: OpenCV return false, camera disconnected?".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(why) => return Err(NokhwaError::CouldntCaptureFrame(why.to_string())),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to read frame from videocapture: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(image_read_vector)
|
||||
let frame_empty = match frame.empty() {
|
||||
Ok(e) => e,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to check for empty OpenCV frame: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
match frame.size() {
|
||||
Ok(size) => {
|
||||
if size.width > 0 && !frame_empty {
|
||||
return match frame.is_continuous() {
|
||||
Ok(cont) => {
|
||||
if cont {
|
||||
let mut raw_vec: Vec<u8> = Vec::new();
|
||||
raw_vec.reserve(
|
||||
(self.resolution().width()
|
||||
* self.resolution().height()
|
||||
* (frame.channels().unwrap_or(3)) as u32)
|
||||
as usize,
|
||||
);
|
||||
|
||||
let frame_data_vec = match Mat::data_typed::<Vec3b>(&frame) {
|
||||
Ok(v) => v,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to convert frame into raw Vec3b: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
for pixel in frame_data_vec.iter() {
|
||||
let pixel_slice: &[u8; 3] = &**pixel;
|
||||
raw_vec.push(pixel_slice[2]);
|
||||
raw_vec.push(pixel_slice[1]);
|
||||
raw_vec.push(pixel_slice[0]);
|
||||
}
|
||||
|
||||
return Ok(raw_vec);
|
||||
}
|
||||
Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Failed to read frame from videocapture: not cont".to_string(),
|
||||
))
|
||||
}
|
||||
Err(why) => Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to read frame from videocapture: failed to read continuous: {}",
|
||||
why.to_string()
|
||||
))),
|
||||
};
|
||||
}
|
||||
Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Frame width is less than zero!".to_string(),
|
||||
))
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(format!(
|
||||
"Failed to read frame from videocapture: failed to read size: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the resolution raw as read by `OpenCV`.
|
||||
@@ -208,7 +278,7 @@ impl OpenCvCaptureDevice {
|
||||
/// If the resolution is failed to be read (e.g. invalid or not supported), this will error.
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn get_resolution_raw(&self) -> Result<Resolution, NokhwaError> {
|
||||
pub fn raw_resolution(&self) -> Result<Resolution, NokhwaError> {
|
||||
let width = match self.video_capture.get(CAP_PROP_FRAME_WIDTH) {
|
||||
Ok(width) => width as u32,
|
||||
Err(why) => {
|
||||
@@ -268,26 +338,30 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
|
||||
})
|
||||
}
|
||||
};
|
||||
set_properties(&mut self.video_capture, new_fmt)?;
|
||||
|
||||
self.camera_format = new_fmt;
|
||||
|
||||
if let Err(why) = set_properties(&mut self.video_capture, new_fmt, &self.camera_location) {
|
||||
self.camera_format = current_format;
|
||||
return Err(why);
|
||||
}
|
||||
if is_opened {
|
||||
self.stop_stream()?;
|
||||
if let Err(why) = self.open_stream() {
|
||||
// revert
|
||||
set_properties(&mut self.video_capture, current_format)?;
|
||||
return Err(NokhwaError::CouldntOpenDevice(why.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_compatible_list_by_resolution(
|
||||
fn compatible_list_by_resolution(
|
||||
&self,
|
||||
_fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
||||
Err(NokhwaError::UnsupportedOperation(CaptureAPIBackend::OpenCv))
|
||||
}
|
||||
|
||||
fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
Err(NokhwaError::UnsupportedOperation(CaptureAPIBackend::OpenCv))
|
||||
}
|
||||
|
||||
@@ -321,38 +395,37 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
|
||||
self.set_camera_format(current_fmt)
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
fn open_stream(&mut self) -> Result<(), NokhwaError> {
|
||||
let is_opened = match self.video_capture.is_opened() {
|
||||
Ok(opened) => opened,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntQueryDevice {
|
||||
property: "Is Stream Open".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
if is_opened {
|
||||
match self.video_capture.release() {
|
||||
Ok(_) => {}
|
||||
Err(why) => return Err(NokhwaError::CouldntOpenStream(why.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
self.video_capture = match self.camera_location.clone() {
|
||||
match self.camera_location.clone() {
|
||||
CameraIndexType::Index(idx) => {
|
||||
let mut vid_cap =
|
||||
match VideoCapture::new(tryinto_num!(i32, idx), self.api_preference as i32) {
|
||||
Ok(vc) => vc,
|
||||
Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())),
|
||||
};
|
||||
set_properties(&mut vid_cap, self.camera_format)?;
|
||||
vid_cap
|
||||
match self
|
||||
.video_capture
|
||||
.open_1(idx as i32, get_api_pref_int() as i32)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to open device: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
CameraIndexType::IPCamera(ip) => {
|
||||
match self
|
||||
.video_capture
|
||||
.open_file(&*ip, get_api_pref_int() as i32)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to open device: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) {
|
||||
Ok(vc) => vc,
|
||||
Err(why) => return Err(NokhwaError::CouldntOpenDevice(why.to_string())),
|
||||
},
|
||||
};
|
||||
|
||||
match self.video_capture.is_opened() {
|
||||
@@ -375,48 +448,35 @@ impl CaptureBackendTrait for OpenCvCaptureDevice {
|
||||
self.video_capture.is_opened().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn get_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let image_data = self.get_raw_frame_vector()?;
|
||||
let image_input_arr = &match image_data.input_array() {
|
||||
Ok(input) => input,
|
||||
Err(why) => return Err(NokhwaError::CouldntCaptureFrame(why.to_string())),
|
||||
};
|
||||
|
||||
let mut image_rgb_out = VectorOfu8::new();
|
||||
let rgb_image_output_arr = &mut match image_rgb_out.output_array() {
|
||||
Ok(out) => out,
|
||||
Err(why) => return Err(NokhwaError::CouldntCaptureFrame(why.to_string())),
|
||||
};
|
||||
|
||||
match cvt_color(
|
||||
image_input_arr,
|
||||
rgb_image_output_arr,
|
||||
ColorConversionCodes::COLOR_BGR2RGB as i32,
|
||||
0,
|
||||
) {
|
||||
Ok(_) => {
|
||||
let rgb_image = image_rgb_out.to_vec();
|
||||
let camera_resolution = self.camera_format.resolution();
|
||||
let imagebuf =
|
||||
match ImageBuffer::from_vec(camera_resolution.width(), camera_resolution.height(), rgb_image) {
|
||||
Some(buf) => {
|
||||
let rgbbuf: ImageBuffer<Rgb<u8>, Vec<u8>> = buf;
|
||||
rgbbuf
|
||||
}
|
||||
None => return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Imagebuffer is not large enough! This is probably a bug, please report it!"
|
||||
.to_string(),
|
||||
)),
|
||||
};
|
||||
Ok(imagebuf)
|
||||
}
|
||||
Err(why) => Err(NokhwaError::CouldntCaptureFrame(why.to_string())),
|
||||
}
|
||||
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let mut image_data = self.frame_raw()?;
|
||||
let camera_resolution = self.camera_format.resolution();
|
||||
image_data.resize(
|
||||
(camera_resolution.width() * camera_resolution.height() * 3) as usize,
|
||||
0_u8,
|
||||
);
|
||||
let imagebuf =
|
||||
match ImageBuffer::from_vec(
|
||||
camera_resolution.width(),
|
||||
camera_resolution.height(),
|
||||
image_data,
|
||||
) {
|
||||
Some(buf) => {
|
||||
let rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = buf;
|
||||
rgb
|
||||
}
|
||||
None => return Err(NokhwaError::CouldntCaptureFrame(
|
||||
"Imagebuffer is not large enough! This is probably a bug, please report it!"
|
||||
.to_string(),
|
||||
)),
|
||||
};
|
||||
Ok(imagebuf)
|
||||
}
|
||||
|
||||
fn get_frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
let data = self.get_raw_frame_vector()?;
|
||||
Ok(data.to_vec())
|
||||
fn frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
let vec = self.raw_frame_vec()?;
|
||||
// println!("{:?}", vec);
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
|
||||
@@ -461,125 +521,87 @@ fn get_api_pref_int() -> u32 {
|
||||
&_ => CAP_ANY as u32,
|
||||
}
|
||||
}
|
||||
fn set_properties(vc: &mut VideoCapture, camera_format: CameraFormat) -> Result<(), NokhwaError> {
|
||||
set_property_fourcc(vc, camera_format.format())?;
|
||||
set_property_res(vc, camera_format.resolution())?;
|
||||
set_property_fps(vc, camera_format.framerate())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_property_res(vc: &mut VideoCapture, res: Resolution) -> Result<(), NokhwaError> {
|
||||
match vc.set(CAP_PROP_FRAME_HEIGHT as i32, f64::from(res.height())) {
|
||||
Ok(r) => {
|
||||
if !r {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Resolution Height".to_string(),
|
||||
value: res.height().to_string(),
|
||||
error: "OpenCV bool assert failure".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Resolution Height".to_string(),
|
||||
value: res.height().to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
match vc.set(CAP_PROP_FRAME_WIDTH as i32, f64::from(res.width())) {
|
||||
Ok(r) => {
|
||||
if !r {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Resolution Width".to_string(),
|
||||
value: res.width().to_string(),
|
||||
error: "OpenCV bool assert failure".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Resolution Width".to_string(),
|
||||
value: res.width().to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_property_fps(vc: &mut VideoCapture, fps: u32) -> Result<(), NokhwaError> {
|
||||
match vc.set(CAP_PROP_FPS as i32, f64::from(fps)) {
|
||||
Ok(r) => {
|
||||
if !r {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Framerate".to_string(),
|
||||
value: fps.to_string(),
|
||||
error: "OpenCV bool assert failure".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "Framerate".to_string(),
|
||||
value: fps.to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_property_fourcc(vc: &mut VideoCapture, frame_fmt: FrameFormat) -> Result<(), NokhwaError> {
|
||||
let fourcc = match frame_fmt {
|
||||
FrameFormat::MJPEG => f64::from(
|
||||
match VideoWriter::fourcc('M' as i8, 'J' as i8, 'P' as i8, 'G' as i8) {
|
||||
Ok(fcc) => fcc,
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
fn set_properties(
|
||||
vc: &mut VideoCapture,
|
||||
camera_format: CameraFormat,
|
||||
camera_location: &CameraIndexType,
|
||||
) -> Result<(), NokhwaError> {
|
||||
let fourcc = match camera_format.format() {
|
||||
FrameFormat::MJPEG => {
|
||||
match VideoWriter::fourcc('m' as i8, 'j' as i8, 'p' as i8, 'g' as i8) {
|
||||
Ok(fmt) => fmt,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "FourCC".to_string(),
|
||||
value: "MJPG".to_string(),
|
||||
property: "FrameFormat".to_string(),
|
||||
value: "FourCC MJPG".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
),
|
||||
FrameFormat::YUYV => f64::from(
|
||||
match VideoWriter::fourcc('Y' as i8, 'U' as i8, 'Y' as i8, 'V' as i8) {
|
||||
Ok(fcc) => fcc,
|
||||
}
|
||||
}
|
||||
FrameFormat::YUYV => {
|
||||
match VideoWriter::fourcc('y' as i8, 'u' as i8, 'y' as i8, 'v' as i8) {
|
||||
Ok(fmt) => fmt,
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "FourCC".to_string(),
|
||||
value: "YUYV".to_string(),
|
||||
property: "FrameFormat".to_string(),
|
||||
value: "FourCC YUYV".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match vc.set(
|
||||
let properties: &Vector<i32> = &vector!(
|
||||
CAP_PROP_FOURCC as i32,
|
||||
f64::from(VideoWriter::fourcc('M' as i8, 'J' as i8, 'P' as i8, 'G' as i8).unwrap()),
|
||||
) {
|
||||
Ok(r) => {
|
||||
if !r {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "FourCC".to_string(),
|
||||
value: format!("FrameFormat: {}, OpenCV FourCC {}", frame_fmt, fourcc),
|
||||
error: "OpenCV bool assert failure".to_string(),
|
||||
});
|
||||
fourcc,
|
||||
CAP_PROP_FRAME_WIDTH as i32,
|
||||
camera_format.width() as i32,
|
||||
CAP_PROP_FRAME_HEIGHT as i32,
|
||||
camera_format.height() as i32,
|
||||
CAP_PROP_FPS as i32,
|
||||
camera_format.framerate() as i32
|
||||
);
|
||||
|
||||
match camera_location {
|
||||
CameraIndexType::Index(idx) => {
|
||||
match vc.open_2(*idx as i32, get_api_pref_int() as i32, properties) {
|
||||
Ok(v) => {
|
||||
if !v {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to re-open camera, OpenCV Bool return error".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to re-open camera with properties: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntSetProperty {
|
||||
property: "FourCC".to_string(),
|
||||
value: format!("FrameFormat: {}, OpenCV FourCC {}", frame_fmt, fourcc),
|
||||
error: why.to_string(),
|
||||
})
|
||||
CameraIndexType::IPCamera(ip) => {
|
||||
match vc.open(ip.as_str(), get_api_pref_int() as i32, properties) {
|
||||
Ok(v) => {
|
||||
if !v {
|
||||
return Err(NokhwaError::CouldntOpenDevice(
|
||||
"Failed to re-open camera, OpenCV Bool return error".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::CouldntOpenDevice(format!(
|
||||
"Failed to re-open camera with properties: {}",
|
||||
why.to_string()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_compatible_list_by_resolution(
|
||||
fn compatible_list_by_resolution(
|
||||
&self,
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
||||
@@ -262,7 +262,7 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
|
||||
Ok(resolution_fps_map)
|
||||
}
|
||||
|
||||
fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
let mut frameformats = vec![];
|
||||
for fmt in self.with_device_handle(|devh| devh).supported_formats() {
|
||||
for frame_desc in fmt.supported_formats() {
|
||||
@@ -402,8 +402,8 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
|
||||
self.with_active_stream_init(Cell::get)
|
||||
}
|
||||
|
||||
fn get_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let data = match self.get_frame_raw() {
|
||||
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let data = match self.frame_raw() {
|
||||
Ok(d) => d,
|
||||
Err(why) => return Err(why),
|
||||
};
|
||||
@@ -424,7 +424,7 @@ impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
|
||||
Ok(imagebuf)
|
||||
}
|
||||
|
||||
fn get_frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
fn frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
// assertions
|
||||
if !self.borrow_active_stream_init().get() {
|
||||
return Err(NokhwaError::CouldntCaptureFrame(
|
||||
|
||||
@@ -259,7 +259,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_compatible_list_by_resolution(
|
||||
fn compatible_list_by_resolution(
|
||||
&self,
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
||||
@@ -297,7 +297,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
|
||||
Ok(resmap)
|
||||
}
|
||||
|
||||
fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
match Capture::enum_formats(&self.device) {
|
||||
Ok(formats) => {
|
||||
let mut frameformat_vec = vec![];
|
||||
@@ -374,8 +374,8 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
|
||||
self.stream_handle.is_some()
|
||||
}
|
||||
|
||||
fn get_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let raw_frame = self.get_frame_raw()?;
|
||||
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
let raw_frame = self.frame_raw()?;
|
||||
let cam_fmt = self.camera_format;
|
||||
let conv = match cam_fmt.format() {
|
||||
FrameFormat::MJPEG => mjpeg_to_rgb888(&raw_frame)?,
|
||||
@@ -395,7 +395,7 @@ impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
|
||||
Ok(imagebuf)
|
||||
}
|
||||
|
||||
fn get_frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
fn frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError> {
|
||||
match &mut self.stream_handle {
|
||||
Some(streamh) => match streamh.next() {
|
||||
Ok((data, _)) => Ok(data.to_vec()),
|
||||
|
||||
+54
-32
@@ -27,13 +27,7 @@ impl Camera {
|
||||
format: Option<CameraFormat>,
|
||||
backend: CaptureAPIBackend,
|
||||
) -> Result<Self, NokhwaError> {
|
||||
let camera_backend = cap_impl_matches! {
|
||||
backend, index, format,
|
||||
("input-v4l", Video4Linux, init_v4l),
|
||||
("input-uvc", UniversalVideoClass, init_uvc),
|
||||
("input-gst", GStreamer, init_gst),
|
||||
("input-opencv", OpenCv, init_opencv)
|
||||
};
|
||||
let camera_backend = init_camera(index, format, backend)?;
|
||||
|
||||
Ok(Camera {
|
||||
idx: index,
|
||||
@@ -65,10 +59,14 @@ impl Camera {
|
||||
/// Sets the current Camera's index. Note that this re-initializes the camera.
|
||||
/// # Errors
|
||||
/// The Backend may fail to initialize.
|
||||
pub fn set_index(self, new_idx: usize) -> Result<Self, NokhwaError> {
|
||||
self.backend.borrow_mut().stop_stream()?;
|
||||
pub fn set_index(&mut self, new_idx: usize) -> Result<(), NokhwaError> {
|
||||
{
|
||||
self.backend.borrow_mut().stop_stream()?;
|
||||
}
|
||||
let new_camera_format = self.backend.borrow().camera_format();
|
||||
Camera::new(new_idx, Some(new_camera_format), self.backend_api)
|
||||
let new_camera = init_camera(new_idx, Some(new_camera_format), self.backend_api)?;
|
||||
*self.backend.borrow_mut() = new_camera;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the current Camera's backend
|
||||
@@ -79,18 +77,22 @@ impl Camera {
|
||||
/// Sets the current Camera's backend. Note that this re-initializes the camera.
|
||||
/// # Errors
|
||||
/// The new backend may not exist or may fail to initialize the new camera.
|
||||
pub fn set_backend(self, new_backend: CaptureAPIBackend) -> Result<Self, NokhwaError> {
|
||||
self.backend.borrow_mut().stop_stream()?;
|
||||
pub fn set_backend(&mut self, new_backend: CaptureAPIBackend) -> Result<(), NokhwaError> {
|
||||
{
|
||||
self.backend.borrow_mut().stop_stream()?;
|
||||
}
|
||||
let new_camera_format = self.backend.borrow().camera_format();
|
||||
Camera::new(self.idx, Some(new_camera_format), new_backend)
|
||||
let new_camera = init_camera(self.idx, Some(new_camera_format), new_backend)?;
|
||||
*self.backend.borrow_mut() = new_camera;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the camera information such as Name and Index as a [`CameraInfo`].
|
||||
pub fn get_info(&self) -> CameraInfo {
|
||||
pub fn info(&self) -> CameraInfo {
|
||||
self.backend.borrow().camera_info()
|
||||
}
|
||||
/// Gets the current [`CameraFormat`].
|
||||
pub fn get_camera_format(&self) -> CameraFormat {
|
||||
pub fn camera_format(&self) -> CameraFormat {
|
||||
self.backend.borrow().camera_format()
|
||||
}
|
||||
/// Will set the current [`CameraFormat`]
|
||||
@@ -103,22 +105,20 @@ impl Camera {
|
||||
/// A hashmap of [`Resolution`]s mapped to framerates
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`NokhwaError::UnsupportedOperation`]).
|
||||
pub fn get_compatible_list_by_resolution(
|
||||
pub fn compatible_list_by_resolution(
|
||||
&self,
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
||||
self.backend
|
||||
.borrow()
|
||||
.get_compatible_list_by_resolution(fourcc)
|
||||
self.backend.borrow().compatible_list_by_resolution(fourcc)
|
||||
}
|
||||
/// A Vector of compatible [`FrameFormat`]s.
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`NokhwaError::UnsupportedOperation`]).
|
||||
pub fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
self.backend.borrow_mut().get_compatible_fourcc()
|
||||
pub fn compatible_fourcc(&self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
||||
self.backend.borrow_mut().compatible_fourcc()
|
||||
}
|
||||
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
|
||||
pub fn get_resolution(&self) -> Resolution {
|
||||
pub fn resolution(&self) -> Resolution {
|
||||
self.backend.borrow().resolution()
|
||||
}
|
||||
/// Will set the current [`Resolution`]
|
||||
@@ -129,7 +129,7 @@ impl Camera {
|
||||
self.backend.borrow_mut().set_resolution(new_res)
|
||||
}
|
||||
/// Gets the current camera framerate (See: [`CameraFormat`]).
|
||||
pub fn get_framerate(&self) -> u32 {
|
||||
pub fn framerate(&self) -> u32 {
|
||||
self.backend.borrow().frame_rate()
|
||||
}
|
||||
/// Will set the current framerate
|
||||
@@ -140,7 +140,7 @@ impl Camera {
|
||||
self.backend.borrow_mut().set_framerate(new_fps)
|
||||
}
|
||||
/// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]).
|
||||
pub fn get_frameformat(&self) -> FrameFormat {
|
||||
pub fn frameformat(&self) -> FrameFormat {
|
||||
self.backend.borrow().frameformat()
|
||||
}
|
||||
/// Will set the current [`FrameFormat`]
|
||||
@@ -165,14 +165,14 @@ impl Camera {
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet,
|
||||
/// this will error.
|
||||
pub fn get_frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
self.backend.borrow_mut().get_frame()
|
||||
pub fn frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
self.backend.borrow_mut().frame()
|
||||
}
|
||||
/// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself.
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
|
||||
pub fn get_frame_raw(&self) -> Result<Vec<u8>, NokhwaError> {
|
||||
self.backend.borrow_mut().get_frame_raw()
|
||||
pub fn frame_raw(&self) -> Result<Vec<u8>, NokhwaError> {
|
||||
self.backend.borrow_mut().frame_raw()
|
||||
}
|
||||
|
||||
/// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed.
|
||||
@@ -186,12 +186,12 @@ impl Camera {
|
||||
/// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture.
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
|
||||
pub fn get_frame_to_buffer(
|
||||
pub fn frame_to_buffer(
|
||||
&self,
|
||||
buffer: &mut [u8],
|
||||
convert_rgba: bool,
|
||||
) -> Result<usize, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
let frame = self.frame()?;
|
||||
let mut frame_data = frame.to_vec();
|
||||
if convert_rgba {
|
||||
let rgba_image: RgbaImage = frame.convert();
|
||||
@@ -206,13 +206,14 @@ impl Camera {
|
||||
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
|
||||
/// # Errors
|
||||
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
|
||||
pub fn get_frame_texture<'a>(
|
||||
pub fn frame_texture<'a>(
|
||||
&mut self,
|
||||
device: &WgpuDevice,
|
||||
queue: &WgpuQueue,
|
||||
label: Option<&'a str>,
|
||||
) -> Result<WgpuTexture, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
use std::{convert::TryFrom, num::NonZeroU32};
|
||||
let frame = self.frame()?;
|
||||
let rgba_frame: RgbaImage = frame.convert();
|
||||
|
||||
let texture_size = Extent3d {
|
||||
@@ -267,6 +268,12 @@ impl Camera {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Camera {
|
||||
fn drop(&mut self) {
|
||||
self.stop_stream().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Update as we go
|
||||
fn figure_out_auto() -> Option<CaptureAPIBackend> {
|
||||
let platform = std::env::consts::OS;
|
||||
@@ -296,3 +303,18 @@ cap_impl_fn! {
|
||||
(V4LCaptureDevice, new, "input-v4l", v4l),
|
||||
(UVCCaptureDevice, create, "input-uvc", uvc)
|
||||
}
|
||||
|
||||
fn init_camera(
|
||||
index: usize,
|
||||
format: Option<CameraFormat>,
|
||||
backend: CaptureAPIBackend,
|
||||
) -> Result<Box<dyn CaptureBackendTrait>, NokhwaError> {
|
||||
let camera_backend = cap_impl_matches! {
|
||||
backend, index, format,
|
||||
("input-v4l", Video4Linux, init_v4l),
|
||||
("input-uvc", UniversalVideoClass, init_uvc),
|
||||
("input-gst", GStreamer, init_gst),
|
||||
("input-opencv", OpenCv, init_opencv)
|
||||
};
|
||||
Ok(camera_backend)
|
||||
}
|
||||
|
||||
+11
-10
@@ -31,18 +31,18 @@ pub trait CaptureBackendTrait {
|
||||
/// If you started the stream and the camera rejects the new camera format, this will return an error.
|
||||
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError>;
|
||||
|
||||
/// A hashmap of [`Resolution`]s mapped to framerates
|
||||
/// A hashmap of [`Resolution`]s mapped to framerates. Not sorted!
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperation`](crate::NokhwaError::UnsupportedOperation)).
|
||||
fn get_compatible_list_by_resolution(
|
||||
fn compatible_list_by_resolution(
|
||||
&self,
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
|
||||
|
||||
/// A Vector of compatible [`FrameFormat`]s.
|
||||
/// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most.
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperation`](crate::NokhwaError::UnsupportedOperation)).
|
||||
fn get_compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
|
||||
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
|
||||
|
||||
/// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]).
|
||||
fn resolution(&self) -> Resolution;
|
||||
@@ -84,12 +84,12 @@ pub trait CaptureBackendTrait {
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), the decoding fails (e.g. MJPEG -> u8), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet,
|
||||
/// this will error.
|
||||
fn get_frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError>;
|
||||
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError>;
|
||||
|
||||
/// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself.
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
|
||||
fn get_frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError>;
|
||||
fn frame_raw(&mut self) -> Result<Vec<u8>, NokhwaError>;
|
||||
|
||||
/// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed.
|
||||
fn min_buffer_size(&self, rgba: bool) -> usize {
|
||||
@@ -103,12 +103,12 @@ pub trait CaptureBackendTrait {
|
||||
/// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture.
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
|
||||
fn get_frame_to_buffer(
|
||||
fn write_frame_to_buffer(
|
||||
&mut self,
|
||||
buffer: &mut [u8],
|
||||
convert_rgba: bool,
|
||||
) -> Result<usize, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
let frame = self.frame()?;
|
||||
let mut frame_data = frame.to_vec();
|
||||
if convert_rgba {
|
||||
let rgba_image: RgbaImage = frame.convert();
|
||||
@@ -123,13 +123,14 @@ pub trait CaptureBackendTrait {
|
||||
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
|
||||
/// # Errors
|
||||
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
|
||||
fn get_frame_texture<'a>(
|
||||
fn frame_texture<'a>(
|
||||
&mut self,
|
||||
device: &WgpuDevice,
|
||||
queue: &WgpuQueue,
|
||||
label: Option<&'a str>,
|
||||
) -> Result<WgpuTexture, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
use std::{convert::TryFrom, num::NonZeroU32};
|
||||
let frame = self.frame()?;
|
||||
let rgba_frame: RgbaImage = frame.convert();
|
||||
|
||||
let texture_size = Extent3d {
|
||||
|
||||
+13
-15
@@ -29,20 +29,22 @@
|
||||
/// - The `Query-Device` column signifies reading device capabilities
|
||||
/// - The `OS` column signifies what OS this is availible on.
|
||||
///
|
||||
/// | Backend | Input | Query | Query-Device | OS |
|
||||
/// |--------------------------------|--------------------|--------------------|--------------------|---------------------|
|
||||
/// | Video4Linux(`input-v4l`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux |
|
||||
/// | libuvc(`input-uvc`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
/// | OpenCV(`input-opencv`) | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
/// | IPCamera(`input-ipcam`/OpenCV) | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
/// | GStreamer(`input-gst`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
/// | FFMpeg | * | * | * | Linux, Windows, Mac |
|
||||
/// | AVFoundation | * | * | * | Mac |
|
||||
/// | MSMF | * | * | * | Windows |
|
||||
/// | JS/WASM | * | * | * | Web |
|
||||
/// | Backend | Input | Query | Query-Device | OS |
|
||||
/// |---------------------------------|--------------------|--------------------|--------------------|---------------------|
|
||||
/// | Video4Linux(`input-v4l`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux |
|
||||
/// | libuvc(`input-uvc`) | :white_check_mark: | :white_check_mark: | :white_check_mark: | Linux, Windows, Mac |
|
||||
/// | OpenCV(`input-opencv`)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
/// | IPCamera(`input-ipcam`/OpenCV)^ | :white_check_mark: | :x: | :x: | Linux, Windows, Mac |
|
||||
/// | GStreamer(`input-gst`)^ | :white_check_mark: | :x: | :white_check_mark: | Linux, Windows, Mac |
|
||||
/// | FFMpeg | * | * | * | Linux, Windows, Mac |
|
||||
/// | AVFoundation | * | * | * | Mac |
|
||||
/// | MSMF | * | * | * | Windows |
|
||||
/// | JS/WASM | * | * | * | Web |
|
||||
///
|
||||
/// :white_check_mark: : Working, :warning: : Experimental, :x: : Not Supported, *: Planned
|
||||
///
|
||||
/// ^ = No CameraFormat setting support.
|
||||
///
|
||||
/// ## Feature
|
||||
/// The default feature includes nothing. Anything starting with `input-*` is a feature that enables the specific backend.
|
||||
/// As a general rule of thumb, you would want to keep at least `input-uvc` or other backend that has querying enabled so you can get device information from `nokhwa`.
|
||||
@@ -89,8 +91,6 @@ mod query;
|
||||
mod utils;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
#[cfg(feature = "output-threaded")]
|
||||
mod threaded_camera;
|
||||
|
||||
pub use camera::Camera;
|
||||
pub use camera_traits::*;
|
||||
@@ -98,6 +98,4 @@ pub use error::NokhwaError;
|
||||
#[cfg(feature = "input-ipcam")]
|
||||
pub use network_camera::NetworkCamera;
|
||||
pub use query::query_devices;
|
||||
#[cfg(feature = "output-threaded")]
|
||||
pub use threaded_camera::ThreadedCamera;
|
||||
pub use utils::*;
|
||||
|
||||
@@ -128,3 +128,17 @@ macro_rules! cap_impl_matches {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "input-opencv")]
|
||||
#[macro_export]
|
||||
macro_rules! vector {
|
||||
( $( $elem:expr ),* ) => {
|
||||
{
|
||||
let mut vector = opencv::core::Vector::new();
|
||||
$(
|
||||
vector.push($elem);
|
||||
)*
|
||||
vector
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+13
-7
@@ -6,7 +6,6 @@ use wgpu::{
|
||||
Device as WgpuDevice, Extent3d, ImageCopyTexture, ImageDataLayout, Queue as WgpuQueue,
|
||||
Texture as WgpuTexture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage,
|
||||
};
|
||||
|
||||
/// A struct that supports IP Cameras via the `OpenCV` backend.
|
||||
pub struct NetworkCamera {
|
||||
ip: String,
|
||||
@@ -49,8 +48,8 @@ impl NetworkCamera {
|
||||
/// Gets the frame decoded as a RGB24 frame
|
||||
/// # Errors
|
||||
/// If the backend fails to capture the stream, or if the decoding fails this will error
|
||||
pub fn get_frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
self.opencv_backend.borrow_mut().get_frame()
|
||||
pub fn frame(&self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
||||
self.opencv_backend.borrow_mut().frame()
|
||||
}
|
||||
|
||||
/// The minimum buffer size needed to write the current frame (RGB24). If `rgba` is true, it will instead return the minimum size of the RGBA buffer needed.
|
||||
@@ -64,12 +63,12 @@ impl NetworkCamera {
|
||||
/// Directly writes the current frame(RGB24) into said `buffer`. If `convert_rgba` is true, the buffer written will be written as an RGBA frame instead of a RGB frame. Returns the amount of bytes written on successful capture.
|
||||
/// # Errors
|
||||
/// If the backend fails to get the frame (e.g. already taken, busy, doesn't exist anymore), or [`open_stream()`](CaptureBackendTrait::open_stream()) has not been called yet, this will error.
|
||||
pub fn get_frame_to_buffer(
|
||||
pub fn frame_to_buffer(
|
||||
&self,
|
||||
buffer: &mut [u8],
|
||||
convert_rgba: bool,
|
||||
) -> Result<usize, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
let frame = self.frame()?;
|
||||
let mut frame_data = frame.to_vec();
|
||||
if convert_rgba {
|
||||
let rgba_image: RgbaImage = frame.convert();
|
||||
@@ -84,13 +83,14 @@ impl NetworkCamera {
|
||||
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
|
||||
/// # Errors
|
||||
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
|
||||
pub fn get_frame_texture<'a>(
|
||||
pub fn frame_texture<'a>(
|
||||
&mut self,
|
||||
device: &WgpuDevice,
|
||||
queue: &WgpuQueue,
|
||||
label: Option<&'a str>,
|
||||
) -> Result<WgpuTexture, NokhwaError> {
|
||||
let frame = self.get_frame()?;
|
||||
use std::{convert::TryFrom, num::NonZeroU32};
|
||||
let frame = self.frame()?;
|
||||
let rgba_frame: RgbaImage = frame.convert();
|
||||
|
||||
let texture_size = Extent3d {
|
||||
@@ -144,3 +144,9 @@ impl NetworkCamera {
|
||||
self.opencv_backend.borrow_mut().stop_stream()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetworkCamera {
|
||||
fn drop(&mut self) {
|
||||
self.stop_stream().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -175,7 +175,10 @@ fn query_uvc() -> Result<Vec<CameraInfo>, NokhwaError> {
|
||||
|
||||
#[cfg(feature = "input-gst")]
|
||||
fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
|
||||
use gstreamer::{Caps, DeviceExt, DeviceMonitor, DeviceMonitorExt, DeviceMonitorExtManual};
|
||||
use gstreamer::{
|
||||
prelude::{DeviceExt, DeviceMonitorExt, DeviceMonitorExtManual},
|
||||
Caps, DeviceMonitor,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
if let Err(why) = gstreamer::init() {
|
||||
return Err(NokhwaError::GeneralError(format!(
|
||||
@@ -211,11 +214,11 @@ fn query_gstreamer() -> Result<Vec<CameraInfo>, NokhwaError> {
|
||||
}
|
||||
let mut counter = 0;
|
||||
let devices: Vec<CameraInfo> = device_monitor
|
||||
.get_devices()
|
||||
.devices()
|
||||
.iter_mut()
|
||||
.map(|gst_dev| {
|
||||
let name = DeviceExt::get_display_name(gst_dev);
|
||||
let class = DeviceExt::get_device_class(gst_dev);
|
||||
let name = DeviceExt::display_name(gst_dev);
|
||||
let class = DeviceExt::device_class(gst_dev);
|
||||
counter += 1;
|
||||
CameraInfo::new(
|
||||
name.to_string(),
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::{Camera, NokhwaError};
|
||||
use flume::{Receiver, Sender};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
pub struct ThreadedCamera {
|
||||
camera: Arc<Mutex<Camera>>,
|
||||
thread_handle: JoinHandle<_>,
|
||||
receiver: Receiver<Result<Vec<u8>, NokhwaError>>,
|
||||
sender: Sender<Result<Vec<u8>, NokhwaError>>,
|
||||
}
|
||||
|
||||
impl ThreadedCamera {}
|
||||
|
||||
fn capture(camera: Arc<Mutex<Camera>>) {}
|
||||
Reference in New Issue
Block a user