raymanfx / libv4l-rs

Video4Linux2 bindings for Rust
MIT License
155 stars 65 forks source link

Device serial and model info #109

Open onkoe opened 2 months ago

onkoe commented 2 months ago

I need to tell the difference between devices using their media device info. I wrote the following, but it'd be great to see something similar in the library:

use std::{
    ffi::{c_char, CStr},
    os::fd::AsRawFd,
    path::Path,
};

use nix::ioctl_readwrite;

/// A struct that contains information about some Linux media device.
///
/// See: https://docs.kernel.org/userspace-api/media/mediactl/media-ioc-device-info.html
#[repr(C)]
pub(super) struct MediaDeviceInfo {
    driver: [c_char; 16],
    model: [c_char; 32],
    serial: [c_char; 40],
    bus_info: [c_char; 32],
    media_version: u32,
    hw_revision: u32,
    driver_version: u32,
    reserved: [u32; 31],
}

const MEDIA_IOC_DEVICE_INFO: u8 = 0x00;
const IOCTL_MEDIA_COMMAND: u8 = b'|';

// call `media_ioc_device_info` to execute the `ioctl`
ioctl_readwrite!(
    media_ioc_device_info,
    IOCTL_MEDIA_COMMAND,
    MEDIA_IOC_DEVICE_INFO,
    MediaDeviceInfo
);

impl MediaDeviceInfo {
    /// Attempts to get information about the media device at the given path.
    pub fn get(path: &Path) -> Result<Self, std::io::Error> {
        // create an uninitialized MediaDeviceInfo
        //
        // SAFETY: This is fine since the kernel will write to this zeroed memory.
        let mut info = unsafe { std::mem::zeroed::<MediaDeviceInfo>() };

        // grab the file descriptor
        let file = std::fs::File::open(path)?;
        let fd = file.as_raw_fd();

        // perform the ioctl
        //
        // SAFETY: The kernel will either write to this or fail to do so.
        //
        // If it does fail, we return using the question mark operator.
        unsafe {
            media_ioc_device_info(fd, &mut info)?;
        }

        Ok(info)
    }

    pub fn model(&self) -> String {
        unsafe {
            CStr::from_ptr(self.model.as_ptr())
                .to_str()
                .unwrap_or_else(|_| {
                    tracing::error!("`ioctl` to get capture device model contained invalid UTF-8");
                    "Model was not valid UTF-8"
                })
                .to_string()
        }
    }

    pub fn serial(&self) -> String {
        unsafe {
            CStr::from_ptr(self.serial.as_ptr())
                .to_str()
                .unwrap_or_else(|_| {
                    tracing::error!("`ioctl` to get capture device serial contained invalid UTF-8");
                    "Serial was not valid UTF-8"
                })
                .to_string()
        }
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::*;

    #[test]
    fn test_media_device_info() {
        let info = MediaDeviceInfo::get(&PathBuf::from("/dev/media0")).unwrap();
        assert_eq!(String::from("C922 Pro Stream Webcam"), info.model());
    }
}

https://docs.kernel.org/userspace-api/media/mediactl/media-ioc-device-info.html#c.MC.MEDIA_IOC_DEVICE_INFO

MarijnS95 commented 2 months ago

@onkoe I've implemented most of the mediactl API 2.5 years ago at https://github.com/marijns95/libv4l-rs/compare/media, would you mind checking that out and seeing if it works for you?

I'll rebase it, button it up and see if I can create a PR out of it.

onkoe commented 2 months ago

It appears that this branch would work great! Thanks for your effort!