fix gstreamer streaming, impl gstreamer query-device capabilities, fix lints, attempt fix openCV

This commit is contained in:
l1npengtul
2021-07-02 02:24:01 +09:00
parent ec51884e64
commit 3b4878d1cb
15 changed files with 990 additions and 503 deletions
+12 -8
View File
@@ -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"]
+14 -12
View File
@@ -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.
+1 -1
View File
@@ -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"]
+47 -3
View File
@@ -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
View File
@@ -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))
}
+224 -202
View File
@@ -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(())
}
+5 -5
View File
@@ -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(
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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::*;
+14
View File
@@ -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
View File
@@ -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
View File
@@ -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(),
-16
View File
@@ -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>>) {}