use std::ffi::c_void;
use std::fs::File;
use std::ops::{Deref, DerefMut};
use std::os::fd::AsFd;
use std::os::raw::c_int;
use std::os::unix::prelude::*;
use std::{io, slice};
use std::{mem, ptr};
use crate::buf_type::BufType;
use crate::raw;
use crate::shared::{BufFlag, Memory};
enum AllocType {
Mmap,
}
struct Buffer {
ptr: *mut c_void,
length: u32,
queued: bool,
}
struct Buffers {
ty: AllocType,
buffers: Vec<Buffer>,
}
unsafe impl Send for Buffers {}
unsafe impl Sync for Buffers {}
pub(super) const DEFAULT_BUFFER_COUNT: u32 = 2;
impl Buffers {
fn allocate(
fd: c_int,
buf_type: BufType,
mem_type: Memory,
mut buffer_count: u32,
) -> io::Result<Self> {
let alloc_type = match mem_type {
Memory::MMAP => AllocType::Mmap,
_ => unimplemented!("only `mmap` memory type is currently supported"),
};
let mut req_bufs: raw::RequestBuffers = unsafe { mem::zeroed() };
req_bufs.count = buffer_count;
req_bufs.type_ = buf_type;
req_bufs.memory = mem_type;
unsafe {
raw::VIDIOC_REQBUFS.ioctl(&fd, &mut req_bufs)?;
}
log::debug!("{:?}", req_bufs);
if req_bufs.count < buffer_count {
log::trace!("failed to allocate {buffer_count} buffers (driver only allocated {0}), using {0} instead", req_bufs.count);
buffer_count = req_bufs.count;
}
let mut buffers = Vec::with_capacity(buffer_count as usize);
for i in 0..buffer_count {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = buf_type;
buf.memory = mem_type;
buf.index = i;
unsafe {
raw::VIDIOC_QUERYBUF.ioctl(&fd, &mut buf)?;
}
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
buf.length as _,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
buf.m.offset.into(),
)
};
if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
assert_eq!(buf.index, i);
assert_eq!(buf.index as usize, buffers.len());
buffers.push(Buffer {
ptr,
length: buf.length,
queued: false,
});
}
Ok(Self {
ty: alloc_type,
buffers,
})
}
}
impl Drop for Buffers {
fn drop(&mut self) {
for buffer in &self.buffers {
match self.ty {
AllocType::Mmap => unsafe {
if libc::munmap(buffer.ptr, buffer.length as _) == -1 {
log::warn!("failed to `munmap` on drop: {}", io::Error::last_os_error());
}
},
}
}
}
}
pub struct ReadStream {
file: File,
buffers: Buffers,
buf_type: BufType,
mem_type: Memory,
}
impl ReadStream {
pub(crate) fn new(
file: File,
buf_type: BufType,
mem_type: Memory,
buffer_count: u32,
) -> io::Result<Self> {
let fd = file.as_raw_fd();
let buffers = Buffers::allocate(fd, buf_type, mem_type, buffer_count)?;
let mut this = Self {
file,
buffers,
buf_type,
mem_type,
};
this.enqueue_all()?;
this.stream_on()?;
Ok(this)
}
fn enqueue(&mut self, index: u32) -> io::Result<()> {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = self.buf_type;
buf.memory = self.mem_type;
buf.index = index;
unsafe {
raw::VIDIOC_QBUF.ioctl(self, &mut buf)?;
}
self.buffers.buffers[index as usize].queued = true;
Ok(())
}
fn enqueue_all(&mut self) -> io::Result<()> {
for i in 0..self.buffers.buffers.len() {
if !self.buffers.buffers[i].queued {
self.enqueue(i as u32)?;
}
}
Ok(())
}
fn stream_on(&mut self) -> io::Result<()> {
unsafe {
let buf_type = self.buf_type.0 as c_int;
raw::VIDIOC_STREAMON.ioctl(self, &buf_type)?;
}
Ok(())
}
fn stream_off(&mut self) -> io::Result<()> {
unsafe {
let buf_type = self.buf_type.0 as c_int;
raw::VIDIOC_STREAMOFF.ioctl(self, &buf_type)?;
}
for b in &mut self.buffers.buffers {
b.queued = false;
}
Ok(())
}
pub fn dequeue<T>(
&mut self,
cb: impl FnOnce(ReadBufferView<'_>) -> io::Result<T>,
) -> io::Result<T> {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = self.buf_type;
buf.memory = self.mem_type;
unsafe {
raw::VIDIOC_DQBUF.ioctl(self, &mut buf)?;
}
let buffer = &mut self.buffers.buffers[buf.index as usize];
buffer.queued = false;
let data =
unsafe { slice::from_raw_parts(buffer.ptr as *const u8, buffer.length as usize) };
let view = ReadBufferView {
flags: buf.flags,
data,
bytesused: buf.bytesused as usize,
};
let res = cb(view);
self.enqueue(buf.index)?;
res
}
pub fn will_block(&self) -> io::Result<bool> {
for i in 0..self.buffers.buffers.len() {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = self.buf_type;
buf.memory = self.mem_type;
buf.index = i as u32;
unsafe {
raw::VIDIOC_QUERYBUF.ioctl(self, &mut buf)?;
}
if buf.flags.contains(BufFlag::DONE) {
return Ok(false);
}
}
Ok(true)
}
}
impl Drop for ReadStream {
fn drop(&mut self) {
self.stream_off().ok();
}
}
impl AsRawFd for ReadStream {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for ReadStream {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct ReadBufferView<'a> {
flags: BufFlag,
data: &'a [u8],
bytesused: usize,
}
impl<'a> ReadBufferView<'a> {
#[inline]
pub fn is_error(&self) -> bool {
self.flags.contains(BufFlag::ERROR)
}
#[inline]
pub fn raw_buffer(&self) -> &'a [u8] {
self.data
}
}
impl Deref for ReadBufferView<'_> {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
&self.data[..self.bytesused]
}
}
pub struct WriteStream {
file: File,
buffers: Buffers,
next_unqueued_buffer: Option<usize>,
buf_type: BufType,
mem_type: Memory,
}
impl WriteStream {
pub(crate) fn new(
file: File,
buf_type: BufType,
mem_type: Memory,
buffer_count: u32,
) -> io::Result<Self> {
let fd = file.as_raw_fd();
let buffers = Buffers::allocate(fd, buf_type, mem_type, buffer_count)?;
Ok(Self {
file,
buffers,
next_unqueued_buffer: Some(0),
buf_type,
mem_type,
})
}
fn enqueue_buffer(&mut self, index: u32) -> io::Result<()> {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = self.buf_type;
buf.memory = self.mem_type;
buf.index = index;
unsafe {
raw::VIDIOC_QBUF.ioctl(self, &mut buf)?;
}
self.buffers.buffers[index as usize].queued = true;
Ok(())
}
pub fn enqueue<T>(
&mut self,
cb: impl FnOnce(WriteBufferView<'_>) -> io::Result<T>,
) -> io::Result<T> {
let buf_index = match self.next_unqueued_buffer {
Some(i) => i,
None => {
let mut buf: raw::Buffer = unsafe { mem::zeroed() };
buf.type_ = self.buf_type;
buf.memory = self.mem_type;
unsafe {
raw::VIDIOC_DQBUF.ioctl(self, &mut buf)?;
}
let buf_index = buf.index as usize;
self.buffers.buffers[buf_index].queued = false;
buf_index
}
};
let buffer = &mut self.buffers.buffers[buf_index];
assert!(!buffer.queued);
let data =
unsafe { slice::from_raw_parts_mut(buffer.ptr as *mut u8, buffer.length as usize) };
let view = WriteBufferView { data };
match cb(view) {
Ok(val) => match self.enqueue_buffer(buf_index as u32) {
Ok(()) => {
match self.next_unqueued_buffer {
Some(i) => {
if i + 1 == self.buffers.buffers.len() {
self.next_unqueued_buffer = None;
} else {
self.next_unqueued_buffer = Some(i + 1);
}
}
None => {
}
}
Ok(val)
}
Err(e) => {
self.next_unqueued_buffer = Some(buf_index);
Err(e)
}
},
Err(e) => {
self.next_unqueued_buffer = Some(buf_index);
Err(e)
}
}
}
}
impl AsRawFd for WriteStream {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for WriteStream {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
pub struct WriteBufferView<'a> {
data: &'a mut [u8],
}
impl Deref for WriteBufferView<'_> {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
self.data
}
}
impl DerefMut for WriteBufferView<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_types_are_send_sync() {
fn assert<T: Send + Sync>() {}
assert::<WriteStream>();
assert::<ReadStream>();
assert::<WriteBufferView<'_>>();
assert::<ReadBufferView<'_>>();
}
}