1#[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
51pub 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 return None;
75 }
76
77 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#[derive(Debug)]
98pub struct Device {
99 file: File,
100 available_capabilities: CapabilityFlags,
101}
102
103impl Device {
104 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 pub fn path(&self) -> io::Result<PathBuf> {
132 fs::read_link(format!("/proc/self/fd/{}", self.fd()))
133 }
134
135 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 pub fn formats(&self, buf_type: BufType) -> FormatDescIter<'_> {
155 FormatDescIter::new(self, buf_type)
156 }
157
158 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 pub fn inputs(&self) -> InputIter<'_> {
183 InputIter {
184 device: self,
185 next_index: 0,
186 finished: false,
187 }
188 }
189
190 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 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 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 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 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 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 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
364pub struct VideoCaptureDevice {
368 file: File,
369 format: PixFormat,
370}
371
372impl VideoCaptureDevice {
373 pub fn format(&self) -> &PixFormat {
377 &self.format
378 }
379
380 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 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
417impl 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
441pub struct VideoOutputDevice {
445 file: File,
446 format: PixFormat,
447}
448
449impl VideoOutputDevice {
450 pub fn format(&self) -> &PixFormat {
452 &self.format
453 }
454
455 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
466impl 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
497pub struct MetaCaptureDevice {
501 file: File,
502 format: MetaFormat,
503}
504
505impl MetaCaptureDevice {
506 pub fn format(&self) -> &MetaFormat {
508 &self.format
509 }
510
511 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
522impl 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
546pub struct Capabilities(raw::Capabilities);
550
551impl Capabilities {
552 pub fn driver(&self) -> &str {
558 byte_array_to_str(&self.0.driver)
559 }
560
561 pub fn card(&self) -> &str {
566 byte_array_to_str(&self.0.card)
567 }
568
569 pub fn bus_info(&self) -> &str {
575 byte_array_to_str(&self.0.bus_info)
576 }
577
578 pub fn all_capabilities(&self) -> CapabilityFlags {
583 self.0.capabilities
584 }
585
586 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
608pub 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 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
648pub 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 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
688pub struct Output(raw::Output);
690
691impl Output {
692 pub fn name(&self) -> &str {
697 byte_array_to_str(&self.0.name)
698 }
699
700 #[inline]
702 pub fn output_type(&self) -> OutputType {
703 self.0.type_
704 }
705
706 #[inline]
711 pub fn audioset(&self) -> u32 {
712 self.0.audioset
713 }
714
715 #[inline]
719 pub fn modulator(&self) -> u32 {
720 self.0.modulator
721 }
722
723 #[inline]
725 pub fn std(&self) -> AnalogStd {
726 self.0.std
727 }
728
729 #[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
750pub struct Input(raw::Input);
752
753impl Input {
754 pub fn name(&self) -> &str {
760 byte_array_to_str(&self.0.name)
761 }
762
763 #[inline]
765 pub fn input_type(&self) -> InputType {
766 self.0.type_
767 }
768
769 #[inline]
774 pub fn audioset(&self) -> u32 {
775 self.0.audioset
776 }
777
778 #[inline]
782 pub fn tuner(&self) -> u32 {
783 self.0.tuner
784 }
785
786 #[inline]
788 pub fn std(&self) -> AnalogStd {
789 self.0.std
790 }
791
792 #[inline]
797 pub fn status(&self) -> InputStatus {
798 self.0.status
799 }
800
801 #[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
823fn 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}