1use std::fs::File;
21use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
22use std::path::Path;
23
24use crate::VideoError;
25use crate::annexb::Codec;
26use crate::v4l2;
27
28const OUTPUT_BUFFERS: u32 = 4;
31const OUTPUT_BUFFER_BYTES: u32 = 1 << 20;
33
34struct OutputBuffer {
36 map: MmapRegion,
38 queued: bool,
39}
40
41struct MmapRegion {
46 ptr: *mut core::ffi::c_void,
47 len: usize,
48}
49
50unsafe impl Send for MmapRegion {}
53
54impl MmapRegion {
55 fn map(fd: BorrowedFd<'_>, offset: u64, len: usize) -> Result<Self, VideoError> {
56 use rustix::mm::{MapFlags, ProtFlags, mmap};
57 let ptr = unsafe {
61 mmap(
62 core::ptr::null_mut(),
63 len,
64 ProtFlags::READ | ProtFlags::WRITE,
65 MapFlags::SHARED,
66 fd,
67 offset,
68 )
69 }
70 .map_err(|e| VideoError::v4l2("mmap", e))?;
71 Ok(Self { ptr, len })
72 }
73
74 fn write(&mut self, bytes: &[u8]) -> usize {
75 let n = bytes.len().min(self.len);
76 unsafe {
79 core::ptr::copy_nonoverlapping(bytes.as_ptr(), self.ptr.cast::<u8>(), n);
80 }
81 n
82 }
83}
84
85impl Drop for MmapRegion {
86 fn drop(&mut self) {
87 unsafe {
89 let _ = rustix::mm::munmap(self.ptr, self.len);
90 }
91 }
92}
93
94struct CaptureSide {
96 dmabufs: Vec<OwnedFd>,
98 width: u32,
99 height: u32,
100 pixelformat: u32,
102 stride: u32,
104 sizeimage: u32,
107}
108
109#[derive(Debug, Clone, Copy)]
115pub struct DecodedFrame {
116 pub index: u32,
119 pub width: u32,
121 pub height: u32,
123 pub pixelformat: u32,
125 pub stride: u32,
127 pub sizeimage: u32,
129}
130
131pub struct Decoder {
133 file: File,
134 codec: Codec,
135 output: Vec<OutputBuffer>,
136 capture: Option<CaptureSide>,
137 streaming: bool,
139}
140
141impl Decoder {
142 pub fn open(path: impl AsRef<Path>, codec: Codec) -> Result<Self, VideoError> {
148 use rustix::fs::{OFlags, fcntl_setfl};
149 let path = path.as_ref();
150 let file = std::fs::OpenOptions::new()
151 .read(true)
152 .write(true)
153 .open(path)
154 .map_err(|source| VideoError::Open {
155 path: path.to_path_buf(),
156 source,
157 })?;
158 fcntl_setfl(&file, OFlags::NONBLOCK).map_err(|e| VideoError::v4l2("fcntl", e))?;
159 let fd = file.as_fd();
160
161 v4l2::subscribe_event(fd, v4l2::EVENT_SOURCE_CHANGE)
162 .map_err(|e| VideoError::v4l2("subscribe source_change", e))?;
163
164 let mut format = v4l2::Format::zeroed(v4l2::BUF_TYPE_OUTPUT_MPLANE);
167 {
168 let pix = format.pix_mp_mut();
169 pix.pixelformat = match codec {
170 Codec::H264 => v4l2::PIX_FMT_H264,
171 Codec::H265 => v4l2::PIX_FMT_HEVC,
172 };
173 pix.num_planes = 1;
174 pix.plane_fmt[0].sizeimage = OUTPUT_BUFFER_BYTES;
175 }
176 v4l2::s_fmt(fd, &mut format).map_err(|e| VideoError::v4l2("s_fmt output", e))?;
177
178 let granted = v4l2::reqbufs(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE, OUTPUT_BUFFERS)
179 .map_err(|e| VideoError::v4l2("reqbufs output", e))?;
180 let mut output = Vec::with_capacity(granted as usize);
181 for index in 0..granted {
182 let mut planes = [v4l2::Plane::zeroed()];
183 v4l2::querybuf(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE, index, &mut planes)
184 .map_err(|e| VideoError::v4l2("querybuf output", e))?;
185 let offset = unsafe { planes[0].m.mem_offset } as u64;
188 let len = planes[0].length as usize;
189 output.push(OutputBuffer {
190 map: MmapRegion::map(fd, offset, len)?,
191 queued: false,
192 });
193 }
194
195 Ok(Self {
196 file,
197 codec,
198 output,
199 capture: None,
200 streaming: false,
201 })
202 }
203
204 pub fn codec(&self) -> Codec {
206 self.codec
207 }
208
209 pub fn ready_for_input(&mut self) -> bool {
211 self.reclaim_output();
212 self.output.iter().any(|b| !b.queued)
213 }
214
215 pub fn feed(&mut self, access_unit: &[u8]) -> Result<bool, VideoError> {
221 self.reclaim_output();
222 let Some(index) = self.output.iter().position(|b| !b.queued) else {
223 return Ok(false);
224 };
225 let used = self.output[index].map.write(access_unit);
226 let fd = self.file.as_fd();
227 let mut planes = [v4l2::Plane::zeroed()];
228 planes[0].bytesused = used as u32;
229 let mut buffer =
230 v4l2::Buffer::mplane(index as u32, v4l2::BUF_TYPE_OUTPUT_MPLANE, &mut planes);
231 v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf output", e))?;
232 self.output[index].queued = true;
233 if !self.streaming {
234 v4l2::streamon(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE)
235 .map_err(|e| VideoError::v4l2("streamon output", e))?;
236 self.streaming = true;
237 }
238 Ok(true)
239 }
240
241 fn reclaim_output(&mut self) {
243 let fd = self.file.as_fd();
244 loop {
245 let mut planes = [v4l2::Plane::zeroed()];
246 let mut buffer = v4l2::Buffer::mplane(0, v4l2::BUF_TYPE_OUTPUT_MPLANE, &mut planes);
247 match v4l2::dqbuf(fd, &mut buffer) {
248 Ok(Some(())) => {
249 if let Some(slot) = self.output.get_mut(buffer.index as usize) {
250 slot.queued = false;
251 }
252 }
253 _ => break,
254 }
255 }
256 }
257
258 pub fn pump(&mut self) -> Result<Option<DecodedFrame>, VideoError> {
261 self.reclaim_output();
262
263 while let Some(event) =
266 v4l2::dqevent(self.file.as_fd()).map_err(|e| VideoError::v4l2("dqevent", e))?
267 {
268 if event.event_type == v4l2::EVENT_SOURCE_CHANGE && self.capture.is_none() {
269 self.setup_capture()?;
270 }
271 }
272
273 let Some(capture) = &self.capture else {
274 return Ok(None);
275 };
276 let mut planes = [v4l2::Plane::zeroed()];
277 let mut buffer = v4l2::Buffer::mplane(0, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
278 match v4l2::dqbuf(self.file.as_fd(), &mut buffer)
279 .map_err(|e| VideoError::v4l2("dqbuf capture", e))?
280 {
281 None => Ok(None),
282 Some(()) => Ok(Some(DecodedFrame {
283 index: buffer.index,
284 width: capture.width,
285 height: capture.height,
286 pixelformat: capture.pixelformat,
287 stride: capture.stride,
288 sizeimage: capture.sizeimage,
289 })),
290 }
291 }
292
293 fn setup_capture(&mut self) -> Result<(), VideoError> {
295 let fd = self.file.as_fd();
296 let format = v4l2::g_fmt(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
297 .map_err(|e| VideoError::v4l2("g_fmt capture", e))?;
298 let pix = format.pix_mp();
299 match pix.pixelformat {
300 v4l2::PIX_FMT_NV12 | v4l2::PIX_FMT_YUV420 => {}
301 other => return Err(VideoError::UnsupportedFormat(other)),
302 }
303
304 let granted = v4l2::reqbufs(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE, 4)
305 .map_err(|e| VideoError::v4l2("reqbufs capture", e))?;
306 let mut dmabufs = Vec::with_capacity(granted as usize);
307 for index in 0..granted {
308 let raw = v4l2::expbuf(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE, index, 0)
309 .map_err(|e| VideoError::v4l2("expbuf", e))?;
310 dmabufs.push(unsafe { OwnedFd::from_raw_fd_checked(raw) });
313 let mut planes = [v4l2::Plane::zeroed()];
314 let mut buffer =
315 v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
316 v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))?;
317 }
318 v4l2::streamon(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
319 .map_err(|e| VideoError::v4l2("streamon capture", e))?;
320
321 self.capture = Some(CaptureSide {
322 dmabufs,
323 width: pix.width,
324 height: pix.height,
325 pixelformat: pix.pixelformat,
326 stride: pix.plane_fmt[0].bytesperline,
327 sizeimage: pix.plane_fmt[0].sizeimage,
328 });
329 Ok(())
330 }
331
332 pub fn dmabuf(&self, index: u32) -> Option<BorrowedFd<'_>> {
334 self.capture
335 .as_ref()
336 .and_then(|c| c.dmabufs.get(index as usize))
337 .map(|fd| fd.as_fd())
338 }
339
340 pub fn recycle(&mut self, index: u32) -> Result<(), VideoError> {
345 let mut planes = [v4l2::Plane::zeroed()];
346 let mut buffer = v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
347 v4l2::qbuf(self.file.as_fd(), &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))
348 }
349
350 pub fn restart(&mut self) -> Result<(), VideoError> {
354 let fd = self.file.as_fd();
355 if self.streaming {
356 v4l2::streamoff(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE)
357 .map_err(|e| VideoError::v4l2("streamoff output", e))?;
358 self.streaming = false;
359 }
360 for slot in &mut self.output {
361 slot.queued = false;
362 }
363 if self.capture.is_some() {
364 v4l2::streamoff(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
365 .map_err(|e| VideoError::v4l2("streamoff capture", e))?;
366 let count = self
369 .capture
370 .as_ref()
371 .map(|c| c.dmabufs.len() as u32)
372 .unwrap_or(0);
373 v4l2::streamon(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
374 .map_err(|e| VideoError::v4l2("streamon capture", e))?;
375 for index in 0..count {
376 let mut planes = [v4l2::Plane::zeroed()];
377 let mut buffer =
378 v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
379 v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))?;
380 }
381 }
382 Ok(())
383 }
384}
385
386trait FromRawChecked {
389 unsafe fn from_raw_fd_checked(raw: i32) -> OwnedFd;
391}
392
393impl FromRawChecked for OwnedFd {
394 unsafe fn from_raw_fd_checked(raw: i32) -> OwnedFd {
395 use std::os::fd::FromRawFd;
396 unsafe { OwnedFd::from_raw_fd(raw) }
398 }
399}