linuxvideo/
lib.rs

1//! Linux video device library.
2//!
3//! This library provides a (hopefully) convenient and high-level wrapper around the V4L2 ioctls,
4//! and allows accessing video devices (capture cards, webcams, etc.) on Linux systems.
5//!
6//! The main entry points to the library are [`list`], for enumerating all V4L2 devices (and opening
7//! one of them by name), and [`Device::open`], for opening a specific path.
8
9#[macro_use]
10mod macros;
11mod buf_type;
12pub mod controls;
13pub mod format;
14mod pixel_format;
15mod raw;
16mod shared;
17pub mod stream;
18pub mod uvc;
19
20use pixel_format::PixelFormat;
21use std::{
22    fmt,
23    fs::{self, File, OpenOptions},
24    io::{self, Read, Write},
25    mem::{self, MaybeUninit},
26    os::unix::prelude::*,
27    path::{Path, PathBuf},
28};
29
30use controls::{ControlDesc, ControlIter, TextMenuIter};
31use format::{Format, FormatDescIter, FrameIntervals, FrameSizes, MetaFormat, PixFormat};
32use raw::controls::Cid;
33use shared::{CaptureParamFlags, Memory, StreamParamCaps};
34use stream::{ReadStream, WriteStream, DEFAULT_BUFFER_COUNT};
35
36pub use buf_type::*;
37pub use shared::{
38    AnalogStd, CapabilityFlags, Fract, InputCapabilities, InputStatus, InputType,
39    OutputCapabilities, OutputType,
40};
41
42const DEVICE_PREFIXES: &[&str] = &[
43    "video",
44    "vbi",
45    "radio",
46    "swradio",
47    "v4l-touch",
48    "v4l-subdev",
49];
50
51/// Returns an iterator over all connected V4L2 devices.
52///
53/// This will enumerate all devices in `/dev` that match one of the V4L2 device file patterns:
54///
55/// - **`/dev/video*`**: webcams, capture cards, video output devices, codecs, video overlays, etc.
56/// - **`/dev/vbi*`**: devices for capturing and outputting raw **V**ertical **B**lanking **I**nterval data.
57/// - **`/dev/radio*`**: AM and FM radio transmitters and receivers.
58/// - **`/dev/swradio*`**: Software-defined radios.
59/// - **`/dev/v4l-touch*`**: Touch screens and other touch devices.
60/// - **`/dev/v4l-subdev*`**: A sub-device exported as part of a bigger device.
61pub fn list() -> io::Result<impl Iterator<Item = io::Result<Device>>> {
62    Ok(fs::read_dir("/dev")?.flat_map(|file| {
63        let file = match file {
64            Ok(file) => file,
65            Err(e) => return Some(Err(e.into())),
66        };
67
68        let name = file.file_name();
69        if !DEVICE_PREFIXES
70            .iter()
71            .any(|p| name.as_bytes().starts_with(p.as_bytes()))
72        {
73            // Doesn't match any V4L2 device patterns.
74            return None;
75        }
76
77        // Sanity check that it's a char device.
78        match file.file_type() {
79            Ok(ty) => {
80                if !ty.is_char_device() {
81                    log::warn!(
82                        "'{}' is not a character device: {:?}",
83                        name.to_string_lossy(),
84                        ty,
85                    );
86                    return None;
87                }
88            }
89            Err(e) => return Some(Err(e.into())),
90        }
91
92        Some(Device::open(&file.path()))
93    }))
94}
95
96/// A V4L2 device.
97#[derive(Debug)]
98pub struct Device {
99    file: File,
100    available_capabilities: CapabilityFlags,
101}
102
103impl Device {
104    /// Opens a V4L2 device file from the given path.
105    ///
106    /// If the path does not refer to a V4L2 device node, an error will be returned.
107    pub fn open<A: AsRef<Path>>(path: A) -> io::Result<Self> {
108        Self::open_impl(path.as_ref())
109    }
110
111    fn open_impl(path: &Path) -> io::Result<Self> {
112        let file = OpenOptions::new().read(true).write(true).open(path)?;
113        let mut this = Self {
114            file,
115            available_capabilities: CapabilityFlags::empty(),
116        };
117        let caps = this.capabilities()?;
118        this.available_capabilities = caps.device_capabilities();
119
120        Ok(this)
121    }
122
123    fn fd(&self) -> RawFd {
124        self.file.as_raw_fd()
125    }
126
127    /// Returns the path to the V4L2 device.
128    ///
129    /// This will invoke `readlink(2)` on `/proc/self/fd/N` to find the path, so it will not work
130    /// on FreeBSD or other Unix-likes that don't expose a procfs with this functionality.
131    pub fn path(&self) -> io::Result<PathBuf> {
132        fs::read_link(format!("/proc/self/fd/{}", self.fd()))
133    }
134
135    /// Queries the device's [`Capabilities`].
136    pub fn capabilities(&self) -> io::Result<Capabilities> {
137        unsafe {
138            let mut caps = MaybeUninit::uninit();
139            let res = raw::VIDIOC_QUERYCAP.ioctl(self, caps.as_mut_ptr())?;
140            assert_eq!(res, 0);
141            Ok(Capabilities(caps.assume_init()))
142        }
143    }
144
145    pub fn supported_buf_types(&self) -> BufTypes {
146        BufTypes::from_capabilities(self.available_capabilities)
147    }
148
149    /// Enumerates the supported pixel formats of a stream.
150    ///
151    /// `buf_type` must be one of `VIDEO_CAPTURE`, `VIDEO_CAPTURE_MPLANE`, `VIDEO_OUTPUT`,
152    /// `VIDEO_OUTPUT_MPLANE`, `VIDEO_OVERLAY`, `SDR_CAPTURE`, `SDR_OUTPUT`, `META_CAPTURE`, or
153    /// `META_OUTPUT`.
154    pub fn formats(&self, buf_type: BufType) -> FormatDescIter<'_> {
155        FormatDescIter::new(self, buf_type)
156    }
157
158    /// Returns the supported frame sizes for a given pixel format.
159    ///
160    /// # Errors
161    ///
162    /// An `ENOTTY` error will be returned if `pixel_format` specifies a format that does not
163    /// describe video data (for example, [`PixelFormat::UVC`] or other metadata formats).
164    pub fn frame_sizes(&self, pixel_format: PixelFormat) -> io::Result<FrameSizes> {
165        FrameSizes::new(self, pixel_format)
166    }
167
168    pub fn frame_intervals(
169        &self,
170        pixel_format: PixelFormat,
171        width: u32,
172        height: u32,
173    ) -> io::Result<FrameIntervals> {
174        FrameIntervals::new(self, pixel_format, width, height)
175    }
176
177    /// Returns an iterator over the [`Input`]s of the device.
178    ///
179    /// # Errors
180    ///
181    /// May return `ENOTTY` if the device is not an input device.
182    pub fn inputs(&self) -> InputIter<'_> {
183        InputIter {
184            device: self,
185            next_index: 0,
186            finished: false,
187        }
188    }
189
190    /// Returns an iterator over the [`Output`]s of the device.
191    ///
192    /// # Errors
193    ///
194    /// May return `ENOTTY` if the device is not an output device.
195    pub fn outputs(&self) -> OutputIter<'_> {
196        OutputIter {
197            device: self,
198            next_index: 0,
199            finished: false,
200        }
201    }
202
203    pub fn controls(&self) -> ControlIter<'_> {
204        ControlIter::new(self)
205    }
206
207    /// Returns an iterator over the valid values of a menu control.
208    pub fn enumerate_menu(&self, ctrl: &ControlDesc) -> TextMenuIter<'_> {
209        TextMenuIter::new(self, ctrl)
210    }
211
212    pub fn read_control_raw(&self, cid: Cid) -> io::Result<i32> {
213        let mut control = raw::controls::Control { id: cid, value: 0 };
214
215        unsafe {
216            raw::VIDIOC_G_CTRL.ioctl(self, &mut control)?;
217        }
218
219        Ok(control.value)
220    }
221
222    pub fn write_control_raw(&mut self, cid: Cid, value: i32) -> io::Result<()> {
223        let mut control = raw::controls::Control { id: cid, value };
224        unsafe {
225            raw::VIDIOC_S_CTRL.ioctl(self, &mut control)?;
226        }
227        Ok(())
228    }
229
230    /// Reads the stream format in use by `buf_type`.
231    ///
232    /// The returned [`Format`] variant will match `buf_type`.
233    ///
234    /// If no format is set, this returns `EINVAL`.
235    ///
236    /// # Panics
237    ///
238    /// This will panic if `buf_type` corresponds to a buffer type that hasn't yet been implemented
239    /// in [`Format`].
240    pub fn format(&self, buf_type: BufType) -> io::Result<Format> {
241        unsafe {
242            let mut format = raw::Format {
243                type_: buf_type,
244                ..mem::zeroed()
245            };
246            raw::VIDIOC_G_FMT.ioctl(self, &mut format)?;
247            let fmt = Format::from_raw(format)
248                .unwrap_or_else(|| todo!("unsupported buffer type {:?}", buf_type));
249            Ok(fmt)
250        }
251    }
252
253    /// Negotiates a stream's format.
254    ///
255    /// The driver will adjust the values in `format` to the closest values it supports (the variant
256    /// will not be changed). The modified `Format` is returned.
257    fn set_format_raw(&mut self, format: Format) -> io::Result<Format> {
258        unsafe {
259            let mut raw_format: raw::Format = mem::zeroed();
260            match format {
261                Format::VideoCapture(f) => {
262                    raw_format.type_ = BufType::VIDEO_CAPTURE;
263                    raw_format.fmt.pix = f.to_raw();
264                }
265                Format::VideoOutput(f) => {
266                    raw_format.type_ = BufType::VIDEO_OUTPUT;
267                    raw_format.fmt.pix = f.to_raw();
268                }
269                Format::VideoCaptureMplane(f) => {
270                    raw_format.type_ = BufType::VIDEO_CAPTURE_MPLANE;
271                    raw_format.fmt.pix_mp = f.to_raw();
272                }
273                Format::VideoOutputMplane(f) => {
274                    raw_format.type_ = BufType::VIDEO_OUTPUT_MPLANE;
275                    raw_format.fmt.pix_mp = f.to_raw();
276                }
277                Format::VideoOverlay(f) => {
278                    raw_format.type_ = BufType::VIDEO_OVERLAY;
279                    raw_format.fmt.win = f.to_raw();
280                }
281                Format::MetaCapture(f) => {
282                    raw_format.type_ = BufType::META_CAPTURE;
283                    raw_format.fmt.meta = f.to_raw();
284                }
285                Format::MetaOutput(f) => {
286                    raw_format.type_ = BufType::META_OUTPUT;
287                    raw_format.fmt.meta = f.to_raw();
288                }
289            }
290            raw::VIDIOC_S_FMT.ioctl(self, &mut raw_format)?;
291            let fmt = Format::from_raw(raw_format).unwrap();
292            Ok(fmt)
293        }
294    }
295
296    /// Puts the device into video capture mode and negotiates a pixel format.
297    ///
298    /// # Format Negotiation
299    ///
300    /// Generally, the driver is allowed to change most properties of the [`PixFormat`], including
301    /// the requested dimensions and the [`PixelFormat`], if the provided value is not supported.
302    /// However, it is not required to do so and may instead return `EINVAL` if the parameters are
303    /// not supported. One example where this happens is with `v4l2loopback`.
304    pub fn video_capture(mut self, format: PixFormat) -> io::Result<VideoCaptureDevice> {
305        let format = match self.set_format_raw(Format::VideoCapture(format))? {
306            Format::VideoCapture(fmt) => fmt,
307            _ => unreachable!(),
308        };
309
310        Ok(VideoCaptureDevice {
311            file: self.file,
312            format,
313        })
314    }
315
316    /// Puts the device into video output mode and negotiates a pixel format.
317    ///
318    /// # Format Negotiation
319    ///
320    /// Generally, the driver is allowed to change most properties of the [`PixFormat`], including
321    /// the requested dimensions and the [`PixelFormat`], if the provided value is not supported.
322    /// However, it is not required to do so and may instead return `EINVAL` if the parameters are
323    /// not supported. One example where this happens is with `v4l2loopback`.
324    pub fn video_output(mut self, format: PixFormat) -> io::Result<VideoOutputDevice> {
325        let format = match self.set_format_raw(Format::VideoOutput(format))? {
326            Format::VideoOutput(fmt) => fmt,
327            _ => unreachable!(),
328        };
329
330        Ok(VideoOutputDevice {
331            file: self.file,
332            format,
333        })
334    }
335
336    /// Puts the device into metadata capture mode and negotiates a data format.
337    pub fn meta_capture(mut self, format: MetaFormat) -> io::Result<MetaCaptureDevice> {
338        let format = match self.set_format_raw(Format::MetaCapture(format))? {
339            Format::MetaCapture(fmt) => fmt,
340            _ => unreachable!(),
341        };
342
343        Ok(MetaCaptureDevice {
344            file: self.file,
345            format,
346        })
347    }
348}
349
350impl AsRawFd for Device {
351    #[inline]
352    fn as_raw_fd(&self) -> RawFd {
353        self.file.as_raw_fd()
354    }
355}
356
357impl AsFd for Device {
358    #[inline]
359    fn as_fd(&self) -> BorrowedFd<'_> {
360        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
361    }
362}
363
364/// A video device configured for video capture.
365///
366/// Returned by [`Device::video_capture`].
367pub struct VideoCaptureDevice {
368    file: File,
369    format: PixFormat,
370}
371
372impl VideoCaptureDevice {
373    /// Returns the pixel format the driver chose for capturing.
374    ///
375    /// This may (and usually will) differ from the format passed to [`Device::video_capture`].
376    pub fn format(&self) -> &PixFormat {
377        &self.format
378    }
379
380    /// Requests a change to the frame interval.
381    ///
382    /// Returns the actual frame interval chosen by the driver.
383    ///
384    /// Supported frame intervals depend on the pixel format and video resolution and can be
385    /// enumerated with [`Device::frame_intervals`].
386    pub fn set_frame_interval(&self, interval: Fract) -> io::Result<Fract> {
387        unsafe {
388            let mut parm = raw::StreamParm {
389                type_: BufType::VIDEO_CAPTURE,
390                union: raw::StreamParmUnion {
391                    capture: raw::CaptureParm {
392                        timeperframe: interval,
393                        capability: StreamParamCaps::TIMEPERFRAME,
394                        capturemode: CaptureParamFlags::empty(),
395                        extendedmode: 0,
396                        readbuffers: 0,
397                        reserved: [0; 4],
398                    },
399                },
400            };
401            raw::VIDIOC_S_PARM.ioctl(self, &mut parm)?;
402            Ok(parm.union.capture.timeperframe)
403        }
404    }
405
406    /// Initializes streaming I/O mode.
407    pub fn into_stream(self) -> io::Result<ReadStream> {
408        Ok(ReadStream::new(
409            self.file,
410            BufType::VIDEO_CAPTURE,
411            Memory::MMAP,
412            DEFAULT_BUFFER_COUNT,
413        )?)
414    }
415}
416
417/// Performs a direct `read()` from the video device.
418///
419/// This will only succeed if the device advertises the `READWRITE` capability, otherwise an
420/// error will be returned and you have to use the streaming API instead.
421impl Read for VideoCaptureDevice {
422    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
423        self.file.read(buf)
424    }
425}
426
427impl AsRawFd for VideoCaptureDevice {
428    #[inline]
429    fn as_raw_fd(&self) -> RawFd {
430        self.file.as_raw_fd()
431    }
432}
433
434impl AsFd for VideoCaptureDevice {
435    #[inline]
436    fn as_fd(&self) -> BorrowedFd<'_> {
437        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
438    }
439}
440
441/// A video device configured for video output.
442///
443/// Returned by [`Device::video_output`].
444pub struct VideoOutputDevice {
445    file: File,
446    format: PixFormat,
447}
448
449impl VideoOutputDevice {
450    /// Returns the video format chosen by the driver.
451    pub fn format(&self) -> &PixFormat {
452        &self.format
453    }
454
455    /// Initializes streaming I/O mode.
456    pub fn into_stream(self) -> io::Result<WriteStream> {
457        Ok(WriteStream::new(
458            self.file,
459            BufType::VIDEO_CAPTURE,
460            Memory::MMAP,
461            DEFAULT_BUFFER_COUNT,
462        )?)
463    }
464}
465
466/// Performs a direct `write()` on the video device file, writing a video frame to it.
467///
468/// This will only succeed if the device advertises the `READWRITE` capability, otherwise an
469/// error will be returned and you have to use the streaming API instead.
470///
471/// Note that some applications, like guvcview, do not support the read/write methods, so using this
472/// on a v4l2loopback device will not work with such applications.
473impl Write for VideoOutputDevice {
474    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
475        self.file.write(buf)
476    }
477
478    fn flush(&mut self) -> io::Result<()> {
479        self.file.flush()
480    }
481}
482
483impl AsRawFd for VideoOutputDevice {
484    #[inline]
485    fn as_raw_fd(&self) -> RawFd {
486        self.file.as_raw_fd()
487    }
488}
489
490impl AsFd for VideoOutputDevice {
491    #[inline]
492    fn as_fd(&self) -> BorrowedFd<'_> {
493        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
494    }
495}
496
497/// A device configured for metadata capture.
498///
499/// Returned by [`Device::meta_capture`].
500pub struct MetaCaptureDevice {
501    file: File,
502    format: MetaFormat,
503}
504
505impl MetaCaptureDevice {
506    /// Returns the metadata format the driver chose.
507    pub fn format(&self) -> &MetaFormat {
508        &self.format
509    }
510
511    /// Initializes streaming I/O mode.
512    pub fn into_stream(self) -> io::Result<ReadStream> {
513        Ok(ReadStream::new(
514            self.file,
515            BufType::META_CAPTURE,
516            Memory::MMAP,
517            DEFAULT_BUFFER_COUNT,
518        )?)
519    }
520}
521
522/// Performs a direct `read()` from the video device.
523///
524/// This will only succeed if the device advertises the `READWRITE` capability, otherwise an
525/// error will be returned and you have to use the streaming API instead.
526impl Read for MetaCaptureDevice {
527    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
528        self.file.read(buf)
529    }
530}
531
532impl AsRawFd for MetaCaptureDevice {
533    #[inline]
534    fn as_raw_fd(&self) -> RawFd {
535        self.file.as_raw_fd()
536    }
537}
538
539impl AsFd for MetaCaptureDevice {
540    #[inline]
541    fn as_fd(&self) -> BorrowedFd<'_> {
542        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
543    }
544}
545
546/// Stores generic device information.
547///
548/// Returned by [`Device::capabilities`].
549pub struct Capabilities(raw::Capabilities);
550
551impl Capabilities {
552    /// Returns the identifier of the V4L2 driver that provides this device.
553    ///
554    /// Examples:
555    /// - `uvcvideo`
556    /// - `v4l2 loopback`
557    pub fn driver(&self) -> &str {
558        byte_array_to_str(&self.0.driver)
559    }
560
561    /// Returns the card or device name.
562    ///
563    /// For `v4l2loopback` devices, the reported card name can be configured by passing the
564    /// `card_label` parameter when loading the module (or via `modprobe.d`).
565    pub fn card(&self) -> &str {
566        byte_array_to_str(&self.0.card)
567    }
568
569    /// Returns a description of where on the system the device is attached.
570    ///
571    /// Examples:
572    /// - `usb-0000:0a:00.3-2.1`
573    /// - `platform:v4l2loopback-002`
574    pub fn bus_info(&self) -> &str {
575        byte_array_to_str(&self.0.bus_info)
576    }
577
578    /// Returns all capabilities the underlying hardware device exposes.
579    ///
580    /// Some capabilities might be inaccessible through the opened device node and require opening a
581    /// different one.
582    pub fn all_capabilities(&self) -> CapabilityFlags {
583        self.0.capabilities
584    }
585
586    /// Returns the capabilities available through the currently opened device node.
587    pub fn device_capabilities(&self) -> CapabilityFlags {
588        if self.0.capabilities.contains(CapabilityFlags::DEVICE_CAPS) {
589            self.0.device_caps
590        } else {
591            self.0.capabilities
592        }
593    }
594}
595
596impl fmt::Debug for Capabilities {
597    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598        f.debug_struct("Capabilities")
599            .field("driver", &self.driver())
600            .field("card", &self.card())
601            .field("bus_info", &self.bus_info())
602            .field("capabilities", &self.0.capabilities)
603            .field("device_caps", &self.0.device_caps)
604            .finish()
605    }
606}
607
608/// Iterator over the [`Output`]s of a [`Device`].
609pub struct OutputIter<'a> {
610    device: &'a Device,
611    next_index: u32,
612    finished: bool,
613}
614
615impl Iterator for OutputIter<'_> {
616    type Item = io::Result<Output>;
617
618    fn next(&mut self) -> Option<Self::Item> {
619        if self.finished {
620            return None;
621        }
622
623        unsafe {
624            let mut raw = raw::Output {
625                index: self.next_index,
626                ..mem::zeroed()
627            };
628            match raw::VIDIOC_ENUMOUTPUT.ioctl(self.device, &mut raw) {
629                Ok(_) => {}
630                Err(e) => {
631                    self.finished = true;
632                    if e.raw_os_error() == Some(libc::EINVAL as _) {
633                        // `EINVAL` indicates the end of the list.
634                        return None;
635                    } else {
636                        return Some(Err(e));
637                    }
638                }
639            }
640
641            self.next_index += 1;
642
643            Some(Ok(Output(raw)))
644        }
645    }
646}
647
648/// Iterator over the [`Input`]s of a [`Device`].
649pub struct InputIter<'a> {
650    device: &'a Device,
651    next_index: u32,
652    finished: bool,
653}
654
655impl Iterator for InputIter<'_> {
656    type Item = io::Result<Input>;
657
658    fn next(&mut self) -> Option<Self::Item> {
659        if self.finished {
660            return None;
661        }
662
663        unsafe {
664            let mut raw = raw::Input {
665                index: self.next_index,
666                ..mem::zeroed()
667            };
668            match raw::VIDIOC_ENUMINPUT.ioctl(self.device, &mut raw) {
669                Ok(_) => {}
670                Err(e) => {
671                    self.finished = true;
672                    if e.raw_os_error() == Some(libc::EINVAL as _) {
673                        // `EINVAL` indicates the end of the list.
674                        return None;
675                    } else {
676                        return Some(Err(e));
677                    }
678                }
679            }
680
681            self.next_index += 1;
682
683            Some(Ok(Input(raw)))
684        }
685    }
686}
687
688/// Information about a device output.
689pub struct Output(raw::Output);
690
691impl Output {
692    /// Returns the output's name.
693    ///
694    /// Examples:
695    /// - `loopback in`
696    pub fn name(&self) -> &str {
697        byte_array_to_str(&self.0.name)
698    }
699
700    /// Returns what kind of device this output is.
701    #[inline]
702    pub fn output_type(&self) -> OutputType {
703        self.0.type_
704    }
705
706    /// Returns the set of selectable audio sources when this output is active.
707    ///
708    /// This may return 0 even if the device supports audio inputs to indicate that the application
709    /// cannot choose an audio input.
710    #[inline]
711    pub fn audioset(&self) -> u32 {
712        self.0.audioset
713    }
714
715    /// Returns the modulator index if this input is of type [`OutputType::MODULATOR`].
716    ///
717    /// For non-modulator outputs, this value should be ignored.
718    #[inline]
719    pub fn modulator(&self) -> u32 {
720        self.0.modulator
721    }
722
723    /// Returns the set of supported analog video standards.
724    #[inline]
725    pub fn std(&self) -> AnalogStd {
726        self.0.std
727    }
728
729    /// Returns the capability flags of this output.
730    #[inline]
731    pub fn capabilities(&self) -> OutputCapabilities {
732        self.0.capabilities
733    }
734}
735
736impl fmt::Debug for Output {
737    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738        f.debug_struct("Output")
739            .field("index", &self.0.index)
740            .field("name", &self.name())
741            .field("output_type", &self.output_type())
742            .field("audioset", &self.0.audioset)
743            .field("modulator", &self.0.modulator)
744            .field("std", &self.0.std)
745            .field("capabilities", &self.0.capabilities)
746            .finish()
747    }
748}
749
750/// Information about a device input.
751pub struct Input(raw::Input);
752
753impl Input {
754    /// Returns the name of the input.
755    ///
756    /// Examples:
757    /// - `Camera 1`
758    /// - `loopback`
759    pub fn name(&self) -> &str {
760        byte_array_to_str(&self.0.name)
761    }
762
763    /// Returns what kind of device this input is.
764    #[inline]
765    pub fn input_type(&self) -> InputType {
766        self.0.type_
767    }
768
769    /// Returns the set of selectable audio sources when this input is active.
770    ///
771    /// This may return 0 even if the device supports audio inputs to indicate that the application
772    /// cannot choose an audio input.
773    #[inline]
774    pub fn audioset(&self) -> u32 {
775        self.0.audioset
776    }
777
778    /// Returns the tuner index if this input is of type [`InputType::TUNER`].
779    ///
780    /// For non-tuner inputs, this value should be ignored.
781    #[inline]
782    pub fn tuner(&self) -> u32 {
783        self.0.tuner
784    }
785
786    /// Returns the set of supported analog video standards for this input.
787    #[inline]
788    pub fn std(&self) -> AnalogStd {
789        self.0.std
790    }
791
792    /// Returns the current status of the input.
793    ///
794    /// Note that the input needs to be selected as the active input for most fields in this value
795    /// to be valid.
796    #[inline]
797    pub fn status(&self) -> InputStatus {
798        self.0.status
799    }
800
801    /// Returns the capability flags of this input.
802    #[inline]
803    pub fn capabilities(&self) -> InputCapabilities {
804        self.0.capabilities
805    }
806}
807
808impl fmt::Debug for Input {
809    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
810        f.debug_struct("Input")
811            .field("index", &self.0.index)
812            .field("name", &self.name())
813            .field("input_type", &self.input_type())
814            .field("audioset", &self.0.audioset)
815            .field("tuner", &self.0.tuner)
816            .field("std", &self.0.std)
817            .field("status", &self.0.status)
818            .field("capabilities", &self.0.capabilities)
819            .finish()
820    }
821}
822
823/// Turns a zero-padded byte array containing UTF-8 or ASCII data into a `&str`.
824fn byte_array_to_str(bytes: &[u8]) -> &str {
825    let len = bytes
826        .iter()
827        .position(|b| *b == 0)
828        .expect("missing NUL terminator");
829    std::str::from_utf8(&bytes[..len]).unwrap()
830}