Skip to main content

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 {}