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> {
131 fs::read_link(format!("/proc/self/fd/{}", self.fd()))
132 }
133
134 pub fn capabilities(&self) -> io::Result<Capabilities> {
136 unsafe {
137 let mut caps = MaybeUninit::uninit();
138 let res = raw::VIDIOC_QUERYCAP.ioctl(self, caps.as_mut_ptr())?;
139 assert_eq!(res, 0);
140 Ok(Capabilities(caps.assume_init()))
141 }
142 }
143
144 pub fn supported_buf_types(&self) -> BufTypes {
145 BufTypes::from_capabilities(self.available_capabilities)
146 }
147
148 pub fn formats(&self, buf_type: BufType) -> FormatDescIter<'_> {
154 FormatDescIter::new(self, buf_type)
155 }
156
157 pub fn frame_sizes(&self, pixel_format: PixelFormat) -> io::Result<FrameSizes> {
164 FrameSizes::new(self, pixel_format)
165 }
166
167 pub fn frame_intervals(
168 &self,
169 pixel_format: PixelFormat,
170 width: u32,
171 height: u32,
172 ) -> io::Result<FrameIntervals> {
173 FrameIntervals::new(self, pixel_format, width, height)
174 }
175
176 pub fn inputs(&self) -> InputIter<'_> {
182 InputIter {
183 device: self,
184 next_index: 0,
185 finished: false,
186 }
187 }
188
189 pub fn outputs(&self) -> OutputIter<'_> {
195 OutputIter {
196 device: self,
197 next_index: 0,
198 finished: false,
199 }
200 }
201
202 pub fn controls(&self) -> ControlIter<'_> {
203 ControlIter::new(self)
204 }
205
206 pub fn enumerate_menu(&self, ctrl: &ControlDesc) -> TextMenuIter<'_> {
208 TextMenuIter::new(self, ctrl)
209 }
210
211 pub fn read_control_raw(&self, cid: Cid) -> io::Result<i32> {
212 let mut control = raw::controls::Control { id: cid, value: 0 };
213
214 unsafe {
215 raw::VIDIOC_G_CTRL.ioctl(self, &mut control)?;
216 }
217
218 Ok(control.value)
219 }
220
221 pub fn write_control_raw(&mut self, cid: Cid, value: i32) -> io::Result<()> {
222 let mut control = raw::controls::Control { id: cid, value };
223 unsafe {
224 raw::VIDIOC_S_CTRL.ioctl(self, &mut control)?;
225 }
226 Ok(())
227 }
228
229 pub fn format(&self, buf_type: BufType) -> io::Result<Format> {
240 unsafe {
241 let mut format = raw::Format {
242 type_: buf_type,
243 ..mem::zeroed()
244 };
245 raw::VIDIOC_G_FMT.ioctl(self, &mut format)?;
246 let fmt = Format::from_raw(format)
247 .unwrap_or_else(|| todo!("unsupported buffer type {:?}", buf_type));
248 Ok(fmt)
249 }
250 }
251
252 fn set_format_raw(&mut self, format: Format) -> io::Result<Format> {
257 unsafe {
258 let mut raw_format: raw::Format = mem::zeroed();
259 match format {
260 Format::VideoCapture(f) => {
261 raw_format.type_ = BufType::VIDEO_CAPTURE;
262 raw_format.fmt.pix = f.to_raw();
263 }
264 Format::VideoOutput(f) => {
265 raw_format.type_ = BufType::VIDEO_OUTPUT;
266 raw_format.fmt.pix = f.to_raw();
267 }
268 Format::VideoCaptureMplane(f) => {
269 raw_format.type_ = BufType::VIDEO_CAPTURE_MPLANE;
270 raw_format.fmt.pix_mp = f.to_raw();
271 }
272 Format::VideoOutputMplane(f) => {
273 raw_format.type_ = BufType::VIDEO_OUTPUT_MPLANE;
274 raw_format.fmt.pix_mp = f.to_raw();
275 }
276 Format::VideoOverlay(f) => {
277 raw_format.type_ = BufType::VIDEO_OVERLAY;
278 raw_format.fmt.win = f.to_raw();
279 }
280 Format::MetaCapture(f) => {
281 raw_format.type_ = BufType::META_CAPTURE;
282 raw_format.fmt.meta = f.to_raw();
283 }
284 Format::MetaOutput(f) => {
285 raw_format.type_ = BufType::META_OUTPUT;
286 raw_format.fmt.meta = f.to_raw();
287 }
288 }
289 raw::VIDIOC_S_FMT.ioctl(self, &mut raw_format)?;
290 let fmt = Format::from_raw(raw_format).unwrap();
291 Ok(fmt)
292 }
293 }
294
295 pub fn video_capture(mut self, format: PixFormat) -> io::Result<VideoCaptureDevice> {
304 let format = match self.set_format_raw(Format::VideoCapture(format))? {
305 Format::VideoCapture(fmt) => fmt,
306 _ => unreachable!(),
307 };
308
309 Ok(VideoCaptureDevice {
310 file: self.file,
311 format,
312 })
313 }
314
315 pub fn video_output(mut self, format: PixFormat) -> io::Result<VideoOutputDevice> {
324 let format = match self.set_format_raw(Format::VideoOutput(format))? {
325 Format::VideoOutput(fmt) => fmt,
326 _ => unreachable!(),
327 };
328
329 Ok(VideoOutputDevice {
330 file: self.file,
331 format,
332 })
333 }
334
335 pub fn meta_capture(mut self, format: MetaFormat) -> io::Result<MetaCaptureDevice> {
337 let format = match self.set_format_raw(Format::MetaCapture(format))? {
338 Format::MetaCapture(fmt) => fmt,
339 _ => unreachable!(),
340 };
341
342 Ok(MetaCaptureDevice {
343 file: self.file,
344 format,
345 })
346 }
347}
348
349impl AsRawFd for Device {
350 #[inline]
351 fn as_raw_fd(&self) -> RawFd {
352 self.file.as_raw_fd()
353 }
354}
355
356impl AsFd for Device {
357 #[inline]
358 fn as_fd(&self) -> BorrowedFd<'_> {
359 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
360 }
361}
362
363pub struct VideoCaptureDevice {
367 file: File,
368 format: PixFormat,
369}
370
371impl VideoCaptureDevice {
372 pub fn format(&self) -> &PixFormat {
376 &self.format
377 }
378
379 pub fn set_frame_interval(&self, interval: Fract) -> io::Result<Fract> {
386 unsafe {
387 let mut parm = raw::StreamParm {
388 type_: BufType::VIDEO_CAPTURE,
389 union: raw::StreamParmUnion {
390 capture: raw::CaptureParm {
391 timeperframe: interval,
392 capability: StreamParamCaps::TIMEPERFRAME,
393 capturemode: CaptureParamFlags::empty(),
394 extendedmode: 0,
395 readbuffers: 0,
396 reserved: [0; 4],
397 },
398 },
399 };
400 raw::VIDIOC_S_PARM.ioctl(self, &mut parm)?;
401 Ok(parm.union.capture.timeperframe)
402 }
403 }
404
405 pub fn into_stream(self) -> io::Result<ReadStream> {
407 Ok(ReadStream::new(
408 self.file,
409 BufType::VIDEO_CAPTURE,
410 Memory::MMAP,
411 DEFAULT_BUFFER_COUNT,
412 )?)
413 }
414}
415
416impl Read for VideoCaptureDevice {
421 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
422 self.file.read(buf)
423 }
424}
425
426impl AsRawFd for VideoCaptureDevice {
427 #[inline]
428 fn as_raw_fd(&self) -> RawFd {
429 self.file.as_raw_fd()
430 }
431}
432
433impl AsFd for VideoCaptureDevice {
434 #[inline]
435 fn as_fd(&self) -> BorrowedFd<'_> {
436 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
437 }
438}
439
440pub struct VideoOutputDevice {
444 file: File,
445 format: PixFormat,
446}
447
448impl VideoOutputDevice {
449 pub fn format(&self) -> &PixFormat {
451 &self.format
452 }
453
454 pub fn into_stream(self) -> io::Result<WriteStream> {
456 Ok(WriteStream::new(
457 self.file,
458 BufType::VIDEO_CAPTURE,
459 Memory::MMAP,
460 DEFAULT_BUFFER_COUNT,
461 )?)
462 }
463}
464
465impl Write for VideoOutputDevice {
473 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
474 self.file.write(buf)
475 }
476
477 fn flush(&mut self) -> io::Result<()> {
478 self.file.flush()
479 }
480}
481
482impl AsRawFd for VideoOutputDevice {
483 #[inline]
484 fn as_raw_fd(&self) -> RawFd {
485 self.file.as_raw_fd()
486 }
487}
488
489impl AsFd for VideoOutputDevice {
490 #[inline]
491 fn as_fd(&self) -> BorrowedFd<'_> {
492 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
493 }
494}
495
496pub struct MetaCaptureDevice {
500 file: File,
501 format: MetaFormat,
502}
503
504impl MetaCaptureDevice {
505 pub fn format(&self) -> &MetaFormat {
507 &self.format
508 }
509
510 pub fn into_stream(self) -> io::Result<ReadStream> {
512 Ok(ReadStream::new(
513 self.file,
514 BufType::META_CAPTURE,
515 Memory::MMAP,
516 DEFAULT_BUFFER_COUNT,
517 )?)
518 }
519}
520
521impl Read for MetaCaptureDevice {
526 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
527 self.file.read(buf)
528 }
529}
530
531impl AsRawFd for MetaCaptureDevice {
532 #[inline]
533 fn as_raw_fd(&self) -> RawFd {
534 self.file.as_raw_fd()
535 }
536}
537
538impl AsFd for MetaCaptureDevice {
539 #[inline]
540 fn as_fd(&self) -> BorrowedFd<'_> {
541 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
542 }
543}
544
545pub struct Capabilities(raw::Capabilities);
549
550impl Capabilities {
551 pub fn driver(&self) -> &str {
557 byte_array_to_str(&self.0.driver)
558 }
559
560 pub fn card(&self) -> &str {
565 byte_array_to_str(&self.0.card)
566 }
567
568 pub fn bus_info(&self) -> &str {
574 byte_array_to_str(&self.0.bus_info)
575 }
576
577 pub fn all_capabilities(&self) -> CapabilityFlags {
582 self.0.capabilities
583 }
584
585 pub fn device_capabilities(&self) -> CapabilityFlags {
587 if self.0.capabilities.contains(CapabilityFlags::DEVICE_CAPS) {
588 self.0.device_caps
589 } else {
590 self.0.capabilities
591 }
592 }
593}
594
595impl fmt::Debug for Capabilities {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 f.debug_struct("Capabilities")
598 .field("driver", &self.driver())
599 .field("card", &self.card())
600 .field("bus_info", &self.bus_info())
601 .field("capabilities", &self.0.capabilities)
602 .field("device_caps", &self.0.device_caps)
603 .finish()
604 }
605}
606
607pub struct OutputIter<'a> {
609 device: &'a Device,
610 next_index: u32,
611 finished: bool,
612}
613
614impl Iterator for OutputIter<'_> {
615 type Item = io::Result<Output>;
616
617 fn next(&mut self) -> Option<Self::Item> {
618 if self.finished {
619 return None;
620 }
621
622 unsafe {
623 let mut raw = raw::Output {
624 index: self.next_index,
625 ..mem::zeroed()
626 };
627 match raw::VIDIOC_ENUMOUTPUT.ioctl(self.device, &mut raw) {
628 Ok(_) => {}
629 Err(e) => {
630 self.finished = true;
631 if e.raw_os_error() == Some(libc::EINVAL as _) {
632 return None;
634 } else {
635 return Some(Err(e));
636 }
637 }
638 }
639
640 self.next_index += 1;
641
642 Some(Ok(Output(raw)))
643 }
644 }
645}
646
647pub struct InputIter<'a> {
649 device: &'a Device,
650 next_index: u32,
651 finished: bool,
652}
653
654impl Iterator for InputIter<'_> {
655 type Item = io::Result<Input>;
656
657 fn next(&mut self) -> Option<Self::Item> {
658 if self.finished {
659 return None;
660 }
661
662 unsafe {
663 let mut raw = raw::Input {
664 index: self.next_index,
665 ..mem::zeroed()
666 };
667 match raw::VIDIOC_ENUMINPUT.ioctl(self.device, &mut raw) {
668 Ok(_) => {}
669 Err(e) => {
670 self.finished = true;
671 if e.raw_os_error() == Some(libc::EINVAL as _) {
672 return None;
674 } else {
675 return Some(Err(e));
676 }
677 }
678 }
679
680 self.next_index += 1;
681
682 Some(Ok(Input(raw)))
683 }
684 }
685}
686
687pub struct Output(raw::Output);
689
690impl Output {
691 pub fn name(&self) -> &str {
696 byte_array_to_str(&self.0.name)
697 }
698
699 #[inline]
701 pub fn output_type(&self) -> OutputType {
702 self.0.type_
703 }
704
705 #[inline]
710 pub fn audioset(&self) -> u32 {
711 self.0.audioset
712 }
713
714 #[inline]
718 pub fn modulator(&self) -> u32 {
719 self.0.modulator
720 }
721
722 #[inline]
724 pub fn std(&self) -> AnalogStd {
725 self.0.std
726 }
727
728 #[inline]
730 pub fn capabilities(&self) -> OutputCapabilities {
731 self.0.capabilities
732 }
733}
734
735impl fmt::Debug for Output {
736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
737 f.debug_struct("Output")
738 .field("index", &self.0.index)
739 .field("name", &self.name())
740 .field("output_type", &self.output_type())
741 .field("audioset", &self.0.audioset)
742 .field("modulator", &self.0.modulator)
743 .field("std", &self.0.std)
744 .field("capabilities", &self.0.capabilities)
745 .finish()
746 }
747}
748
749pub struct Input(raw::Input);
751
752impl Input {
753 pub fn name(&self) -> &str {
759 byte_array_to_str(&self.0.name)
760 }
761
762 #[inline]
764 pub fn input_type(&self) -> InputType {
765 self.0.type_
766 }
767
768 #[inline]
773 pub fn audioset(&self) -> u32 {
774 self.0.audioset
775 }
776
777 #[inline]
781 pub fn tuner(&self) -> u32 {
782 self.0.tuner
783 }
784
785 #[inline]
787 pub fn std(&self) -> AnalogStd {
788 self.0.std
789 }
790
791 #[inline]
796 pub fn status(&self) -> InputStatus {
797 self.0.status
798 }
799
800 #[inline]
802 pub fn capabilities(&self) -> InputCapabilities {
803 self.0.capabilities
804 }
805}
806
807impl fmt::Debug for Input {
808 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
809 f.debug_struct("Input")
810 .field("index", &self.0.index)
811 .field("name", &self.name())
812 .field("input_type", &self.input_type())
813 .field("audioset", &self.0.audioset)
814 .field("tuner", &self.0.tuner)
815 .field("std", &self.0.std)
816 .field("status", &self.0.status)
817 .field("capabilities", &self.0.capabilities)
818 .finish()
819 }
820}
821
822fn byte_array_to_str(bytes: &[u8]) -> &str {
824 let len = bytes
825 .iter()
826 .position(|b| *b == 0)
827 .expect("missing NUL terminator");
828 std::str::from_utf8(&bytes[..len]).unwrap()
829}