use std::collections::VecDeque;
use std::io::BufWriter;
use std::io::Result as IoResult;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::os::fd::AsFd;
use std::os::fd::BorrowedFd;
use v4l2r::bindings;
use v4l2r::bindings::v4l2_fmtdesc;
use v4l2r::bindings::v4l2_format;
use v4l2r::bindings::v4l2_requestbuffers;
use v4l2r::ioctl::BufferCapabilities;
use v4l2r::ioctl::BufferField;
use v4l2r::ioctl::BufferFlags;
use v4l2r::ioctl::MemoryConsistency;
use v4l2r::ioctl::V4l2Buffer;
use v4l2r::ioctl::V4l2PlanesWithBackingMut;
use v4l2r::memory::MemoryType;
use v4l2r::PixelFormat;
use v4l2r::QueueType;
use crate::ioctl::virtio_media_dispatch_ioctl;
use crate::ioctl::IoctlResult;
use crate::ioctl::VirtioMediaIoctlHandler;
use crate::memfd::MemFdBuffer;
use crate::mmap::MmapMappingManager;
use crate::protocol::DequeueBufferEvent;
use crate::protocol::SgEntry;
use crate::protocol::V4l2Event;
use crate::protocol::V4l2Ioctl;
use crate::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW;
use crate::ReadFromDescriptorChain;
use crate::VirtioMediaDevice;
use crate::VirtioMediaDeviceSession;
use crate::VirtioMediaEventQueue;
use crate::VirtioMediaHostMemoryMapper;
use crate::WriteToDescriptorChain;
#[derive(Debug, PartialEq, Eq)]
enum BufferState {
New,
Incoming,
Outgoing {
sequence: u32,
},
}
struct Buffer {
state: BufferState,
v4l2_buffer: V4l2Buffer,
fd: MemFdBuffer,
offset: u32,
}
impl Buffer {
fn set_state(&mut self, state: BufferState) {
let mut flags = self.v4l2_buffer.flags();
match state {
BufferState::New => {
*self.v4l2_buffer.get_first_plane_mut().bytesused = 0;
flags -= BufferFlags::QUEUED;
}
BufferState::Incoming => {
*self.v4l2_buffer.get_first_plane_mut().bytesused = 0;
flags |= BufferFlags::QUEUED;
}
BufferState::Outgoing { sequence } => {
*self.v4l2_buffer.get_first_plane_mut().bytesused = BUFFER_SIZE;
self.v4l2_buffer.set_sequence(sequence);
self.v4l2_buffer.set_timestamp(bindings::timeval {
tv_sec: (sequence + 1) as bindings::__time_t / 1000,
tv_usec: (sequence + 1) as bindings::__time_t % 1000,
});
flags -= BufferFlags::QUEUED;
}
}
self.v4l2_buffer.set_flags(flags);
self.state = state;
}
}
pub struct SimpleCaptureDeviceSession {
id: u32,
iteration: u64,
buffers: Vec<Buffer>,
queued_buffers: VecDeque<usize>,
streaming: bool,
}
impl VirtioMediaDeviceSession for SimpleCaptureDeviceSession {
fn poll_fd(&self) -> Option<BorrowedFd<'_>> {
None
}
}
impl SimpleCaptureDeviceSession {
fn process_queued_buffers<Q: VirtioMediaEventQueue>(
&mut self,
evt_queue: &mut Q,
) -> IoctlResult<()> {
while let Some(buf_id) = self.queued_buffers.pop_front() {
let buffer = self.buffers.get_mut(buf_id).ok_or(libc::EIO)?;
let sequence = self.iteration as u32;
buffer
.fd
.as_file()
.seek(SeekFrom::Start(0))
.map_err(|_| libc::EIO)?;
let mut writer = BufWriter::new(buffer.fd.as_file());
let y = (sequence % 256) as u8;
let u = ((sequence + 64) % 256) as u8;
let v = ((sequence + 128) % 256) as u8;
for _ in 0..(WIDTH * HEIGHT) {
writer.write_all(&[y]).map_err(|_| libc::EIO)?;
}
for _ in 0..(WIDTH * HEIGHT / 4) {
writer.write_all(&[u]).map_err(|_| libc::EIO)?;
}
for _ in 0..(WIDTH * HEIGHT / 4) {
writer.write_all(&[v]).map_err(|_| libc::EIO)?;
}
drop(writer);
*buffer.v4l2_buffer.get_first_plane_mut().bytesused = BUFFER_SIZE;
buffer.set_state(BufferState::Outgoing { sequence });
self.iteration += 1;
let v4l2_buffer = buffer.v4l2_buffer.clone();
evt_queue.send_event(V4l2Event::DequeueBuffer(DequeueBufferEvent::new(
self.id,
v4l2_buffer,
)));
}
Ok(())
}
}
pub struct SimpleCaptureDevice<Q: VirtioMediaEventQueue, HM: VirtioMediaHostMemoryMapper> {
evt_queue: Q,
mmap_manager: MmapMappingManager<HM>,
active_session: Option<u32>,
}
impl<Q, HM> SimpleCaptureDevice<Q, HM>
where
Q: VirtioMediaEventQueue,
HM: VirtioMediaHostMemoryMapper,
{
pub fn new(evt_queue: Q, mapper: HM) -> Self {
Self {
evt_queue,
mmap_manager: MmapMappingManager::from(mapper),
active_session: None,
}
}
}
impl<Q, HM, Reader, Writer> VirtioMediaDevice<Reader, Writer> for SimpleCaptureDevice<Q, HM>
where
Q: VirtioMediaEventQueue,
HM: VirtioMediaHostMemoryMapper,
Reader: ReadFromDescriptorChain,
Writer: WriteToDescriptorChain,
{
type Session = SimpleCaptureDeviceSession;
fn new_session(&mut self, session_id: u32) -> Result<Self::Session, i32> {
Ok(SimpleCaptureDeviceSession {
id: session_id,
iteration: 0,
buffers: Default::default(),
queued_buffers: Default::default(),
streaming: false,
})
}
fn close_session(&mut self, session: Self::Session) {
if self.active_session == Some(session.id) {
self.active_session = None;
}
for buffer in &session.buffers {
self.mmap_manager.unregister_buffer(buffer.offset);
}
}
fn do_ioctl(
&mut self,
session: &mut Self::Session,
ioctl: V4l2Ioctl,
reader: &mut Reader,
writer: &mut Writer,
) -> IoResult<()> {
virtio_media_dispatch_ioctl(self, session, ioctl, reader, writer)
}
fn do_mmap(
&mut self,
session: &mut Self::Session,
flags: u32,
offset: u32,
) -> Result<(u64, u64), i32> {
let buffer = session
.buffers
.iter_mut()
.find(|b| b.offset == offset)
.ok_or(libc::EINVAL)?;
let rw = (flags & VIRTIO_MEDIA_MMAP_FLAG_RW) != 0;
let fd = buffer.fd.as_file().as_fd();
let (guest_addr, size) = self
.mmap_manager
.create_mapping(offset, fd, rw)
.map_err(|_| libc::EINVAL)?;
Ok((guest_addr, size))
}
fn do_munmap(&mut self, guest_addr: u64) -> Result<(), i32> {
self.mmap_manager
.remove_mapping(guest_addr)
.map(|_| ())
.map_err(|_| libc::EINVAL)
}
}
const PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"YU12").to_u32();
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
const FRAME_RATE: u32 = 30;
const BUFFER_SIZE: u32 = WIDTH * HEIGHT * 3 / 2;
const INPUTS: [bindings::v4l2_input; 1] = [bindings::v4l2_input {
index: 0,
name: *b"Default\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
type_: bindings::V4L2_INPUT_TYPE_CAMERA,
..unsafe { std::mem::zeroed() }
}];
fn default_fmtdesc(queue: QueueType) -> v4l2_fmtdesc {
v4l2_fmtdesc {
index: 0,
type_: queue as u32,
pixelformat: PIXELFORMAT,
..Default::default()
}
}
fn default_fmt(queue: QueueType) -> v4l2_format {
let pix_mp = bindings::v4l2_pix_format_mplane {
width: WIDTH,
height: HEIGHT,
pixelformat: PIXELFORMAT,
field: bindings::v4l2_field_V4L2_FIELD_NONE,
colorspace: bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB,
num_planes: 3,
plane_fmt: [
bindings::v4l2_plane_pix_format {
sizeimage: WIDTH * HEIGHT,
bytesperline: WIDTH,
..Default::default()
},
bindings::v4l2_plane_pix_format {
sizeimage: WIDTH * HEIGHT / 4,
bytesperline: WIDTH / 2,
..Default::default()
},
bindings::v4l2_plane_pix_format {
sizeimage: WIDTH * HEIGHT / 4,
bytesperline: WIDTH / 2,
..Default::default()
},
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
],
..Default::default()
};
v4l2_format {
type_: queue as u32,
fmt: bindings::v4l2_format__bindgen_ty_1 { pix_mp },
}
}
impl<Q, HM> VirtioMediaIoctlHandler for SimpleCaptureDevice<Q, HM>
where
Q: VirtioMediaEventQueue,
HM: VirtioMediaHostMemoryMapper,
{
type Session = SimpleCaptureDeviceSession;
fn enum_fmt(
&mut self,
_session: &Self::Session,
queue: QueueType,
index: u32,
) -> IoctlResult<v4l2_fmtdesc> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
if index > 0 {
return Err(libc::EINVAL);
}
Ok(default_fmtdesc(queue))
}
fn g_fmt(&mut self, _session: &Self::Session, queue: QueueType) -> IoctlResult<v4l2_format> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
Ok(default_fmt(queue))
}
fn s_fmt(
&mut self,
_session: &mut Self::Session,
queue: QueueType,
_format: v4l2_format,
) -> IoctlResult<v4l2_format> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
Ok(default_fmt(queue))
}
fn try_fmt(
&mut self,
_session: &Self::Session,
queue: QueueType,
_format: v4l2_format,
) -> IoctlResult<v4l2_format> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
Ok(default_fmt(queue))
}
fn g_parm(
&mut self,
_session: &Self::Session,
queue: QueueType,
) -> IoctlResult<bindings::v4l2_streamparm> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
let mut parm = bindings::v4l2_streamparm {
type_: queue as u32,
..Default::default()
};
let capture = unsafe { &mut parm.parm.capture };
capture.capability = bindings::V4L2_CAP_TIMEPERFRAME;
capture.timeperframe = bindings::v4l2_fract {
numerator: 1,
denominator: FRAME_RATE,
};
Ok(parm)
}
fn s_parm(
&mut self,
_session: &mut Self::Session,
mut parm: bindings::v4l2_streamparm,
) -> IoctlResult<bindings::v4l2_streamparm> {
if parm.type_ != QueueType::VideoCaptureMplane as u32 {
return Err(libc::EINVAL);
}
let capture = unsafe { &mut parm.parm.capture };
capture.capability = bindings::V4L2_CAP_TIMEPERFRAME;
capture.timeperframe = bindings::v4l2_fract {
numerator: 1,
denominator: FRAME_RATE,
};
Ok(parm)
}
fn reqbufs(
&mut self,
session: &mut Self::Session,
queue: QueueType,
memory: MemoryType,
count: u32,
_flags: MemoryConsistency,
) -> IoctlResult<v4l2_requestbuffers> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
if memory != MemoryType::Mmap {
return Err(libc::EINVAL);
}
if session.streaming {
return Err(libc::EBUSY);
}
match self.active_session {
Some(id) if id != session.id => return Err(libc::EBUSY),
_ => (),
}
if count == 0 {
self.active_session = None;
self.streamoff(session, queue)?;
} else {
session.queued_buffers.clear();
for buffer in session.buffers.iter_mut() {
buffer.set_state(BufferState::New);
}
self.active_session = Some(session.id);
}
let count = std::cmp::min(count, 32);
for buffer in &session.buffers {
self.mmap_manager.unregister_buffer(buffer.offset);
}
session.buffers = (0..count)
.map(|i| {
MemFdBuffer::new(BUFFER_SIZE as u64)
.map_err(|e| {
log::error!("failed to allocate MMAP buffers: {:#}", e);
libc::ENOMEM
})
.and_then(|fd| {
let offset = self
.mmap_manager
.register_buffer(None, BUFFER_SIZE)
.map_err(|_| libc::EINVAL)?;
let mut v4l2_buffer = V4l2Buffer::new(queue, i, MemoryType::Mmap);
if let V4l2PlanesWithBackingMut::Mmap(mut planes) =
v4l2_buffer.planes_with_backing_iter_mut()
{
let mut plane = planes.next().unwrap();
plane.set_mem_offset(offset);
*plane.length = BUFFER_SIZE;
} else {
panic!()
}
v4l2_buffer.set_field(BufferField::None);
v4l2_buffer.set_flags(BufferFlags::TIMESTAMP_MONOTONIC);
Ok(Buffer {
state: BufferState::New,
v4l2_buffer,
fd,
offset,
})
})
})
.collect::<Result<_, _>>()?;
Ok(v4l2_requestbuffers {
count,
type_: queue as u32,
memory: memory as u32,
capabilities: (BufferCapabilities::SUPPORTS_MMAP
| BufferCapabilities::SUPPORTS_ORPHANED_BUFS)
.bits(),
flags: 0,
..Default::default()
})
}
fn querybuf(
&mut self,
session: &Self::Session,
queue: QueueType,
index: u32,
) -> IoctlResult<v4l2r::ioctl::V4l2Buffer> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
let buffer = session.buffers.get(index as usize).ok_or(libc::EINVAL)?;
Ok(buffer.v4l2_buffer.clone())
}
fn qbuf(
&mut self,
session: &mut Self::Session,
buffer: v4l2r::ioctl::V4l2Buffer,
_guest_regions: Vec<Vec<SgEntry>>,
) -> IoctlResult<v4l2r::ioctl::V4l2Buffer> {
let host_buffer = session
.buffers
.get_mut(buffer.index() as usize)
.ok_or(libc::EINVAL)?;
if matches!(host_buffer.state, BufferState::Incoming) {
return Err(libc::EINVAL);
}
host_buffer.set_state(BufferState::Incoming);
session.queued_buffers.push_back(buffer.index() as usize);
let buffer = host_buffer.v4l2_buffer.clone();
if session.streaming {
session.process_queued_buffers(&mut self.evt_queue)?;
}
Ok(buffer)
}
fn streamon(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
if queue != QueueType::VideoCaptureMplane || session.buffers.is_empty() {
return Err(libc::EINVAL);
}
session.streaming = true;
session.process_queued_buffers(&mut self.evt_queue)?;
Ok(())
}
fn streamoff(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
if queue != QueueType::VideoCaptureMplane {
return Err(libc::EINVAL);
}
session.streaming = false;
session.queued_buffers.clear();
for buffer in session.buffers.iter_mut() {
buffer.set_state(BufferState::New);
}
Ok(())
}
fn g_input(&mut self, _session: &Self::Session) -> IoctlResult<i32> {
Ok(0)
}
fn s_input(&mut self, _session: &mut Self::Session, input: i32) -> IoctlResult<i32> {
if input != 0 {
Err(libc::EINVAL)
} else {
Ok(0)
}
}
fn enuminput(
&mut self,
_session: &Self::Session,
index: u32,
) -> IoctlResult<bindings::v4l2_input> {
match INPUTS.get(index as usize) {
Some(&input) => Ok(input),
None => Err(libc::EINVAL),
}
}
fn enum_framesizes(
&mut self,
_session: &Self::Session,
index: u32,
pixel_format: u32,
) -> IoctlResult<bindings::v4l2_frmsizeenum> {
if pixel_format != PIXELFORMAT {
return Err(libc::EINVAL);
}
if index > 0 {
return Err(libc::EINVAL);
}
Ok(bindings::v4l2_frmsizeenum {
index,
pixel_format,
type_: bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE,
__bindgen_anon_1: bindings::v4l2_frmsizeenum__bindgen_ty_1 {
discrete: bindings::v4l2_frmsize_discrete {
width: WIDTH,
height: HEIGHT,
},
},
..Default::default()
})
}
fn enum_frameintervals(
&mut self,
_session: &Self::Session,
index: u32,
pixel_format: u32,
width: u32,
height: u32,
) -> IoctlResult<bindings::v4l2_frmivalenum> {
if pixel_format != PIXELFORMAT {
return Err(libc::EINVAL);
}
if width != WIDTH || height != HEIGHT {
return Err(libc::EINVAL);
}
if index > 0 {
return Err(libc::EINVAL);
}
Ok(bindings::v4l2_frmivalenum {
index,
pixel_format,
width,
height,
type_: bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_DISCRETE,
__bindgen_anon_1: bindings::v4l2_frmivalenum__bindgen_ty_1 {
discrete: bindings::v4l2_fract {
numerator: 1,
denominator: FRAME_RATE,
},
},
..Default::default()
})
}
}