use std::fs::File;
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::path::Path;
use crate::VideoError;
use crate::annexb::Codec;
use crate::v4l2;
const OUTPUT_BUFFERS: u32 = 4;
const OUTPUT_BUFFER_BYTES: u32 = 1 << 20;
struct OutputBuffer {
map: MmapRegion,
queued: bool,
}
struct MmapRegion {
ptr: *mut core::ffi::c_void,
len: usize,
}
unsafe impl Send for MmapRegion {}
impl MmapRegion {
fn map(fd: BorrowedFd<'_>, offset: u64, len: usize) -> Result<Self, VideoError> {
use rustix::mm::{MapFlags, ProtFlags, mmap};
let ptr = unsafe {
mmap(
core::ptr::null_mut(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED,
fd,
offset,
)
}
.map_err(|e| VideoError::v4l2("mmap", e))?;
Ok(Self { ptr, len })
}
fn write(&mut self, bytes: &[u8]) -> usize {
let n = bytes.len().min(self.len);
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), self.ptr.cast::<u8>(), n);
}
n
}
}
impl Drop for MmapRegion {
fn drop(&mut self) {
unsafe {
let _ = rustix::mm::munmap(self.ptr, self.len);
}
}
}
struct CaptureSide {
dmabufs: Vec<OwnedFd>,
width: u32,
height: u32,
pixelformat: u32,
stride: u32,
sizeimage: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct DecodedFrame {
pub index: u32,
pub width: u32,
pub height: u32,
pub pixelformat: u32,
pub stride: u32,
pub sizeimage: u32,
}
pub struct Decoder {
file: File,
codec: Codec,
output: Vec<OutputBuffer>,
capture: Option<CaptureSide>,
streaming: bool,
}
impl Decoder {
pub fn open(path: impl AsRef<Path>, codec: Codec) -> Result<Self, VideoError> {
use rustix::fs::{OFlags, fcntl_setfl};
let path = path.as_ref();
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|source| VideoError::Open {
path: path.to_path_buf(),
source,
})?;
fcntl_setfl(&file, OFlags::NONBLOCK).map_err(|e| VideoError::v4l2("fcntl", e))?;
let fd = file.as_fd();
v4l2::subscribe_event(fd, v4l2::EVENT_SOURCE_CHANGE)
.map_err(|e| VideoError::v4l2("subscribe source_change", e))?;
let mut format = v4l2::Format::zeroed(v4l2::BUF_TYPE_OUTPUT_MPLANE);
{
let pix = format.pix_mp_mut();
pix.pixelformat = match codec {
Codec::H264 => v4l2::PIX_FMT_H264,
Codec::H265 => v4l2::PIX_FMT_HEVC,
};
pix.num_planes = 1;
pix.plane_fmt[0].sizeimage = OUTPUT_BUFFER_BYTES;
}
v4l2::s_fmt(fd, &mut format).map_err(|e| VideoError::v4l2("s_fmt output", e))?;
let granted = v4l2::reqbufs(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE, OUTPUT_BUFFERS)
.map_err(|e| VideoError::v4l2("reqbufs output", e))?;
let mut output = Vec::with_capacity(granted as usize);
for index in 0..granted {
let mut planes = [v4l2::Plane::zeroed()];
v4l2::querybuf(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE, index, &mut planes)
.map_err(|e| VideoError::v4l2("querybuf output", e))?;
let offset = unsafe { planes[0].m.mem_offset } as u64;
let len = planes[0].length as usize;
output.push(OutputBuffer {
map: MmapRegion::map(fd, offset, len)?,
queued: false,
});
}
Ok(Self {
file,
codec,
output,
capture: None,
streaming: false,
})
}
pub fn codec(&self) -> Codec {
self.codec
}
pub fn ready_for_input(&mut self) -> bool {
self.reclaim_output();
self.output.iter().any(|b| !b.queued)
}
pub fn feed(&mut self, access_unit: &[u8]) -> Result<bool, VideoError> {
self.reclaim_output();
let Some(index) = self.output.iter().position(|b| !b.queued) else {
return Ok(false);
};
let used = self.output[index].map.write(access_unit);
let fd = self.file.as_fd();
let mut planes = [v4l2::Plane::zeroed()];
planes[0].bytesused = used as u32;
let mut buffer =
v4l2::Buffer::mplane(index as u32, v4l2::BUF_TYPE_OUTPUT_MPLANE, &mut planes);
v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf output", e))?;
self.output[index].queued = true;
if !self.streaming {
v4l2::streamon(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE)
.map_err(|e| VideoError::v4l2("streamon output", e))?;
self.streaming = true;
}
Ok(true)
}
fn reclaim_output(&mut self) {
let fd = self.file.as_fd();
loop {
let mut planes = [v4l2::Plane::zeroed()];
let mut buffer = v4l2::Buffer::mplane(0, v4l2::BUF_TYPE_OUTPUT_MPLANE, &mut planes);
match v4l2::dqbuf(fd, &mut buffer) {
Ok(Some(())) => {
if let Some(slot) = self.output.get_mut(buffer.index as usize) {
slot.queued = false;
}
}
_ => break,
}
}
}
pub fn pump(&mut self) -> Result<Option<DecodedFrame>, VideoError> {
self.reclaim_output();
while let Some(event) =
v4l2::dqevent(self.file.as_fd()).map_err(|e| VideoError::v4l2("dqevent", e))?
{
if event.event_type == v4l2::EVENT_SOURCE_CHANGE && self.capture.is_none() {
self.setup_capture()?;
}
}
let Some(capture) = &self.capture else {
return Ok(None);
};
let mut planes = [v4l2::Plane::zeroed()];
let mut buffer = v4l2::Buffer::mplane(0, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
match v4l2::dqbuf(self.file.as_fd(), &mut buffer)
.map_err(|e| VideoError::v4l2("dqbuf capture", e))?
{
None => Ok(None),
Some(()) => Ok(Some(DecodedFrame {
index: buffer.index,
width: capture.width,
height: capture.height,
pixelformat: capture.pixelformat,
stride: capture.stride,
sizeimage: capture.sizeimage,
})),
}
}
fn setup_capture(&mut self) -> Result<(), VideoError> {
let fd = self.file.as_fd();
let format = v4l2::g_fmt(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
.map_err(|e| VideoError::v4l2("g_fmt capture", e))?;
let pix = format.pix_mp();
match pix.pixelformat {
v4l2::PIX_FMT_NV12 | v4l2::PIX_FMT_YUV420 => {}
other => return Err(VideoError::UnsupportedFormat(other)),
}
let granted = v4l2::reqbufs(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE, 4)
.map_err(|e| VideoError::v4l2("reqbufs capture", e))?;
let mut dmabufs = Vec::with_capacity(granted as usize);
for index in 0..granted {
let raw = v4l2::expbuf(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE, index, 0)
.map_err(|e| VideoError::v4l2("expbuf", e))?;
dmabufs.push(unsafe { OwnedFd::from_raw_fd_checked(raw) });
let mut planes = [v4l2::Plane::zeroed()];
let mut buffer =
v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))?;
}
v4l2::streamon(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
.map_err(|e| VideoError::v4l2("streamon capture", e))?;
self.capture = Some(CaptureSide {
dmabufs,
width: pix.width,
height: pix.height,
pixelformat: pix.pixelformat,
stride: pix.plane_fmt[0].bytesperline,
sizeimage: pix.plane_fmt[0].sizeimage,
});
Ok(())
}
pub fn dmabuf(&self, index: u32) -> Option<BorrowedFd<'_>> {
self.capture
.as_ref()
.and_then(|c| c.dmabufs.get(index as usize))
.map(|fd| fd.as_fd())
}
pub fn recycle(&mut self, index: u32) -> Result<(), VideoError> {
let mut planes = [v4l2::Plane::zeroed()];
let mut buffer = v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
v4l2::qbuf(self.file.as_fd(), &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))
}
pub fn restart(&mut self) -> Result<(), VideoError> {
let fd = self.file.as_fd();
if self.streaming {
v4l2::streamoff(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE)
.map_err(|e| VideoError::v4l2("streamoff output", e))?;
self.streaming = false;
}
for slot in &mut self.output {
slot.queued = false;
}
if self.capture.is_some() {
v4l2::streamoff(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
.map_err(|e| VideoError::v4l2("streamoff capture", e))?;
let count = self
.capture
.as_ref()
.map(|c| c.dmabufs.len() as u32)
.unwrap_or(0);
v4l2::streamon(fd, v4l2::BUF_TYPE_CAPTURE_MPLANE)
.map_err(|e| VideoError::v4l2("streamon capture", e))?;
for index in 0..count {
let mut planes = [v4l2::Plane::zeroed()];
let mut buffer =
v4l2::Buffer::mplane(index, v4l2::BUF_TYPE_CAPTURE_MPLANE, &mut planes);
v4l2::qbuf(fd, &mut buffer).map_err(|e| VideoError::v4l2("qbuf capture", e))?;
}
}
Ok(())
}
}
trait FromRawChecked {
unsafe fn from_raw_fd_checked(raw: i32) -> OwnedFd;
}
impl FromRawChecked for OwnedFd {
unsafe fn from_raw_fd_checked(raw: i32) -> OwnedFd {
use std::os::fd::FromRawFd;
unsafe { OwnedFd::from_raw_fd(raw) }
}
}