nokhwa/camera.rs
1/*
2 * Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use nokhwa_core::types::RequestedFormatType;
18use nokhwa_core::{
19 buffer::Buffer,
20 error::NokhwaError,
21 pixel_format::FormatDecoder,
22 traits::CaptureBackendTrait,
23 types::{
24 ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
25 FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
26 },
27};
28use std::{borrow::Cow, collections::HashMap};
29#[cfg(feature = "output-wgpu")]
30use wgpu::{Device as WgpuDevice, Queue as WgpuQueue, Texture as WgpuTexture};
31
32/// The main `Camera` struct. This is the struct that abstracts over all the backends, providing a simplified interface for use.
33pub struct Camera {
34 idx: CameraIndex,
35 api: ApiBackend,
36 device: Box<dyn CaptureBackendTrait>,
37}
38
39impl Camera {
40 /// Create a new camera from an `index` and `format`
41 /// # Errors
42 /// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
43 pub fn new(index: CameraIndex, format: RequestedFormat) -> Result<Self, NokhwaError> {
44 Camera::with_backend(index, format, ApiBackend::Auto)
45 }
46
47 /// Create a new camera from an `index`, `format`, and `backend`. `format` can be `None`.
48 /// # Errors
49 /// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
50 pub fn with_backend(
51 index: CameraIndex,
52 format: RequestedFormat,
53 backend: ApiBackend,
54 ) -> Result<Self, NokhwaError> {
55 let camera_backend = init_camera(&index, format, backend)?;
56
57 Ok(Camera {
58 idx: index,
59 api: backend,
60 device: camera_backend,
61 })
62 }
63
64 /// Create a new `Camera` from raw values.
65 /// # Errors
66 /// This will error if you either have a bad platform configuration (e.g. `input-v4l` but not on linux) or the backend cannot create the camera (e.g. permission denied).
67 #[deprecated(since = "0.10.0", note = "please use `new` instead.")]
68 pub fn new_with(
69 index: CameraIndex,
70 width: u32,
71 height: u32,
72 fps: u32,
73 fourcc: FrameFormat,
74 backend: ApiBackend,
75 ) -> Result<Self, NokhwaError> {
76 let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
77 Camera::with_backend(
78 index,
79 RequestedFormat::with_formats(RequestedFormatType::Exact(camera_format), &[fourcc]),
80 backend,
81 )
82 }
83
84 /// Allows creation of a [`Camera`] with a custom backend. This is useful if you are creating e.g. a custom module.
85 ///
86 /// You **must** have set a format beforehand.
87 pub fn with_custom(
88 idx: CameraIndex,
89 api: ApiBackend,
90 device: Box<dyn CaptureBackendTrait>,
91 ) -> Self {
92 Self { idx, api, device }
93 }
94
95 /// Gets the current Camera's index.
96 #[must_use]
97 pub fn index(&self) -> &CameraIndex {
98 &self.idx
99 }
100
101 /// Sets the current Camera's index. Note that this re-initializes the camera.
102 /// # Errors
103 /// The Backend may fail to initialize.
104 pub fn set_index(&mut self, new_idx: &CameraIndex) -> Result<(), NokhwaError> {
105 {
106 self.device.stop_stream()?;
107 }
108 let new_camera_format = self.device.camera_format();
109 let temp = vec![new_camera_format.format()];
110 let new_camera = init_camera(
111 new_idx,
112 RequestedFormat::with_formats(RequestedFormatType::Exact(new_camera_format), &temp),
113 self.api,
114 )?;
115 self.device = new_camera;
116 Ok(())
117 }
118
119 /// Gets the current Camera's backend
120 #[must_use]
121 pub fn backend(&self) -> ApiBackend {
122 self.api
123 }
124
125 /// Sets the current Camera's backend. Note that this re-initializes the camera.
126 /// # Errors
127 /// The new backend may not exist or may fail to initialize the new camera.
128 pub fn set_backend(&mut self, new_backend: ApiBackend) -> Result<(), NokhwaError> {
129 {
130 self.device.stop_stream()?;
131 }
132 let new_camera_format = self.device.camera_format();
133 let temp = vec![new_camera_format.format()];
134 let new_camera = init_camera(
135 &self.idx,
136 RequestedFormat::with_formats(RequestedFormatType::Exact(new_camera_format), &temp),
137 new_backend,
138 )?;
139 self.device = new_camera;
140 Ok(())
141 }
142
143 /// Gets the camera information such as Name and Index as a [`CameraInfo`].
144 #[must_use]
145 pub fn info(&self) -> &CameraInfo {
146 self.device.camera_info()
147 }
148
149 /// Gets the current [`CameraFormat`].
150 #[must_use]
151 pub fn camera_format(&self) -> CameraFormat {
152 self.device.camera_format()
153 }
154
155 /// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state)
156 /// # Errors
157 /// If the camera can not get its most recent [`CameraFormat`]. this will error.
158 pub fn refresh_camera_format(&mut self) -> Result<CameraFormat, NokhwaError> {
159 self.device.refresh_camera_format()?;
160 Ok(self.device.camera_format())
161 }
162
163 /// Will set the current [`CameraFormat`], using a [`RequestedFormat.`]
164 /// This will reset the current stream if used while stream is opened.
165 ///
166 /// This will also update the cache.
167 ///
168 /// This will return the new [`CameraFormat`]
169 /// # Errors
170 /// If nothing fits the requested criteria, this will return an error.
171 pub fn set_camera_requset(
172 &mut self,
173 request: RequestedFormat,
174 ) -> Result<CameraFormat, NokhwaError> {
175 let new_format = request
176 .fulfill(self.device.compatible_camera_formats()?.as_slice())
177 .ok_or(NokhwaError::GetPropertyError {
178 property: "Compatible Camera Format by request".to_string(),
179 error: "Failed to fufill".to_string(),
180 })?;
181 self.device.set_camera_format(new_format)?;
182 Ok(new_format)
183 }
184
185 #[deprecated(since = "0.10.0", note = "please use `set_camera_requset` instead.")]
186 /// Will set the current [`CameraFormat`]
187 /// This will reset the current stream if used while stream is opened.
188 ///
189 /// This will also update the cache.
190 /// # Errors
191 /// If you started the stream and the camera rejects the new camera format, this will return an error.
192 pub fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
193 self.device.set_camera_format(new_fmt)
194 }
195
196 /// A hashmap of [`Resolution`]s mapped to framerates
197 /// # Errors
198 /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a [`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError).
199 pub fn compatible_list_by_resolution(
200 &mut self,
201 fourcc: FrameFormat,
202 ) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
203 self.device.compatible_list_by_resolution(fourcc)
204 }
205
206 /// A Vector of compatible [`FrameFormat`]s.
207 /// # Errors
208 /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a [`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError).
209 pub fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
210 self.device.compatible_fourcc()
211 }
212
213 /// A Vector of available [`CameraFormat`]s.
214 /// # Errors
215 /// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a [`UnsupportedOperationError`](crate::NokhwaError::UnsupportedOperationError).
216 pub fn compatible_camera_formats(&mut self) -> Result<Vec<CameraFormat>, NokhwaError> {
217 self.device.compatible_camera_formats()
218 }
219
220 /// Gets the current camera resolution (See: [`Resolution`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
221 #[must_use]
222 pub fn resolution(&self) -> Resolution {
223 self.device.resolution()
224 }
225
226 /// Will set the current [`Resolution`]
227 /// This will reset the current stream if used while stream is opened.
228 ///
229 /// This will also update the cache.
230 /// # Errors
231 /// If you started the stream and the camera rejects the new resolution, this will return an error.
232 pub fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
233 self.device.set_resolution(new_res)
234 }
235
236 /// Gets the current camera framerate (See: [`CameraFormat`]).
237 #[must_use]
238 pub fn frame_rate(&self) -> u32 {
239 self.device.frame_rate()
240 }
241
242 /// Will set the current framerate
243 /// This will reset the current stream if used while stream is opened.
244 ///
245 /// This will also update the cache.
246 /// # Errors
247 /// If you started the stream and the camera rejects the new framerate, this will return an error.
248 pub fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
249 self.device.set_frame_rate(new_fps)
250 }
251
252 /// Gets the current camera's frame format (See: [`FrameFormat`], [`CameraFormat`]). This will force refresh to the current latest if it has changed.
253 #[must_use]
254 pub fn frame_format(&self) -> FrameFormat {
255 self.device.frame_format()
256 }
257
258 /// Will set the current [`FrameFormat`]
259 /// This will reset the current stream if used while stream is opened.
260 ///
261 /// This will also update the cache.
262 /// # Errors
263 /// If you started the stream and the camera rejects the new frame format, this will return an error.
264 pub fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
265 self.device.set_frame_format(fourcc)
266 }
267
268 /// Gets the current supported list of [`KnownCameraControl`](crate::utils::KnownCameraControl)
269 /// # Errors
270 /// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
271 pub fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
272 Ok(self
273 .device
274 .camera_controls()?
275 .iter()
276 .map(CameraControl::control)
277 .collect())
278 }
279
280 /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`.
281 /// # Errors
282 /// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
283 pub fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
284 let known_controls = self.supported_camera_controls()?;
285 let maybe_camera_controls = known_controls
286 .iter()
287 .map(|x| self.camera_control(*x))
288 .filter(Result::is_ok)
289 .map(Result::unwrap)
290 .collect::<Vec<CameraControl>>();
291
292 Ok(maybe_camera_controls)
293 }
294
295 /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`.
296 /// # Errors
297 /// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
298 pub fn camera_controls_string(&self) -> Result<HashMap<String, CameraControl>, NokhwaError> {
299 let known_controls = self.supported_camera_controls()?;
300 let maybe_camera_controls = known_controls
301 .iter()
302 .map(|x| (x.to_string(), self.camera_control(*x)))
303 .filter(|(_, x)| x.is_ok())
304 .map(|(c, x)| (c, Result::unwrap(x)))
305 .collect::<Vec<(String, CameraControl)>>();
306 let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
307
308 for (kc, cc) in maybe_camera_controls {
309 control_map.insert(kc, cc);
310 }
311
312 Ok(control_map)
313 }
314
315 /// Gets the current supported list of [`CameraControl`]s keyed by its name as a `String`.
316 /// # Errors
317 /// If the list cannot be collected, this will error. This can be treated as a "nothing supported".
318 pub fn camera_controls_known_camera_controls(
319 &self,
320 ) -> Result<HashMap<KnownCameraControl, CameraControl>, NokhwaError> {
321 let known_controls = self.supported_camera_controls()?;
322 let maybe_camera_controls = known_controls
323 .iter()
324 .map(|x| (*x, self.camera_control(*x)))
325 .filter(|(_, x)| x.is_ok())
326 .map(|(c, x)| (c, Result::unwrap(x)))
327 .collect::<Vec<(KnownCameraControl, CameraControl)>>();
328 let mut control_map = HashMap::with_capacity(maybe_camera_controls.len());
329
330 for (kc, cc) in maybe_camera_controls {
331 control_map.insert(kc, cc);
332 }
333
334 Ok(control_map)
335 }
336
337 /// Gets the value of [`KnownCameraControl`].
338 /// # Errors
339 /// If the `control` is not supported or there is an error while getting the camera control values (e.g. unexpected value, too high, etc)
340 /// this will error.
341 pub fn camera_control(
342 &self,
343 control: KnownCameraControl,
344 ) -> Result<CameraControl, NokhwaError> {
345 self.device.camera_control(control)
346 }
347
348 /// Sets the control to `control` in the camera.
349 /// Usually, the pipeline is calling [`camera_control()`](crate::camera_traits::CaptureBackendTrait::camera_control), getting a camera control that way
350 /// then calling [`value()`](crate::utils::CameraControl::value()) to get a [`ControlValueSetter`](crate::utils::ControlValueSetter) and setting the value that way.
351 /// # Errors
352 /// If the `control` is not supported, the value is invalid (less than min, greater than max, not in step), or there was an error setting the control,
353 /// this will error.
354 pub fn set_camera_control(
355 &mut self,
356 id: KnownCameraControl,
357 value: ControlValueSetter,
358 ) -> Result<(), NokhwaError> {
359 self.device.set_camera_control(id, value)
360 }
361
362 /// Will open the camera stream with set parameters. This will be called internally if you try and call [`frame()`](CaptureBackendTrait::frame()) before you call [`open_stream()`](CaptureBackendTrait::open_stream()).
363 /// # Errors
364 /// If the specific backend fails to open the camera (e.g. already taken, busy, doesn't exist anymore) this will error.
365 pub fn open_stream(&mut self) -> Result<(), NokhwaError> {
366 self.device.open_stream()
367 }
368
369 /// Checks if stream if open. If it is, it will return true.
370 #[must_use]
371 pub fn is_stream_open(&self) -> bool {
372 self.device.is_stream_open()
373 }
374
375 /// Will get a frame from the camera as a Raw RGB image buffer. Depending on the backend, if you have not called [`open_stream()`](CaptureBackendTrait::open_stream()) before you called this,
376 /// it will either return an error.
377 /// # Errors
378 /// 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,
379 /// this will error.
380 pub fn frame(&mut self) -> Result<Buffer, NokhwaError> {
381 self.device.frame()
382 }
383
384 /// Will get a frame from the camera **without** any processing applied, meaning you will usually get a frame you need to decode yourself.
385 /// # Errors
386 /// 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.
387 pub fn frame_raw(&mut self) -> Result<Cow<'_, [u8]>, NokhwaError> {
388 match self.device.frame_raw() {
389 Ok(f) => Ok(f),
390 Err(why) => Err(why),
391 }
392 }
393
394 /// Directly writes the current frame into said `buffer`.
395 /// # Errors
396 /// 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.
397 pub fn write_frame_to_buffer<F: FormatDecoder>(
398 &mut self,
399 buffer: &mut [u8],
400 ) -> Result<(), NokhwaError> {
401 self.device.frame()?.decode_image_to_buffer::<F>(buffer)
402 }
403
404 #[cfg(feature = "output-wgpu")]
405 #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-wgpu")))]
406 /// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
407 /// # Errors
408 /// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
409 pub fn frame_texture<'a, F: FormatDecoder>(
410 &mut self,
411 device: &WgpuDevice,
412 queue: &WgpuQueue,
413 label: Option<&'a str>,
414 ) -> Result<WgpuTexture, NokhwaError> {
415 self.device.frame_texture(device, queue, label)
416 }
417
418 /// Will drop the stream.
419 /// # Errors
420 /// Please check the `Quirks` section of each backend.
421 pub fn stop_stream(&mut self) -> Result<(), NokhwaError> {
422 self.device.stop_stream()
423 }
424}
425
426impl Drop for Camera {
427 fn drop(&mut self) {
428 self.stop_stream().unwrap();
429 }
430}
431
432// TODO: Update as we go
433#[allow(clippy::ifs_same_cond)]
434fn figure_out_auto() -> Option<ApiBackend> {
435 let platform = std::env::consts::OS;
436 let mut cap = ApiBackend::Auto;
437 if cfg!(feature = "input-v4l") && platform == "linux" {
438 cap = ApiBackend::Video4Linux;
439 } else if cfg!(feature = "input-msmf") && platform == "windows" {
440 cap = ApiBackend::MediaFoundation;
441 } else if cfg!(feature = "input-avfoundation") && (platform == "macos" || platform == "ios") {
442 cap = ApiBackend::AVFoundation;
443 } else if cfg!(feature = "input-opencv") {
444 cap = ApiBackend::OpenCv;
445 }
446 if cap == ApiBackend::Auto {
447 return None;
448 }
449 Some(cap)
450}
451
452macro_rules! cap_impl_fn {
453 {
454 $( ($backend:expr, $init_fn:ident, $cfg:meta, $backend_name:ident) ),+
455 } => {
456 $(
457 paste::paste! {
458 #[cfg ($cfg) ]
459 fn [< init_ $backend_name>](idx: &CameraIndex, setting: RequestedFormat) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
460 use crate::backends::capture::$backend;
461 match <$backend>::$init_fn(idx, setting) {
462 Ok(cap) => Some(Ok(cap.into())),
463 Err(why) => Some(Err(why)),
464 }
465 }
466 #[cfg(not( $cfg ))]
467 fn [< init_ $backend_name>](_idx: &CameraIndex, _setting: RequestedFormat) -> Option<Result<Box<dyn CaptureBackendTrait>, NokhwaError>> {
468 None
469 }
470 }
471 )+
472 };
473}
474
475macro_rules! cap_impl_matches {
476 {
477 $use_backend: expr, $index:expr, $setting:expr,
478 $( ($feature:expr, $backend:ident, $fn:ident) ),+
479 } => {
480 {
481 let i = $index;
482 let s = $setting;
483 match $use_backend {
484 ApiBackend::Auto => match figure_out_auto() {
485 Some(cap) => match cap {
486 $(
487 ApiBackend::$backend => {
488 match cfg!(feature = $feature) {
489 true => {
490 match $fn(i,s) {
491 Some(cap) => match cap {
492 Ok(c) => c,
493 Err(why) => return Err(why),
494 }
495 None => {
496 return Err(NokhwaError::NotImplementedError(
497 "Platform requirements not satisfied (Wrong Platform - Not Implemented).".to_string(),
498 ));
499 }
500 }
501 }
502 false => {
503 return Err(NokhwaError::NotImplementedError(
504 "Platform requirements not satisfied. (Wrong Platform - Not Selected)".to_string(),
505 ));
506 }
507 }
508 }
509 )+
510 _ => {
511 return Err(NokhwaError::NotImplementedError(
512 "Platform requirements not satisfied. (Invalid Backend)".to_string(),
513 ));
514 }
515 }
516 None => {
517 return Err(NokhwaError::NotImplementedError(
518 "Platform requirements not satisfied. (No Selection)".to_string(),
519 ));
520 }
521 }
522 $(
523 ApiBackend::$backend => {
524 match cfg!(feature = $feature) {
525 true => {
526 match $fn(i,s) {
527 Some(cap) => match cap {
528 Ok(c) => c,
529 Err(why) => return Err(why),
530 }
531 None => {
532 return Err(NokhwaError::NotImplementedError(
533 "Platform requirements not satisfied (Wrong Platform - Not Implemented).".to_string(),
534 ));
535 }
536 }
537 }
538 false => {
539 return Err(NokhwaError::NotImplementedError(
540 "Platform requirements not satisfied. (Wrong Platform - Not Selected)".to_string(),
541 ));
542 }
543 }
544 }
545 )+
546
547 _ => {
548 return Err(NokhwaError::NotImplementedError(
549 "Platform requirements not satisfied. (Wrong Platform - Not Selected)".to_string(),
550 ));
551 }
552 }
553 }
554 }
555}
556
557cap_impl_fn! {
558 // (GStreamerCaptureDevice, new, feature = "input-gst", gst),
559 (OpenCvCaptureDevice, new, feature = "input-opencv", opencv),
560 // (UVCCaptureDevice, create, feature = "input-uvc", uvc),
561 (V4LCaptureDevice, new, all(feature = "input-v4l", target_os = "linux"), v4l),
562 (MediaFoundationCaptureDevice, new, all(feature = "input-msmf", target_os = "windows"), msmf),
563 (AVFoundationCaptureDevice, new, all(feature = "input-avfoundation", any(target_os = "macos", target_os = "ios")), avfoundation)
564}
565
566fn init_camera(
567 index: &CameraIndex,
568 format: RequestedFormat,
569 backend: ApiBackend,
570) -> Result<Box<dyn CaptureBackendTrait>, NokhwaError> {
571 let camera_backend = cap_impl_matches! {
572 backend, index, format,
573 ("input-v4l", Video4Linux, init_v4l),
574 ("input-msmf", MediaFoundation, init_msmf),
575 ("input-avfoundation", AVFoundation, init_avfoundation),
576 ("input-opencv", OpenCv, init_opencv)
577 };
578 Ok(camera_backend)
579}
580
581#[cfg(feature = "camera-sync-impl")]
582unsafe impl Send for Camera {}