vidi/io/
subdev.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* Copyright (C) 2025 DorotaC
 * SPDX-License-Identifier: LGPL-2.1-or-later OR MPL-2.0
 */

/*! Utilities for handling the V4L2 subdevice abstraction */

use std::fs::File;
use std::io;
use std::os::fd::AsRawFd;
use std::path::Path;
use thiserror::Error;
use v4l2_subdev::{self, MediaBusFmt, Zeroed, v4l2_colorspace, v4l2_quantization, v4l2_field, v4l2_xfer_func, MbusFrameFormatFlags};

#[derive(Error, Debug)]
pub enum Error {
    #[error("I/O error")]
    Io(io::Error),
    #[error("This is not a Media subdevice")]
    NotASubdevice,
}

impl From<io::Error> for Error {
    fn from(value: io::Error) -> Self {
        Self::Io(value)
    }
}

pub struct Io;

impl Io {
    pub fn open(&mut self, path: &Path) -> Result<Subdevice, Error> {
        let f = File::open(path)?;
        Ok(Subdevice(f))
    }
}

pub struct Subdevice(File);

impl Subdevice {
    pub fn enum_mbus_code(&self, _io: &mut Io, pad: u32) -> io::Result<Vec<SubdeviceMbusCode>> {
        let mut out = Vec::new();
        loop {
            let mut v =  v4l2_subdev::sys::v4l2_subdev_mbus_code_enum {
                index: out.len() as u32,
                pad,
                ..Zeroed::zeroed()
            };
            let ret = unsafe {
                v4l2_subdev::enum_mbus_code(self.0.as_raw_fd(), &mut v)
            };
            
            match ret {
                Err(nix::Error::EINVAL) => {
                    return if out.is_empty() {
                        Err(nix::Error::EINVAL.into())
                    } else {
                        Ok(out)
                    }
                },
                Ok(_) => {},
                Err(other) => { return Err(other.into()) },
            };
            out.push(SubdeviceMbusCode { code: v.code.into() , flags: v.flags });
        }
    }
    
    pub fn enum_frame_size(&self, _io: &mut Io, pad: u32, code: MediaBusFmt) -> io::Result<Vec<SubdeviceFrameSize>> {
        let mut out = Vec::new();
        loop {
            let mut v =  v4l2_subdev::sys::v4l2_subdev_frame_size_enum {
                index: out.len() as u32,
                pad,
                code: code.into(),
                ..Zeroed::zeroed()
            };
            let ret = unsafe {
                v4l2_subdev::enum_frame_size(self.0.as_raw_fd(), &mut v)
            };
            
            match ret {
                Err(nix::Error::EINVAL) => {
                    return if out.is_empty() {
                        Err(nix::Error::EINVAL.into())
                    } else {
                        Ok(out)
                    }
                },
                Ok(_) => {},
                Err(other) => { return Err(other.into()) },
            };
            out.push(SubdeviceFrameSize {
                min_width: v.min_width,
                max_width: v.max_width,
                min_height: v.min_height,
                max_height: v.max_height,
            });
        }
    }
    
    pub fn get_format(&self, _io:&mut Io, pad: u32) -> io::Result<MbusFrameFormat> {
        let mut v =  v4l2_subdev::sys::v4l2_subdev_format {
            pad,
            ..Zeroed::zeroed()
        };
        unsafe {
            v4l2_subdev::g_fmt(self.0.as_raw_fd(), &mut v)
        }?;
        Ok(v.format.into())
    }

    pub fn set_format(&mut self, _io:&mut Io, pad: u32, format: MbusFrameFormat) -> io::Result<()> {
        let mut v = v4l2_subdev::sys::v4l2_subdev_format {
            pad,
            format: format.into(),
            ..Zeroed::zeroed()
        };
        unsafe {
            v4l2_subdev::s_fmt(self.0.as_raw_fd(), &mut v)
        }?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct SubdeviceMbusCode {
    pub code: MediaBusFmt,
    pub flags: u32,
}

#[derive(Debug)]
pub struct SubdeviceFrameSize {
    pub min_width: u32,
    pub max_width: u32,
    pub min_height: u32,
    pub max_height: u32,
}

#[derive(Debug, Clone)]
pub struct MbusFrameFormat {
    /// Image width in pixels.
    pub width: u32,
    /// Image height in pixels. If field is one of V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM or V4L2_FIELD_ALTERNATE then height refers to the number of lines in the field, otherwise it refers to the number of lines in the frame (which is twice the field height for interlaced formats).
    pub height: u32,
    pub code: MediaBusFmt,
    // FIXME: Bindgen doesn't implement TryInto for rustified enums.
    // The following enums are unused, so skip the structs.
    // https://github.com/rust-lang/rust-bindgen/pull/2908
    /// Field order. Zero for metadata mbus codes.
    pub field: u32,//v4l2_field,
    /// Image colorspace. If the driver cannot handle the requested conversion, it will return another supported colorspace.
    pub colorspace: u32,// v4l2_colorspace,
    // FIXME: not sure what the union discriminant is
    /// Quantization range. If the driver cannot handle the requested conversion, it will return another supported quantization.
    pub quantization: u16,// v4l2_quantization,
    /// Transfer function. If the driver cannot handle the requested conversion, it will return another supported transfer function.
    pub xfer_func: u16,// v4l2_xfer_func,
    pub flags: MbusFrameFormatFlags,
}

impl From<v4l2_subdev::sys::v4l2_mbus_framefmt> for MbusFrameFormat {
    fn from(value: v4l2_subdev::sys::v4l2_mbus_framefmt) -> Self {
        Self {
            width: value.width,
            height: value.height,
            code: value.code.into(),
            field: value.field,
            colorspace: value.colorspace,
            quantization: value.quantization,
            xfer_func: value.xfer_func,
            flags: MbusFrameFormatFlags::from_bits_retain(value.flags),
        }
    }
}

impl From<MbusFrameFormat> for  v4l2_subdev::sys::v4l2_mbus_framefmt {
    fn from(value: MbusFrameFormat) -> Self {
        Self {
            width: value.width,
            height: value.height,
            code: value.code.into(),
            field: value.field,
            colorspace: value.colorspace,
            quantization: value.quantization,
            xfer_func: value.xfer_func,
            flags: value.flags.bits(),
            // TODO: I don't understand what goes in ycbcr_enc/hsv_enc. Fill in the union. Have fun with the unsafe block.
            ..Self::zeroed()
        }
    }
}