1#[cfg(target_os = "linux")]
2mod v4lstream;
3
4#[cfg(not(target_os = "linux"))]
6mod empty_impl {
7 use cu_sensor_payloads::CuImage;
8 use cu29::prelude::*;
9
10 pub struct V4l {}
11
12 impl Freezable for V4l {}
13
14 impl CuSrcTask for V4l {
15 type Resources<'r> = ();
16 type Output<'m> = output_msg!(CuImage<Vec<u8>>);
17
18 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
19 where
20 Self: Sized,
21 {
22 Ok(Self {})
23 }
24
25 fn process(
26 &mut self,
27 _clock: &RobotClock,
28 _new_msg: &mut Self::Output<'_>,
29 ) -> CuResult<()> {
30 Ok(())
31 }
32 }
33}
34
35#[cfg(not(target_os = "linux"))]
36pub use empty_impl::V4l;
37
38#[cfg(target_os = "linux")]
39pub use linux_impl::V4l;
40
41#[cfg(target_os = "linux")]
42mod linux_impl {
43 use std::time::Duration;
44 use v4l::video::Capture;
45
46 use crate::v4lstream::CuV4LStream;
47 use cu_sensor_payloads::{CuImage, CuImageBufferFormat};
48 use cu29::prelude::*;
49
50 use nix::time::{ClockId, clock_gettime};
51
52 pub use v4l::buffer::Type;
53 pub use v4l::framesize::FrameSizeEnum;
54 pub use v4l::io::traits::{CaptureStream, Stream};
55 pub use v4l::prelude::*;
56 pub use v4l::video::capture::Parameters;
57 pub use v4l::{Format, FourCC, Timestamp};
58
59 pub struct V4l {
61 stream: CuV4LStream,
62 settled_format: CuImageBufferFormat,
63 v4l_clock_time_offset_ns: i64,
64 }
65
66 impl Freezable for V4l {}
67
68 fn cutime_from_v4ltime(offset_ns: i64, v4l_time: Timestamp) -> CuTime {
69 let duration: Duration = v4l_time.into();
70 ((duration.as_nanos() as i64 + offset_ns) as u64).into()
71 }
72
73 impl CuSrcTask for V4l {
74 type Resources<'r> = ();
75 type Output<'m> = output_msg!(CuImage<Vec<u8>>);
76
77 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
78 where
79 Self: Sized,
80 {
81 let mut v4l_device = 0usize;
83 let mut req_width: Option<u32> = None;
84 let mut req_height: Option<u32> = None;
85 let mut req_fps: Option<u32> = None;
86 let mut req_fourcc: Option<String> = None;
87 let mut req_buffers: u32 = 4;
88 let mut req_timeout: Duration = Duration::from_millis(500); if let Some(config) = _config {
91 if let Some(device) = config.get::<u32>("device") {
92 v4l_device = device as usize;
93 }
94 if let Some(width) = config.get::<u32>("width") {
95 req_width = Some(width);
96 }
97 if let Some(height) = config.get::<u32>("height") {
98 req_height = Some(height);
99 }
100 if let Some(fps) = config.get::<u32>("fps") {
101 req_fps = Some(fps);
102 }
103 if let Some(fourcc) = config.get::<String>("fourcc") {
104 req_fourcc = Some(fourcc);
105 }
106 if let Some(buffers) = config.get::<u32>("buffers") {
107 req_buffers = buffers;
108 }
109 if let Some(timeout) = config.get::<u32>("timeout_ms") {
110 req_timeout = Duration::from_millis(timeout as u64);
111 }
112 }
113 let dev = Device::new(v4l_device)
114 .map_err(|e| CuError::new_with_cause("Failed to open camera", e))?;
115
116 let formats = dev
118 .enum_formats()
119 .map_err(|e| CuError::new_with_cause("Failed to enum formats", e))?;
120
121 if formats.is_empty() {
122 return Err("The V4l device did not provide any video format.".into());
123 }
124
125 let fourcc: FourCC = if let Some(fourcc) = req_fourcc {
127 if fourcc.len() != 4 {
128 return Err("Invalid fourcc provided".into());
129 }
130 FourCC::new(fourcc.as_bytes()[0..4].try_into().unwrap())
131 } else {
132 debug!("No fourcc provided, just use the first one we can find.");
133 formats.first().unwrap().fourcc
134 };
135 debug!("V4L: Using fourcc: {}", fourcc.to_string());
136 let actual_fmt = if let Some(format) = formats.iter().find(|f| f.fourcc == fourcc) {
137 let resolutions = dev
139 .enum_framesizes(format.fourcc)
140 .map_err(|e| CuError::new_with_cause("Failed to enum frame sizes", e))?;
141 let (width, height) =
142 if let (Some(req_width), Some(req_height)) = (req_width, req_height) {
143 let mut frame_size: (u32, u32) = (0, 0);
144 for frame in resolutions.iter() {
145 let FrameSizeEnum::Discrete(size) = &frame.size else {
146 todo!()
147 };
148 if size.width == req_width && size.height == req_height {
149 frame_size = (size.width, size.height);
150 break;
151 }
152 }
153 frame_size
154 } else {
155 let fs = resolutions.first().unwrap();
157 let FrameSizeEnum::Discrete(size) = &fs.size else {
158 todo!()
159 };
160 (size.width, size.height)
161 };
162
163 let req_fmt = Format::new(width, height, fourcc);
165 let actual_fmt = dev
166 .set_format(&req_fmt)
167 .map_err(|e| CuError::new_with_cause("Failed to set format", e))?;
168
169 if let Some(fps) = req_fps {
170 debug!("V4L: Set fps to {}", fps);
171 let new_params = Parameters::with_fps(fps);
172 dev.set_params(&new_params)
173 .map_err(|e| CuError::new_with_cause("Failed to set params", e))?;
174 }
175 debug!(
176 "V4L: Negotiated resolution: {}x{}",
177 actual_fmt.width, actual_fmt.height
178 );
179 actual_fmt
180 } else {
181 return Err(format!(
182 "The V4l device {v4l_device} does not provide a format with the FourCC {fourcc}."
183 )
184 .into());
185 };
186 debug!(
187 "V4L: Init stream: device {} with {} buffers of size {} bytes",
188 v4l_device, req_buffers, actual_fmt.size
189 );
190
191 let mut stream = CuV4LStream::with_buffers(
192 &dev,
193 Type::VideoCapture,
194 req_buffers,
195 CuHostMemoryPool::new(
196 format!("V4L Host Pool {v4l_device}").as_str(),
197 req_buffers as usize + 1,
198 || vec![0; actual_fmt.size as usize],
199 )
200 .map_err(|e| {
201 CuError::new_with_cause(
202 "Could not create host memory pool backing the V4lStream",
203 e,
204 )
205 })?,
206 )
207 .map_err(|e| CuError::new_with_cause("Could not create the V4lStream", e))?;
208 let req_timeout_ms = req_timeout.as_millis() as u64;
209 debug!("V4L: Set timeout to {} ms", req_timeout_ms);
210 stream.set_timeout(req_timeout);
211
212 let cuformat = CuImageBufferFormat {
213 width: actual_fmt.width,
214 height: actual_fmt.height,
215 stride: actual_fmt.stride,
216 pixel_format: actual_fmt.fourcc.repr,
217 };
218
219 Ok(Self {
220 stream,
221 settled_format: cuformat,
222 v4l_clock_time_offset_ns: 0, })
224 }
225
226 fn start(&mut self, robot_clock: &RobotClock) -> CuResult<()> {
227 let rb_ns = robot_clock.now().as_nanos();
228 clock_gettime(ClockId::CLOCK_MONOTONIC)
229 .map(|ts| {
230 self.v4l_clock_time_offset_ns =
231 ts.tv_sec() * 1_000_000_000 + ts.tv_nsec() - rb_ns as i64
232 })
233 .map_err(|e| CuError::new_with_cause("Failed to get the current time", e))?;
234
235 self.stream
236 .start()
237 .map_err(|e| CuError::new_with_cause("could not start stream", e))
238 }
239
240 fn process(&mut self, _clock: &RobotClock, new_msg: &mut Self::Output<'_>) -> CuResult<()> {
241 let (handle, meta) = self
242 .stream
243 .next()
244 .map_err(|e| CuError::new_with_cause("could not get next frame from stream", e))?;
245 if meta.bytesused != 0 {
246 let cutime = cutime_from_v4ltime(self.v4l_clock_time_offset_ns, meta.timestamp);
247 let image = CuImage::new(self.settled_format, handle.clone());
248 new_msg.set_payload(image);
249 new_msg.tov = Tov::Time(cutime);
250 } else {
251 debug!("Empty frame received");
252 }
253 Ok(())
254 }
255
256 fn stop(&mut self, _clock: &RobotClock) -> CuResult<()> {
257 self.stream
258 .stop()
259 .map_err(|e| CuError::new_with_cause("could not stop stream", e))
260 }
261 }
262
263 #[cfg(test)]
264 mod tests {
265 use super::*;
266 use rerun::RecordingStreamBuilder;
267 use rerun::components::ImageBuffer;
268 use rerun::datatypes::{Blob, ImageFormat};
269 use rerun::{Image, PixelFormat};
270 use std::thread;
271
272 use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
273
274 const IMG_WIDTH: usize = 3840;
275 const IMG_HEIGHT: usize = 2160;
276
277 #[derive(Debug)]
278 struct NullLog {}
279 impl WriteStream<CuLogEntry> for NullLog {
280 fn log(&mut self, _obj: &CuLogEntry) -> CuResult<()> {
281 Ok(())
282 }
283 fn flush(&mut self) -> CuResult<()> {
284 Ok(())
285 }
286 }
287
288 #[test]
289 #[ignore]
290 fn emulate_copper_backend() {
291 let clock = RobotClock::new();
292
293 let term_logger = TermLogger::new(
294 LevelFilter::Debug,
295 Config::default(),
296 TerminalMode::Mixed,
297 ColorChoice::Auto,
298 );
299 let _logger = LoggerRuntime::init(clock.clone(), NullLog {}, Some(*term_logger));
300
301 let rec = RecordingStreamBuilder::new("Camera Viz")
302 .spawn()
303 .map_err(|e| CuError::new_with_cause("Failed to spawn rerun stream", e))
304 .unwrap();
305
306 let mut config = ComponentConfig::new();
307 config.set("device", 0);
308 config.set("width", IMG_WIDTH as u32);
309 config.set("height", IMG_HEIGHT as u32);
310 config.set("fps", 30);
311 config.set("fourcc", "NV12".to_string());
312 config.set("buffers", 4);
313 config.set("timeout_ms", 500);
314
315 let mut v4l = V4l::new(Some(&config), ()).unwrap();
316 v4l.start(&clock).unwrap();
317
318 let mut msg = CuMsg::new(None);
319 let format = rerun::components::ImageFormat(ImageFormat {
321 width: IMG_WIDTH as u32,
322 height: IMG_HEIGHT as u32,
323 pixel_format: Some(PixelFormat::NV12),
324 color_model: None, channel_datatype: None, });
327 for _ in 0..1000 {
328 let _output = v4l.process(&clock, &mut msg);
329 if let Some(frame) = msg.payload() {
330 let slice: &[u8] = &frame.buffer_handle.lock().unwrap();
331 let blob = Blob::from(slice);
332 let rerun_img = ImageBuffer::from(blob);
333 let image = Image::new(rerun_img, format);
334
335 rec.log("images", &image).unwrap();
336 } else {
337 debug!("----> No frame");
338 thread::sleep(Duration::from_millis(300)); }
340 }
341
342 v4l.stop(&clock).unwrap();
343 }
344 }
345}