videocall_nokhwa_bindings_linux/
lib.rs

1/*
2 * Copyright 2025 Security Union LLC
3 *
4 * Licensed under either of
5 *
6 * * Apache License, Version 2.0
7 *   (http://www.apache.org/licenses/LICENSE-2.0)
8 * * MIT license
9 *   (http://opensource.org/licenses/MIT)
10 *
11 * at your option.
12 *
13 * Unless you explicitly state otherwise, any contribution intentionally
14 * submitted for inclusion in the work by you, as defined in the Apache-2.0
15 * license, shall be dual licensed as above, without any additional terms or
16 * conditions.
17 */
18
19#![allow(clippy::all)] // Ignores all Clippy warnings
20#![allow(warnings)] // Overrides --deny warnings
21
22/*
23 * Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
24 *
25 * Licensed under the Apache License, Version 2.0 (the "License");
26 * you may not use this file except in compliance with the License.
27 * You may obtain a copy of the License at
28 *
29 *     http://www.apache.org/licenses/LICENSE-2.0
30 *
31 * Unless required by applicable law or agreed to in writing, software
32 * distributed under the License is distributed on an "AS IS" BASIS,
33 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34 * See the License for the specific language governing permissions and
35 * limitations under the License.
36 */
37
38#[cfg(target_os = "linux")]
39mod internal {
40    use std::{
41        borrow::Cow,
42        collections::HashMap,
43        io::{self, ErrorKind},
44    };
45    use v4l::v4l_sys::{
46        V4L2_CID_BACKLIGHT_COMPENSATION, V4L2_CID_BRIGHTNESS, V4L2_CID_CONTRAST, V4L2_CID_EXPOSURE,
47        V4L2_CID_FOCUS_RELATIVE, V4L2_CID_GAIN, V4L2_CID_GAMMA, V4L2_CID_HUE,
48        V4L2_CID_IRIS_RELATIVE, V4L2_CID_PAN_RELATIVE, V4L2_CID_SATURATION, V4L2_CID_SHARPNESS,
49        V4L2_CID_TILT_RELATIVE, V4L2_CID_WHITE_BALANCE_TEMPERATURE, V4L2_CID_ZOOM_RELATIVE,
50    };
51    use v4l::{
52        control::{Control, Flags, Type, Value},
53        frameinterval::FrameIntervalEnum,
54        framesize::FrameSizeEnum,
55        io::traits::CaptureStream,
56        prelude::MmapStream,
57        video::{capture::Parameters, Capture},
58        Device, Format, FourCC,
59    };
60    use videocall_nokhwa_core::{
61        buffer::Buffer,
62        error::NokhwaError,
63        traits::CaptureBackendTrait,
64        types::{
65            ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo,
66            ControlValueDescription, ControlValueSetter, FrameFormat, KnownCameraControl,
67            KnownCameraControlFlag, RequestedFormat, RequestedFormatType, Resolution,
68        },
69    };
70
71    /// Attempts to convert a [`KnownCameraControl`] into a V4L2 Control ID.
72    /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
73    #[allow(clippy::cast_possible_truncation)]
74    pub fn known_camera_control_to_id(ctrl: KnownCameraControl) -> u32 {
75        match ctrl {
76            KnownCameraControl::Brightness => V4L2_CID_BRIGHTNESS,
77            KnownCameraControl::Contrast => V4L2_CID_CONTRAST,
78            KnownCameraControl::Hue => V4L2_CID_HUE,
79            KnownCameraControl::Saturation => V4L2_CID_SATURATION,
80            KnownCameraControl::Sharpness => V4L2_CID_SHARPNESS,
81            KnownCameraControl::Gamma => V4L2_CID_GAMMA,
82            KnownCameraControl::WhiteBalance => V4L2_CID_WHITE_BALANCE_TEMPERATURE,
83            KnownCameraControl::BacklightComp => V4L2_CID_BACKLIGHT_COMPENSATION,
84            KnownCameraControl::Gain => V4L2_CID_GAIN,
85            KnownCameraControl::Pan => V4L2_CID_PAN_RELATIVE,
86            KnownCameraControl::Tilt => V4L2_CID_TILT_RELATIVE,
87            KnownCameraControl::Zoom => V4L2_CID_ZOOM_RELATIVE,
88            KnownCameraControl::Exposure => V4L2_CID_EXPOSURE,
89            KnownCameraControl::Iris => V4L2_CID_IRIS_RELATIVE,
90            KnownCameraControl::Focus => V4L2_CID_FOCUS_RELATIVE,
91            KnownCameraControl::Other(id) => id as u32,
92        }
93    }
94
95    /// Attempts to convert a [`u32`] V4L2 Control ID into a [`KnownCameraControl`]
96    /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
97    #[allow(clippy::cast_lossless)]
98    pub fn id_to_known_camera_control(id: u32) -> KnownCameraControl {
99        match id {
100            V4L2_CID_BRIGHTNESS => KnownCameraControl::Brightness,
101            V4L2_CID_CONTRAST => KnownCameraControl::Contrast,
102            V4L2_CID_HUE => KnownCameraControl::Hue,
103            V4L2_CID_SATURATION => KnownCameraControl::Saturation,
104            V4L2_CID_SHARPNESS => KnownCameraControl::Sharpness,
105            V4L2_CID_GAMMA => KnownCameraControl::Gamma,
106            V4L2_CID_WHITE_BALANCE_TEMPERATURE => KnownCameraControl::WhiteBalance,
107            V4L2_CID_BACKLIGHT_COMPENSATION => KnownCameraControl::BacklightComp,
108            V4L2_CID_GAIN => KnownCameraControl::Gain,
109            V4L2_CID_PAN_RELATIVE => KnownCameraControl::Pan,
110            V4L2_CID_TILT_RELATIVE => KnownCameraControl::Tilt,
111            V4L2_CID_ZOOM_RELATIVE => KnownCameraControl::Zoom,
112            V4L2_CID_EXPOSURE => KnownCameraControl::Exposure,
113            V4L2_CID_IRIS_RELATIVE => KnownCameraControl::Iris,
114            V4L2_CID_FOCUS_RELATIVE => KnownCameraControl::Focus,
115            id => KnownCameraControl::Other(id as u128),
116        }
117    }
118
119    /// query v4l2 cameras
120    #[allow(clippy::unnecessary_wraps)]
121    #[allow(clippy::cast_possible_truncation)]
122    pub fn query() -> Result<Vec<CameraInfo>, NokhwaError> {
123        Ok({
124            let camera_info: Vec<CameraInfo> = v4l::context::enum_devices()
125                .iter()
126                .map(|node| {
127                    CameraInfo::new(
128                        &node
129                            .name()
130                            .unwrap_or(format!("{}", node.path().to_string_lossy())),
131                        &format!("Video4Linux Device @ {}", node.path().to_string_lossy()),
132                        "",
133                        CameraIndex::Index(node.index() as u32),
134                    )
135                })
136                .collect();
137            camera_info
138        })
139    }
140
141    type SharedDevice = std::sync::Arc<std::sync::Mutex<Device>>;
142    type WeakSharedDevice = std::sync::Weak<std::sync::Mutex<Device>>;
143
144    struct WeakSharedDeviceEntry {
145        device: WeakSharedDevice,
146        index: usize,
147    }
148
149    type SharedDeviceList = std::sync::OnceLock<std::sync::Mutex<Vec<WeakSharedDeviceEntry>>>;
150
151    /// Global array that keep track of every Device that are currently open.
152    /// This is used to open multiple handle to the same device.
153    /// This is a workaround for the fact that the v4l2 backend does not support multiple handles to the same device.
154    /// This replicate behavior of MF backend.
155    /// The reference is a reference of Weak<Mutex<Device>>, so that the Device can be dropped when the last handle is closed.
156    /// This list might need some cleanup because it will accumulate every device that is opened, this should not be a problem because the list should not grow too much.
157    /// The list is also protected by a mutex, so it should be thread safe.
158    static DEVICES: SharedDeviceList = std::sync::OnceLock::new();
159
160    fn cleanup_dropped_devices(devices: &mut Vec<WeakSharedDeviceEntry>) {
161        devices.retain(|entry| entry.device.strong_count() > 0);
162    }
163
164    fn new_shared_device(index: usize) -> Result<SharedDevice, NokhwaError> {
165        let mut devices = DEVICES
166            .get_or_init(|| std::sync::Mutex::new(Vec::new()))
167            .lock()
168            .map_err(|e| NokhwaError::InitializeError {
169                backend: ApiBackend::Video4Linux,
170                error: format!("Fail to lock global device list mutex: {}", e),
171            })?;
172
173        // do some cleanup, this will avoid here memory to grow forever
174        // if for some reason someone has tons of camera plugged in
175        cleanup_dropped_devices(&mut devices);
176
177        if let Some(entry) = devices.iter().find(|entry| entry.index == index) {
178            if let Some(device) = entry.device.upgrade() {
179                return Ok(device);
180            }
181        }
182
183        // Cleanup a second, the device we are interested might have been dropped during before upgrade call
184        // For this point on we are assured that the device is not in the list
185        cleanup_dropped_devices(&mut devices);
186
187        // Let's be extra sure, this code should never panic, but maybe will help catch some race condition
188        assert!(
189            devices.iter().find(|entry| entry.index == index).is_none(),
190            "Device {index} should not be in the list"
191        );
192
193        // Now we can open the device, and never run into a busy io error,
194        // as long as the device isn't opened by other programs.
195        let device = match Device::new(index) {
196            Ok(dev) => dev,
197            Err(why) => {
198                return Err(NokhwaError::OpenDeviceError(
199                    index.to_string(),
200                    format!("V4L2 Error: {}", why),
201                ))
202            }
203        };
204
205        let device = std::sync::Arc::new(std::sync::Mutex::new(device));
206        devices.push(WeakSharedDeviceEntry {
207            device: std::sync::Arc::downgrade(&device),
208            index,
209        });
210
211        // Last check to be sure that every devices have a unique index
212        // and that the data isn't corrupted
213        if devices.len() > 1 {
214            assert_eq!(
215                devices
216                    .windows(2)
217                    .filter(|window| window[0].index == window[1].index)
218                    .count(),
219                devices.len(),
220                "Device list should not contain duplicate indexes"
221            );
222        }
223
224        Ok(device)
225    }
226
227    fn get_device_format(device: &Device) -> Result<CameraFormat, NokhwaError> {
228        match device.format() {
229            Ok(format) => {
230                let frame_format =
231                    fourcc_to_frameformat(format.fourcc).ok_or(NokhwaError::GetPropertyError {
232                        property: "FrameFormat".to_string(),
233                        error: "unsupported".to_string(),
234                    })?;
235
236                let fps = match device.params() {
237                    Ok(params) => {
238                        if params.interval.numerator != 1
239                            || params.interval.denominator % params.interval.numerator != 0
240                        {
241                            return Err(NokhwaError::GetPropertyError {
242                                property: "V4L2 FrameRate".to_string(),
243                                error: format!(
244                                    "Framerate not whole number: {} / {}",
245                                    params.interval.denominator, params.interval.numerator
246                                ),
247                            });
248                        }
249
250                        if params.interval.numerator == 1 {
251                            params.interval.denominator
252                        } else {
253                            params.interval.denominator / params.interval.numerator
254                        }
255                    }
256                    Err(why) => {
257                        return Err(NokhwaError::GetPropertyError {
258                            property: "V4L2 FrameRate".to_string(),
259                            error: why.to_string(),
260                        })
261                    }
262                };
263
264                Ok(CameraFormat::new(
265                    Resolution::new(format.width, format.height),
266                    frame_format,
267                    fps,
268                ))
269            }
270            Err(why) => Err(NokhwaError::GetPropertyError {
271                property: "parameters".to_string(),
272                error: why.to_string(),
273            }),
274        }
275    }
276
277    /// The backend struct that interfaces with V4L2.
278    /// To see what this does, please see [`CaptureBackendTrait`].
279    /// # Quirks
280    /// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_frame_rate()`](CaptureBackendTrait::set_frame_rate), or [`set_frame_format()`](CaptureBackendTrait::set_frame_format) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format).
281    pub struct V4LCaptureDevice<'a> {
282        camera_format: CameraFormat,
283        camera_info: CameraInfo,
284        device: SharedDevice,
285        stream_handle: Option<MmapStream<'a>>,
286    }
287
288    impl<'a> V4LCaptureDevice<'a> {
289        /// Creates a new capture device using the `V4L2` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
290        /// # Errors
291        /// This function will error if the camera is currently busy or if `V4L2` can't read device information.
292        #[allow(clippy::too_many_lines)]
293        pub fn new(index: &CameraIndex, cam_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
294            let index = index.clone();
295
296            let shared_device = new_shared_device(index.as_index()? as usize)?;
297            let device = shared_device
298                .lock()
299                .map_err(|e| NokhwaError::InitializeError {
300                    backend: ApiBackend::Video4Linux,
301                    error: format!("Fail to lock device mutex: {}", e),
302                })?;
303
304            // get all formats
305            // get all fcc
306            let mut camera_formats = vec![];
307
308            let frame_formats = match device.enum_formats() {
309                Ok(formats) => {
310                    let mut frame_format_vec = vec![];
311                    formats
312                        .iter()
313                        .for_each(|fmt| frame_format_vec.push(fmt.fourcc));
314                    frame_format_vec.dedup();
315                    Ok(frame_format_vec)
316                }
317                Err(why) => Err(NokhwaError::GetPropertyError {
318                    property: "FrameFormat".to_string(),
319                    error: why.to_string(),
320                }),
321            }?;
322
323            for ff in frame_formats {
324                let framefmt = match fourcc_to_frameformat(ff) {
325                    Some(s) => s,
326                    None => continue,
327                };
328                // i write unmaintainable blobs of code because i am so cute uwu~~
329                let mut formats = device
330                    .enum_framesizes(ff)
331                    .map_err(|why| NokhwaError::GetPropertyError {
332                        property: "ResolutionList".to_string(),
333                        error: why.to_string(),
334                    })?
335                    .into_iter()
336                    .flat_map(|x| {
337                        match x.size {
338                            FrameSizeEnum::Discrete(d) => {
339                                [Resolution::new(d.width, d.height)].to_vec()
340                            }
341                            // we step over each step, getting a new resolution.
342                            FrameSizeEnum::Stepwise(s) => (s.min_width..s.max_width)
343                                .step_by(s.step_width as usize)
344                                .zip((s.min_height..s.max_height).step_by(s.step_height as usize))
345                                .map(|(x, y)| Resolution::new(x, y))
346                                .collect(),
347                        }
348                    })
349                    .flat_map(|res| {
350                        device
351                            .enum_frameintervals(ff, res.x(), res.y())
352                            .unwrap_or_default()
353                            .into_iter()
354                            .flat_map(|x| match x.interval {
355                                FrameIntervalEnum::Discrete(dis) => {
356                                    if dis.numerator == 1 {
357                                        vec![CameraFormat::new(
358                                            Resolution::new(x.width, x.height),
359                                            framefmt,
360                                            dis.denominator,
361                                        )]
362                                    } else {
363                                        vec![]
364                                    }
365                                }
366                                FrameIntervalEnum::Stepwise(step) => {
367                                    let mut intvec = vec![];
368                                    for fstep in (step.min.numerator..=step.max.numerator)
369                                        .step_by(step.step.numerator as usize)
370                                    {
371                                        if step.max.denominator != 1 || step.min.denominator != 1 {
372                                            intvec.push(CameraFormat::new(
373                                                Resolution::new(x.width, x.height),
374                                                framefmt,
375                                                fstep,
376                                            ));
377                                        }
378                                    }
379                                    intvec
380                                }
381                            })
382                    })
383                    .collect::<Vec<CameraFormat>>();
384                camera_formats.append(&mut formats);
385            }
386
387            let format = cam_fmt
388                .fulfill(&camera_formats)
389                .ok_or(NokhwaError::GetPropertyError {
390                    property: "CameraFormat".to_string(),
391                    error: "Failed to Fufill".to_string(),
392                })?;
393
394            let current_format = get_device_format(&device)?;
395
396            if current_format.width() != format.width()
397                || current_format.height() != format.height()
398                || current_format.format() != format.format()
399            {
400                if let Err(why) = device.set_format(&Format::new(
401                    format.width(),
402                    format.height(),
403                    frameformat_to_fourcc(format.format()),
404                )) {
405                    return Err(NokhwaError::SetPropertyError {
406                        property: "Resolution, FrameFormat".to_string(),
407                        value: format.to_string(),
408                        error: why.to_string(),
409                    });
410                }
411            }
412
413            if current_format.frame_rate() != format.frame_rate() {
414                if let Err(why) = device.set_params(&Parameters::with_fps(format.frame_rate())) {
415                    return Err(NokhwaError::SetPropertyError {
416                        property: "Frame rate".to_string(),
417                        value: format.frame_rate().to_string(),
418                        error: why.to_string(),
419                    });
420                }
421            }
422
423            let device_caps = device
424                .query_caps()
425                .map_err(|why| NokhwaError::GetPropertyError {
426                    property: "Device Capabilities".to_string(),
427                    error: why.to_string(),
428                })?;
429
430            drop(device);
431
432            let mut v4l2 = V4LCaptureDevice {
433                camera_format: format,
434                camera_info: CameraInfo::new(
435                    &device_caps.card,
436                    &device_caps.driver,
437                    &format!("{} {:?}", device_caps.bus, device_caps.version),
438                    index,
439                ),
440                device: shared_device,
441                stream_handle: None,
442            };
443
444            v4l2.force_refresh_camera_format()?;
445            if v4l2.camera_format() != format {
446                return Err(NokhwaError::SetPropertyError {
447                    property: "CameraFormat".to_string(),
448                    value: String::new(),
449                    error: "Not same/Rejected".to_string(),
450                });
451            }
452
453            Ok(v4l2)
454        }
455
456        /// Create a new `V4L2` Camera with desired settings. This may or may not work.
457        /// # Errors
458        /// This function will error if the camera is currently busy or if `V4L2` can't read device information.
459        #[deprecated(since = "0.10.0", note = "please use `new` instead.")]
460        #[allow(clippy::needless_pass_by_value)]
461        pub fn new_with(
462            index: CameraIndex,
463            width: u32,
464            height: u32,
465            fps: u32,
466            fourcc: FrameFormat,
467        ) -> Result<Self, NokhwaError> {
468            let camera_format = CameraFormat::new_from(width, height, fourcc, fps);
469            V4LCaptureDevice::new(
470                &index,
471                RequestedFormat::with_formats(
472                    RequestedFormatType::Exact(camera_format),
473                    vec![camera_format.format()].as_slice(),
474                ),
475            )
476        }
477
478        fn lock_device(&self) -> Result<std::sync::MutexGuard<'_, Device>, NokhwaError> {
479            self.device
480                .lock()
481                .map_err(|e| NokhwaError::GeneralError(format!("Failed to lock device: {}", e)))
482        }
483
484        fn get_resolution_list(&self, fourcc: FrameFormat) -> Result<Vec<Resolution>, NokhwaError> {
485            let format = frameformat_to_fourcc(fourcc);
486
487            match self.lock_device()?.enum_framesizes(format) {
488                Ok(frame_sizes) => {
489                    let mut resolutions = vec![];
490                    for frame_size in frame_sizes {
491                        match frame_size.size {
492                            FrameSizeEnum::Discrete(dis) => {
493                                resolutions.push(Resolution::new(dis.width, dis.height));
494                            }
495                            FrameSizeEnum::Stepwise(step) => {
496                                resolutions.push(Resolution::new(step.min_width, step.min_height));
497                                resolutions.push(Resolution::new(step.max_width, step.max_height));
498                                // TODO: Respect step size
499                            }
500                        }
501                    }
502                    Ok(resolutions)
503                }
504                Err(why) => Err(NokhwaError::GetPropertyError {
505                    property: "Resolutions".to_string(),
506                    error: why.to_string(),
507                }),
508            }
509        }
510
511        /// Force refreshes the inner [`CameraFormat`] state.
512        /// # Errors
513        /// If the internal representation in the driver is invalid, this will error.
514        pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
515            let camera_format = get_device_format(&*self.lock_device()?)?;
516            self.camera_format = camera_format;
517            Ok(())
518        }
519    }
520
521    impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
522        fn backend(&self) -> ApiBackend {
523            ApiBackend::Video4Linux
524        }
525
526        fn camera_info(&self) -> &CameraInfo {
527            &self.camera_info
528        }
529
530        fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
531            self.force_refresh_camera_format()
532        }
533
534        fn camera_format(&self) -> CameraFormat {
535            self.camera_format
536        }
537
538        fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
539            let device = self.lock_device()?;
540            let prev_format = match Capture::format(&*device) {
541                Ok(fmt) => fmt,
542                Err(why) => {
543                    return Err(NokhwaError::GetPropertyError {
544                        property: "Resolution, FrameFormat".to_string(),
545                        error: why.to_string(),
546                    })
547                }
548            };
549            let prev_fps = match Capture::params(&*device) {
550                Ok(fps) => fps,
551                Err(why) => {
552                    return Err(NokhwaError::GetPropertyError {
553                        property: "Frame rate".to_string(),
554                        error: why.to_string(),
555                    })
556                }
557            };
558
559            let v4l_fcc = match new_fmt.format() {
560                FrameFormat::MJPEG => FourCC::new(b"MJPG"),
561                FrameFormat::YUYV => FourCC::new(b"YUYV"),
562                FrameFormat::GRAY => FourCC::new(b"GRAY"),
563                FrameFormat::RAWRGB => FourCC::new(b"RGB3"),
564                FrameFormat::RAWBGR => FourCC::new(b"BGR3"),
565                FrameFormat::NV12 => FourCC::new(b"NV12"),
566            };
567
568            let format = Format::new(new_fmt.width(), new_fmt.height(), v4l_fcc);
569            let frame_rate = Parameters::with_fps(new_fmt.frame_rate());
570
571            if let Err(why) = Capture::set_format(&*device, &format) {
572                return Err(NokhwaError::SetPropertyError {
573                    property: "Resolution, FrameFormat".to_string(),
574                    value: format.to_string(),
575                    error: why.to_string(),
576                });
577            }
578            if let Err(why) = Capture::set_params(&*device, &frame_rate) {
579                return Err(NokhwaError::SetPropertyError {
580                    property: "Frame rate".to_string(),
581                    value: frame_rate.to_string(),
582                    error: why.to_string(),
583                });
584            }
585
586            drop(device);
587
588            if self.stream_handle.is_some() {
589                return match self.open_stream() {
590                    Ok(_) => Ok(()),
591                    Err(why) => {
592                        // undo
593                        let device = self.lock_device()?;
594                        if let Err(why) = Capture::set_format(&*device, &prev_format) {
595                            return Err(NokhwaError::SetPropertyError {
596                                property: format!("Attempt undo due to stream acquisition failure with error {}. Resolution, FrameFormat", why),
597                                value: prev_format.to_string(),
598                                error: why.to_string(),
599                            });
600                        }
601                        if let Err(why) = Capture::set_params(&*device, &prev_fps) {
602                            return Err(NokhwaError::SetPropertyError {
603                                property:
604                                format!("Attempt undo due to stream acquisition failure with error {}. Frame rate", why),
605                                value: prev_fps.to_string(),
606                                error: why.to_string(),
607                            });
608                        }
609                        Err(why)
610                    }
611                };
612            }
613            self.camera_format = new_fmt;
614
615            self.force_refresh_camera_format()?;
616            if self.camera_format != new_fmt {
617                return Err(NokhwaError::SetPropertyError {
618                    property: "CameraFormat".to_string(),
619                    value: new_fmt.to_string(),
620                    error: "Rejected".to_string(),
621                });
622            }
623
624            Ok(())
625        }
626
627        fn compatible_list_by_resolution(
628            &mut self,
629            fourcc: FrameFormat,
630        ) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
631            let resolutions = self.get_resolution_list(fourcc)?;
632            let format = frameformat_to_fourcc(fourcc);
633            let mut res_map = HashMap::new();
634            for res in resolutions {
635                let mut compatible_fps = vec![];
636                match self
637                    .lock_device()?
638                    .enum_frameintervals(format, res.width(), res.height())
639                {
640                    Ok(intervals) => {
641                        for interval in intervals {
642                            match interval.interval {
643                                FrameIntervalEnum::Discrete(dis) => {
644                                    compatible_fps.push(dis.denominator);
645                                }
646                                FrameIntervalEnum::Stepwise(step) => {
647                                    for fstep in (step.min.numerator..step.max.numerator)
648                                        .step_by(step.step.numerator as usize)
649                                    {
650                                        if step.max.denominator != 1 || step.min.denominator != 1 {
651                                            compatible_fps.push(fstep);
652                                        }
653                                    }
654                                }
655                            }
656                        }
657                    }
658                    Err(why) => {
659                        return Err(NokhwaError::GetPropertyError {
660                            property: "Frame rate".to_string(),
661                            error: why.to_string(),
662                        })
663                    }
664                }
665                res_map.insert(res, compatible_fps);
666            }
667            Ok(res_map)
668        }
669
670        fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
671            match self.lock_device()?.enum_formats() {
672                Ok(formats) => {
673                    let mut frame_format_vec = vec![];
674                    for format in formats {
675                        match fourcc_to_frameformat(format.fourcc) {
676                            Some(ff) => frame_format_vec.push(ff),
677                            None => continue,
678                        }
679                    }
680                    frame_format_vec.sort();
681                    frame_format_vec.dedup();
682                    Ok(frame_format_vec)
683                }
684                Err(why) => Err(NokhwaError::GetPropertyError {
685                    property: "FrameFormat".to_string(),
686                    error: why.to_string(),
687                }),
688            }
689        }
690
691        fn resolution(&self) -> Resolution {
692            self.camera_format.resolution()
693        }
694
695        fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
696            let mut new_fmt = self.camera_format;
697            new_fmt.set_resolution(new_res);
698            self.set_camera_format(new_fmt)
699        }
700
701        fn frame_rate(&self) -> u32 {
702            self.camera_format.frame_rate()
703        }
704
705        fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
706            let mut new_fmt = self.camera_format;
707            new_fmt.set_frame_rate(new_fps);
708            self.set_camera_format(new_fmt)
709        }
710
711        fn frame_format(&self) -> FrameFormat {
712            self.camera_format.format()
713        }
714
715        fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
716            let mut new_fmt = self.camera_format;
717            new_fmt.set_format(fourcc);
718            self.set_camera_format(new_fmt)
719        }
720
721        fn camera_control(
722            &self,
723            control: KnownCameraControl,
724        ) -> Result<CameraControl, NokhwaError> {
725            let controls = self.camera_controls()?;
726            for supported_control in controls {
727                if supported_control.control() == control {
728                    return Ok(supported_control);
729                }
730            }
731            Err(NokhwaError::GetPropertyError {
732                property: control.to_string(),
733                error: "not found/not supported".to_string(),
734            })
735        }
736
737        #[allow(clippy::cast_possible_wrap)]
738        fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
739            let device = self.lock_device()?;
740            device
741                .query_controls()
742                .map_err(|why| NokhwaError::GetPropertyError {
743                    property: "V4L2 Controls".to_string(),
744                    error: why.to_string(),
745                })?
746                .into_iter()
747                .map(|desc| {
748                    let id_as_kcc = id_to_known_camera_control(desc.id);
749                    let ctrl_current = device.control(desc.id)?.value;
750
751                    let ctrl_value_desc = match (desc.typ, ctrl_current) {
752                        (
753                            Type::Integer
754                            | Type::Integer64
755                            | Type::Menu
756                            | Type::U8
757                            | Type::U16
758                            | Type::U32
759                            | Type::IntegerMenu,
760                            Value::Integer(current),
761                        ) => ControlValueDescription::IntegerRange {
762                            min: desc.minimum as i64,
763                            max: desc.maximum,
764                            value: current,
765                            step: desc.step as i64,
766                            default: desc.default,
767                        },
768                        (Type::Boolean, Value::Boolean(current)) => {
769                            ControlValueDescription::Boolean {
770                                value: current,
771                                default: desc.default != 0,
772                            }
773                        }
774
775                        (Type::String, Value::String(current)) => ControlValueDescription::String {
776                            value: current,
777                            default: None,
778                        },
779                        _ => {
780                            return Err(io::Error::new(
781                                ErrorKind::Unsupported,
782                                "what is this?????? todo: support ig",
783                            ))
784                        }
785                    };
786
787                    let is_readonly = desc
788                        .flags
789                        .intersects(Flags::READ_ONLY)
790                        .then_some(KnownCameraControlFlag::ReadOnly);
791                    let is_writeonly = desc
792                        .flags
793                        .intersects(Flags::WRITE_ONLY)
794                        .then_some(KnownCameraControlFlag::WriteOnly);
795                    let is_disabled = desc
796                        .flags
797                        .intersects(Flags::DISABLED)
798                        .then_some(KnownCameraControlFlag::Disabled);
799                    let is_volatile = desc
800                        .flags
801                        .intersects(Flags::VOLATILE)
802                        .then_some(KnownCameraControlFlag::Volatile);
803                    let is_inactive = desc
804                        .flags
805                        .intersects(Flags::INACTIVE)
806                        .then_some(KnownCameraControlFlag::Disabled);
807                    let flags_vec = vec![
808                        is_inactive,
809                        is_readonly,
810                        is_volatile,
811                        is_disabled,
812                        is_writeonly,
813                    ]
814                    .into_iter()
815                    .filter(Option::is_some)
816                    .collect::<Option<Vec<KnownCameraControlFlag>>>()
817                    .unwrap_or_default();
818
819                    Ok(CameraControl::new(
820                        id_as_kcc,
821                        desc.name,
822                        ctrl_value_desc,
823                        flags_vec,
824                        !desc.flags.intersects(Flags::INACTIVE),
825                    ))
826                })
827                .filter(Result::is_ok)
828                .collect::<Result<Vec<CameraControl>, io::Error>>()
829                .map_err(|x| NokhwaError::GetPropertyError {
830                    property: "www".to_string(),
831                    error: x.to_string(),
832                })
833        }
834
835        fn set_camera_control(
836            &mut self,
837            id: KnownCameraControl,
838            value: ControlValueSetter,
839        ) -> Result<(), NokhwaError> {
840            let conv_value = match value.clone() {
841                ControlValueSetter::None => Value::None,
842                ControlValueSetter::Integer(i) => Value::Integer(i),
843                ControlValueSetter::Boolean(b) => Value::Boolean(b),
844                ControlValueSetter::String(s) => Value::String(s),
845                ControlValueSetter::Bytes(b) => Value::CompoundU8(b),
846                v => {
847                    return Err(NokhwaError::SetPropertyError {
848                        property: id.to_string(),
849                        value: v.to_string(),
850                        error: "not supported".to_string(),
851                    })
852                }
853            };
854            self.lock_device()?
855                .set_control(Control {
856                    id: known_camera_control_to_id(id),
857                    value: conv_value,
858                })
859                .map_err(|why| NokhwaError::SetPropertyError {
860                    property: id.to_string(),
861                    value: format!("{:?}", value),
862                    error: why.to_string(),
863                })?;
864            // verify
865
866            let control = self.camera_control(id)?;
867            if control.value() != value {
868                return Err(NokhwaError::SetPropertyError {
869                    property: id.to_string(),
870                    value: format!("{:?}", value),
871                    error: "Rejected".to_string(),
872                });
873            }
874            Ok(())
875        }
876
877        fn open_stream(&mut self) -> Result<(), NokhwaError> {
878            // Disable mut warning, since mut is only required when not using arena buffers
879            #[allow(unused_mut)]
880            let mut stream =
881                match MmapStream::new(&*self.lock_device()?, v4l::buffer::Type::VideoCapture) {
882                    Ok(s) => s,
883                    Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
884                };
885
886            // Explicitly start now, or won't work with the RPi. As a consequence, buffers will only be used as required.
887            // WARNING: This will cause drop of half of the frames
888            #[cfg(feature = "no-arena-buffer")]
889            match stream.start() {
890                Ok(s) => s,
891                Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
892            }
893            self.stream_handle = Some(stream);
894            Ok(())
895        }
896
897        fn is_stream_open(&self) -> bool {
898            self.stream_handle.is_some()
899        }
900
901        fn frame(&mut self) -> Result<Buffer, NokhwaError> {
902            let cam_fmt = self.camera_format;
903            let raw_frame = self.frame_raw()?;
904            Ok(Buffer::new(
905                cam_fmt.resolution(),
906                &raw_frame,
907                cam_fmt.format(),
908            ))
909        }
910
911        fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
912            match &mut self.stream_handle {
913                Some(sh) => match sh.next() {
914                    Ok((data, _)) => Ok(Cow::Borrowed(data)),
915                    Err(why) => Err(NokhwaError::ReadFrameError(why.to_string())),
916                },
917                None => Err(NokhwaError::ReadFrameError(
918                    "Stream Not Started".to_string(),
919                )),
920            }
921        }
922
923        fn stop_stream(&mut self) -> Result<(), NokhwaError> {
924            if self.stream_handle.is_some() {
925                self.stream_handle = None;
926            }
927            Ok(())
928        }
929    }
930
931    fn fourcc_to_frameformat(fourcc: FourCC) -> Option<FrameFormat> {
932        match fourcc.str().ok()? {
933            "YUYV" => Some(FrameFormat::YUYV),
934            "MJPG" => Some(FrameFormat::MJPEG),
935            "GRAY" => Some(FrameFormat::GRAY),
936            "RGB3" => Some(FrameFormat::RAWRGB),
937            "BGR3" => Some(FrameFormat::RAWBGR),
938            "NV12" => Some(FrameFormat::NV12),
939            _ => None,
940        }
941    }
942
943    fn frameformat_to_fourcc(fourcc: FrameFormat) -> FourCC {
944        match fourcc {
945            FrameFormat::MJPEG => FourCC::new(b"MJPG"),
946            FrameFormat::YUYV => FourCC::new(b"YUYV"),
947            FrameFormat::GRAY => FourCC::new(b"GRAY"),
948            FrameFormat::RAWRGB => FourCC::new(b"RGB3"),
949            FrameFormat::RAWBGR => FourCC::new(b"BGR3"),
950            FrameFormat::NV12 => FourCC::new(b"NV12"),
951        }
952    }
953}
954
955#[cfg(not(target_os = "linux"))]
956mod internal {
957    use std::borrow::Cow;
958    use std::collections::HashMap;
959    use std::marker::PhantomData;
960    use videocall_nokhwa_core::buffer::Buffer;
961    use videocall_nokhwa_core::error::NokhwaError;
962    use videocall_nokhwa_core::traits::CaptureBackendTrait;
963    use videocall_nokhwa_core::types::{
964        ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter,
965        FrameFormat, KnownCameraControl, RequestedFormat, Resolution,
966    };
967
968    /// Attempts to convert a [`KnownCameraControl`] into a V4L2 Control ID.
969    /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
970    #[allow(clippy::cast_possible_truncation)]
971    pub fn known_camera_control_to_id(_ctrl: KnownCameraControl) -> u32 {
972        0
973    }
974
975    /// Attempts to convert a [`u32`] V4L2 Control ID into a [`KnownCameraControl`]
976    /// If the associated control is not found, this will return `None` (`ColorEnable`, `Roll`)
977    #[allow(clippy::cast_lossless)]
978    pub fn id_to_known_camera_control(id: u32) -> KnownCameraControl {
979        KnownCameraControl::Other(id as u128)
980    }
981
982    /// The backend struct that interfaces with V4L2.
983    /// To see what this does, please see [`CaptureBackendTrait`].
984    /// # Quirks
985    /// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution), [`set_frame_rate()`](CaptureBackendTrait::set_frame_rate), or [`set_frame_format()`](CaptureBackendTrait::set_frame_format) each internally calls [`set_camera_format()`](CaptureBackendTrait::set_camera_format).
986    pub struct V4LCaptureDevice<'a> {
987        __holder: PhantomData<&'a str>,
988    }
989
990    #[allow(unused_variables)]
991    impl<'a> V4LCaptureDevice<'a> {
992        /// Creates a new capture device using the `V4L2` backend. Indexes are gives to devices by the OS, and usually numbered by order of discovery.
993        /// # Errors
994        /// This function will error if the camera is currently busy or if `V4L2` can't read device information.
995        #[allow(clippy::too_many_lines)]
996        pub fn new(index: &CameraIndex, cam_fmt: RequestedFormat) -> Result<Self, NokhwaError> {
997            Err(NokhwaError::NotImplementedError(
998                "V4L2 only on Linux".to_string(),
999            ))
1000        }
1001
1002        /// Create a new `V4L2` Camera with desired settings. This may or may not work.
1003        /// # Errors
1004        /// This function will error if the camera is currently busy or if `V4L2` can't read device information.
1005        #[deprecated(since = "0.10.0", note = "please use `new` instead.")]
1006        pub fn new_with(
1007            index: CameraIndex,
1008            width: u32,
1009            height: u32,
1010            fps: u32,
1011            fourcc: FrameFormat,
1012        ) -> Result<Self, NokhwaError> {
1013            Err(NokhwaError::NotImplementedError(
1014                "V4L2 only on Linux".to_string(),
1015            ))
1016        }
1017
1018        /// Force refreshes the inner [`CameraFormat`] state.
1019        /// # Errors
1020        /// If the internal representation in the driver is invalid, this will error.
1021        pub fn force_refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
1022            Err(NokhwaError::NotImplementedError(
1023                "V4L2 only on Linux".to_string(),
1024            ))
1025        }
1026    }
1027
1028    #[allow(unused_variables)]
1029    impl<'a> CaptureBackendTrait for V4LCaptureDevice<'a> {
1030        fn backend(&self) -> ApiBackend {
1031            ApiBackend::Video4Linux
1032        }
1033
1034        fn camera_info(&self) -> &CameraInfo {
1035            todo!()
1036        }
1037
1038        fn refresh_camera_format(&mut self) -> Result<(), NokhwaError> {
1039            todo!()
1040        }
1041
1042        fn camera_format(&self) -> CameraFormat {
1043            todo!()
1044        }
1045
1046        fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
1047            todo!()
1048        }
1049
1050        fn compatible_list_by_resolution(
1051            &mut self,
1052            fourcc: FrameFormat,
1053        ) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
1054            todo!()
1055        }
1056
1057        fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
1058            todo!()
1059        }
1060
1061        fn resolution(&self) -> Resolution {
1062            todo!()
1063        }
1064
1065        fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
1066            todo!()
1067        }
1068
1069        fn frame_rate(&self) -> u32 {
1070            todo!()
1071        }
1072
1073        fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
1074            todo!()
1075        }
1076
1077        fn frame_format(&self) -> FrameFormat {
1078            todo!()
1079        }
1080
1081        fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
1082            todo!()
1083        }
1084
1085        fn camera_control(
1086            &self,
1087            control: KnownCameraControl,
1088        ) -> Result<CameraControl, NokhwaError> {
1089            todo!()
1090        }
1091
1092        fn camera_controls(&self) -> Result<Vec<CameraControl>, NokhwaError> {
1093            todo!()
1094        }
1095
1096        fn set_camera_control(
1097            &mut self,
1098            id: KnownCameraControl,
1099            value: ControlValueSetter,
1100        ) -> Result<(), NokhwaError> {
1101            todo!()
1102        }
1103
1104        fn open_stream(&mut self) -> Result<(), NokhwaError> {
1105            todo!()
1106        }
1107
1108        fn is_stream_open(&self) -> bool {
1109            todo!()
1110        }
1111
1112        fn frame(&mut self) -> Result<Buffer, NokhwaError> {
1113            todo!()
1114        }
1115
1116        fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
1117            todo!()
1118        }
1119
1120        fn stop_stream(&mut self) -> Result<(), NokhwaError> {
1121            todo!()
1122        }
1123    }
1124}
1125
1126pub use internal::*;