diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/BUILD.bazel index 91d42116f63..d4996d719f8 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/BUILD.bazel @@ -20,10 +20,8 @@ rust_binary( edition = "2024", deps = crate_deps([ "clap", - "env_logger", "libc", "log", - "thiserror", "v4l2r", "vhost-user-backend", "virtio-media", diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/Cargo.toml index 18f4dfc9093..17d5ccbe53e 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/Cargo.toml +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/Cargo.toml @@ -5,10 +5,8 @@ edition = "2024" [dependencies] clap = { workspace = true } -env_logger = { workspace = true } libc = { workspace = true } log = { workspace = true } -thiserror = { workspace = true } v4l2r = { workspace = true } vhost-user-backend = { workspace = true } vhu_media = { workspace = true } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/device.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/device.rs index 0fca80f05a3..3c6961bad80 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/device.rs +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/device.rs @@ -12,285 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -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::PixelFormat; use v4l2r::QueueType; 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::V4l2Buffer; -use v4l2r::ioctl::V4l2PlanesWithBackingMut; -use v4l2r::memory::MemoryType; -use virtio_media::VirtioMediaDevice; -use virtio_media::VirtioMediaDeviceSession; -use virtio_media::VirtioMediaEventQueue; -use virtio_media::VirtioMediaHostMemoryMapper; -use virtio_media::io::ReadFromDescriptorChain; -use virtio_media::io::WriteToDescriptorChain; +use vhu_media::devices::CaptureDeviceFormat; use virtio_media::ioctl::IoctlResult; -use virtio_media::ioctl::VirtioMediaIoctlHandler; -use virtio_media::ioctl::virtio_media_dispatch_ioctl; -use virtio_media::memfd::MemFdBuffer; -use virtio_media::mmap::MmapMappingManager; -use virtio_media::protocol::DequeueBufferEvent; -use virtio_media::protocol::SgEntry; -use virtio_media::protocol::V4l2Event; -use virtio_media::protocol::V4l2Ioctl; -use virtio_media::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW; -/// Current status of a buffer. -#[derive(Debug, PartialEq, Eq)] -enum BufferState { - /// Buffer has just been created (or streamed off) and not been used yet. - New, - /// Buffer has been QBUF'd by the driver but not yet processed. - Incoming, - /// Buffer has been processed and is ready for dequeue. - Outgoing { - /// Sequence of the generated frame. - sequence: u32, - }, -} - -/// Information about a single buffer. -struct Buffer { - /// Current state of the buffer. - state: BufferState, - /// V4L2 representation of this buffer to be sent to the guest when requested. - v4l2_buffer: V4l2Buffer, - /// Backing storage for the buffer. - fd: MemFdBuffer, - /// Offset that can be used to map the buffer. - /// - /// Cached from `v4l2_buffer` to avoid doing a match. - offset: u32, -} - -impl Buffer { - fn new(v4l2_buffer: V4l2Buffer, fd: MemFdBuffer, offset: u32) -> Self { - Self { - state: BufferState::New, - v4l2_buffer, - fd, - offset, - } - } - - /// Update the state of the buffer as well as its V4L2 representation. - 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; - } -} - -/// Session data of [`EmulatedCamera`]. -pub struct EmulatedCameraSession { - /// Id of the session. - id: u32, - /// Current iteration of the pattern generation cycle. - iteration: u64, - /// Buffers currently allocated for this session. - buffers: Vec, - /// Queue of buffers awaiting processing. - queued_buffers: VecDeque, - /// Is the session currently streaming? - streaming: bool, -} - -impl VirtioMediaDeviceSession for EmulatedCameraSession { - fn poll_fd(&self) -> Option> { - None - } -} - -impl EmulatedCameraSession { - fn write_pattern(iteration: u64, sink: W) -> IoctlResult<()> { - let mut writer = BufWriter::new(sink); - let y = (iteration % 256) as u8; - let u = ((iteration + 64) % 256) as u8; - let v = ((iteration + 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)?; - } - Ok(()) - } - - /// Write basic pattern into the queued buffers - fn process_queued_buffers( - &mut self, - evt_queue: &mut Q, - ) -> IoctlResult<()> { - while let Some(buf_id) = self.queued_buffers.pop_front() { - let iteration = self.iteration; - let buffer = self.buffers.get_mut(buf_id).ok_or(libc::EIO)?; - buffer - .fd - .as_file() - .seek(SeekFrom::Start(0)) - .map_err(|_| libc::EIO)?; - - Self::write_pattern(iteration, buffer.fd.as_file())?; - - buffer.set_state(BufferState::Outgoing { - sequence: iteration as u32, - }); - evt_queue.send_event(V4l2Event::DequeueBuffer(DequeueBufferEvent::new( - self.id, - buffer.v4l2_buffer.clone(), - ))); - - self.iteration += 1; - } - - Ok(()) - } -} - -/// Emulated camera used for testing Android camera stack. -/// -/// This implementation looks forward to have feature parity with existing Android Guest Emulated -/// camera https://cs.android.com/android/platform/superproject/main/+/main:hardware/google/camera/devices/EmulatedCamera/ -pub struct EmulatedCamera { - /// Queue used to send events to the guest. - evt_queue: Q, - /// Host MMAP mapping manager. - mmap_manager: MmapMappingManager, - /// ID of the session with allocated buffers, if any. - /// - /// v4l2-compliance checks that only a single session can have allocated buffers at a given - /// time, since that's how actual hardware works - no two sessions can access a camera at the - /// same time. It will fails if we allow simultaneous sessions to be active, so we need this - /// artificial limitation to make it pass fully. - active_session: Option, -} - -impl EmulatedCamera -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 VirtioMediaDevice for EmulatedCamera -where - Q: VirtioMediaEventQueue, - HM: VirtioMediaHostMemoryMapper, - Reader: ReadFromDescriptorChain, - Writer: WriteToDescriptorChain, -{ - type Session = EmulatedCameraSession; - - fn new_session(&mut self, session_id: u32) -> std::result::Result { - Ok(EmulatedCameraSession { - id: session_id, - iteration: 0, - buffers: Default::default(), - queued_buffers: Default::default(), - streaming: false, - }) - } - - fn close_session(&mut self, session: Self::Session) { - // Nothing to cleanup when `close_session` is called for sessions without - // allocated buffers, hence the early return. - if self.active_session != Some(session.id) { - return; - } - - 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, - ) -> std::result::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) -> std::result::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 PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"YU12").to_u32(); const BUFFER_SIZE: u32 = WIDTH * HEIGHT * 3 / 2; const INPUTS: [bindings::v4l2_input; 1] = [bindings::v4l2_input { @@ -300,384 +35,71 @@ const INPUTS: [bindings::v4l2_input; 1] = [bindings::v4l2_input { ..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 }, - } -} - -/// Implementations of the ioctls required by a v4l2 CAPTURE device. -impl VirtioMediaIoctlHandler for EmulatedCamera -where - Q: VirtioMediaEventQueue, - HM: VirtioMediaHostMemoryMapper, -{ - type Session = EmulatedCameraSession; - - fn enum_fmt( - &mut self, - _session: &Self::Session, - queue: QueueType, - index: u32, - ) -> IoctlResult { - 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 { - 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 { - 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 { - if queue != QueueType::VideoCaptureMplane { - return Err(libc::EINVAL); - } - Ok(default_fmt(queue)) - } - - fn g_parm( - &mut self, - _session: &Self::Session, - queue: QueueType, - ) -> IoctlResult { - if queue != QueueType::VideoCaptureMplane { - return Err(libc::EINVAL); - } - - let mut parm = bindings::v4l2_streamparm { - type_: queue as u32, +pub struct MplaneFormat; + +impl CaptureDeviceFormat for MplaneFormat { + const QUEUE_TYPE: QueueType = QueueType::VideoCaptureMplane; + const PIXELFORMAT: u32 = PIXELFORMAT; + const BUFFER_SIZE: u32 = BUFFER_SIZE; + const FRAME_RATE: u32 = FRAME_RATE; + const WIDTH: u32 = WIDTH; + const HEIGHT: u32 = HEIGHT; + const INPUTS: &'static [bindings::v4l2_input] = &INPUTS; + + 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() }; - // SAFETY: The `parm` union is used for the capture type. - 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 { - if parm.type_ != QueueType::VideoCaptureMplane as u32 { - return Err(libc::EINVAL); - } - - // We just return the fixed values, ignoring what the user set. - // SAFETY: The `parm` union is used for the capture type. - 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, - ) -> IoctlResult { - if queue != QueueType::VideoCaptureMplane { - return Err(libc::EINVAL); - } - if memory != MemoryType::Mmap { - return Err(libc::EINVAL); - } - if session.streaming { - return Err(libc::EBUSY); - } - // Buffers cannot be requested on a session if there is already another session with - // allocated buffers. - match self.active_session { - Some(id) if id != session.id => return Err(libc::EBUSY), - _ => (), - } - - // Reqbufs(0) is an implicit streamoff. - if count == 0 { - self.active_session = None; - self.streamoff(session, queue)?; - } else { - // TODO factorize with streamoff. - 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() - { - // SAFETY: every buffer has at least one plane. - let mut plane = planes.next().unwrap(); - plane.set_mem_offset(offset); - *plane.length = BUFFER_SIZE; - } else { - // SAFETY: we have just set the buffer type to MMAP. Reaching this point means a bug in - // the code. - panic!() - } - v4l2_buffer.set_field(BufferField::None); - v4l2_buffer.set_flags(BufferFlags::TIMESTAMP_MONOTONIC); - - Ok(Buffer::new(v4l2_buffer, fd, offset)) - }) - }) - .collect::>()?; - - Ok(v4l2_requestbuffers { - count, + v4l2_format { type_: queue as u32, - memory: memory as u32, - capabilities: (BufferCapabilities::SUPPORTS_MMAP - | BufferCapabilities::SUPPORTS_ORPHANED_BUFS) - .bits(), - // This device does not support V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS, - // so the flags field in v4l2_requestbuffers must be 0. - flags: 0, - ..Default::default() - }) - } - - fn querybuf( - &mut self, - session: &Self::Session, - queue: QueueType, - index: u32, - ) -> IoctlResult { - if queue != QueueType::VideoCaptureMplane { - return Err(libc::EINVAL); + fmt: bindings::v4l2_format__bindgen_ty_1 { pix_mp }, } - 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>, - ) -> IoctlResult { - let host_buffer = session - .buffers - .get_mut(buffer.index() as usize) - .ok_or(libc::EINVAL)?; - // Attempt to queue already queued buffer. - 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); + fn write_pattern(iteration: u64, sink: W) -> IoctlResult<()> { + let mut writer = BufWriter::new(sink); + let y = (iteration % 256) as u8; + let u = ((iteration + 64) % 256) as u8; + let v = ((iteration + 128) % 256) as u8; + for _ in 0..(WIDTH * HEIGHT) { + writer.write_all(&[y]).map_err(|_| libc::EIO)?; } - 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); + for _ in 0..(WIDTH * HEIGHT / 4) { + writer.write_all(&[u]).map_err(|_| libc::EIO)?; } - session.streaming = false; - session.queued_buffers.clear(); - for buffer in session.buffers.iter_mut() { - buffer.set_state(BufferState::New); + for _ in 0..(WIDTH * HEIGHT / 4) { + writer.write_all(&[v]).map_err(|_| libc::EIO)?; } - Ok(()) } - fn g_input(&mut self, _session: &Self::Session) -> IoctlResult { - Ok(0) - } - - fn s_input(&mut self, _session: &mut Self::Session, input: i32) -> IoctlResult { - if input != 0 { Err(libc::EINVAL) } else { Ok(0) } - } - - fn enuminput( - &mut self, - _session: &Self::Session, - index: u32, - ) -> IoctlResult { - INPUTS.get(index as usize).map(|&x| x).ok_or(libc::EINVAL) - } - - fn enum_framesizes( - &mut self, - _session: &Self::Session, - index: u32, - pixel_format: u32, - ) -> IoctlResult { - 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 { - 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() - }) - } } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/main.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/main.rs index 5ad797021df..8acf640a5bf 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/main.rs +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_mplane/src/main.rs @@ -12,62 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; - use clap::Parser; -use log::error; -use thiserror::Error; +use std::sync::{Arc, RwLock}; use vhost_user_backend::VhostUserDaemon; use vhu_media::VhuMediaBackend; +use vhu_media::cli::{CmdLineArgs, Config, Error, init_logging, Result}; use virtio_media::protocol::VirtioMediaDeviceConfig; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; mod device; -#[derive(Debug, Error)] -pub(crate) enum Error { - #[error("Could not create daemon: {0}")] - CouldNotCreateDaemon(vhost_user_backend::Error), - #[error("Fatal error: {0}")] - ServeFailed(vhost_user_backend::Error), -} - -type Result = std::result::Result; - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct CmdLineArgs { - /// Location of vhost-user Unix domain socket. - #[clap(short, long, value_name = "SOCKET")] - socket_path: PathBuf, - /// Log verbosity, one of Off, Error, Warning, Info, Debug, Trace. - #[clap(short, long, default_value_t = log::LevelFilter::Debug)] - verbosity: log::LevelFilter, -} - -#[derive(PartialEq, Debug)] -struct Config { - socket_path: PathBuf, -} - -impl TryFrom for Config { - type Error = Error; - - fn try_from(args: CmdLineArgs) -> Result { - Ok(Config { - socket_path: args.socket_path, - }) - } -} - -fn init_logging(verbosity: log::LevelFilter) -> Result<()> { - env_logger::builder() - .format_timestamp_secs() - .filter_level(verbosity) - .init(); - Ok(()) -} const VFL_TYPE_VIDEO: u32 = 0; @@ -87,7 +41,12 @@ fn start_backend(config: Config) -> Result<()> { }; let backend = Arc::new(RwLock::new(VhuMediaBackend::new( config, - |event_queue, host_mapper| crate::device::EmulatedCamera::new(event_queue, host_mapper), + |event_queue, host_mapper| { + vhu_media::devices::EmulatedCamera::<_, _, crate::device::MplaneFormat>::new( + event_queue, + host_mapper, + ) + }, ))); let mut daemon = VhostUserDaemon::new( String::from("vhost-user-media-backend"), diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/BUILD.bazel index 282b42d3518..62a43336bcf 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/BUILD.bazel @@ -20,7 +20,6 @@ rust_binary( edition = "2024", deps = crate_deps([ "clap", - "env_logger", "image", "libc", "log", diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/Cargo.toml index 6dc6a097341..a14aad12157 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/Cargo.toml +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] clap = { workspace = true } -env_logger = { workspace = true } image = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/device.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/device.rs index 912229166fa..19b31dc1b36 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/device.rs +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/device.rs @@ -12,136 +12,62 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::VecDeque; use std::io::BufWriter; use std::io::Cursor; -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 image::ColorType; use image::codecs::jpeg::JpegEncoder; use v4l2r::PixelFormat; use v4l2r::QueueType; 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::V4l2Buffer; -use v4l2r::ioctl::V4l2PlanesWithBackingMut; -use v4l2r::memory::MemoryType; -use virtio_media::VirtioMediaDevice; -use virtio_media::VirtioMediaDeviceSession; -use virtio_media::VirtioMediaEventQueue; -use virtio_media::VirtioMediaHostMemoryMapper; -use virtio_media::io::ReadFromDescriptorChain; -use virtio_media::io::WriteToDescriptorChain; +use vhu_media::devices::CaptureDeviceFormat; use virtio_media::ioctl::IoctlResult; -use virtio_media::ioctl::VirtioMediaIoctlHandler; -use virtio_media::ioctl::virtio_media_dispatch_ioctl; -use virtio_media::memfd::MemFdBuffer; -use virtio_media::mmap::MmapMappingManager; -use virtio_media::protocol::DequeueBufferEvent; -use virtio_media::protocol::SgEntry; -use virtio_media::protocol::V4l2Event; -use virtio_media::protocol::V4l2Ioctl; -use virtio_media::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW; -/// Current status of a buffer. -#[derive(Debug, PartialEq, Eq)] -enum BufferState { - /// Buffer has just been created (or streamed off) and not been used yet. - New, - /// Buffer has been QBUF'd by the driver but not yet processed. - Incoming, - /// Buffer has been processed and is ready for dequeue. - Outgoing { - /// Sequence of the generated frame. - sequence: u32, - }, -} - -/// Information about a single buffer. -struct Buffer { - /// Current state of the buffer. - state: BufferState, - /// V4L2 representation of this buffer to be sent to the guest when requested. - v4l2_buffer: V4l2Buffer, - /// Backing storage for the buffer. - fd: MemFdBuffer, - /// Offset that can be used to map the buffer. - /// - /// Cached from `v4l2_buffer` to avoid doing a match. - offset: u32, -} +const WIDTH: u32 = 640; +const HEIGHT: u32 = 480; +const FRAME_RATE: u32 = 30; +const BYTES_PER_LINE: u32 = WIDTH * 3; +const PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"MJPG").to_u32(); +const BUFFER_SIZE: u32 = 9040; -impl Buffer { - fn new(v4l2_buffer: V4l2Buffer, fd: MemFdBuffer, offset: u32) -> Self { - Self { - state: BufferState::New, - v4l2_buffer, - fd, - offset, - } - } +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 unset_flag(flags: &mut BufferFlags, v: BufferFlags) { - *flags &= !v; - } +pub struct SplaneFormat; + +impl CaptureDeviceFormat for SplaneFormat { + const QUEUE_TYPE: QueueType = QueueType::VideoCapture; + const PIXELFORMAT: u32 = PIXELFORMAT; + const BUFFER_SIZE: u32 = BUFFER_SIZE; + const FRAME_RATE: u32 = FRAME_RATE; + const WIDTH: u32 = WIDTH; + const HEIGHT: u32 = HEIGHT; + const INPUTS: &'static [bindings::v4l2_input] = &INPUTS; + + fn default_fmt(queue: QueueType) -> v4l2_format { + let pix = bindings::v4l2_pix_format { + width: WIDTH, + height: HEIGHT, + pixelformat: PIXELFORMAT, + field: bindings::v4l2_field_V4L2_FIELD_NONE, + bytesperline: BYTES_PER_LINE, + sizeimage: BUFFER_SIZE, + colorspace: bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB, + ..Default::default() + }; - /// Update the state of the buffer as well as its V4L2 representation. - 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; - Self::unset_flag(&mut flags, BufferFlags::QUEUED); - } - BufferState::Incoming => { - *self.v4l2_buffer.get_first_plane_mut().bytesused = 0; - flags |= BufferFlags::QUEUED; - } - BufferState::Outgoing { sequence } => { - 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, - }); - Self::unset_flag(&mut flags, BufferFlags::QUEUED); - } + v4l2_format { + type_: queue as u32, + fmt: bindings::v4l2_format__bindgen_ty_1 { pix }, } - self.v4l2_buffer.set_flags(flags); - self.state = state; - } -} - -/// Session data of [`EmulatedCamera`]. -pub struct EmulatedCameraSession { - /// Id of the session. - id: u32, - /// Current iteration of the pattern generation cycle. - iteration: u64, - /// Buffers currently allocated for this session. - buffers: Vec, - /// FIFO of queued buffers awaiting processing. - queued_buffers: VecDeque, - /// Is the session currently streaming? - streaming: bool, -} - -impl VirtioMediaDeviceSession for EmulatedCameraSession { - fn poll_fd(&self) -> Option> { - None } -} -impl EmulatedCameraSession { fn write_pattern(iteration: u64, sink: W) -> IoctlResult<()> { let buffer_size: u32 = BYTES_PER_LINE * HEIGHT; let mut rgb_buffer: Vec = vec![0; buffer_size as usize]; @@ -169,518 +95,4 @@ impl EmulatedCameraSession { Ok(()) } - /// Write basic pattern into the queued buffers - fn process_queued_buffers( - &mut self, - evt_queue: &mut Q, - ) -> IoctlResult<()> { - while let Some(buf_id) = self.queued_buffers.pop_front() { - let iteration = self.iteration; - let buffer = self.buffers.get_mut(buf_id).ok_or(libc::EIO)?; - buffer - .fd - .as_file() - .seek(SeekFrom::Start(0)) - .map_err(|_| libc::EIO)?; - - Self::write_pattern(iteration, buffer.fd.as_file())?; - - *buffer.v4l2_buffer.get_first_plane_mut().bytesused = BUFFER_SIZE; - buffer.set_state(BufferState::Outgoing { - sequence: iteration as u32, - }); - evt_queue.send_event(V4l2Event::DequeueBuffer(DequeueBufferEvent::new( - self.id, - buffer.v4l2_buffer.clone(), - ))); - - self.iteration += 1; - } - - Ok(()) - } -} - -/// Emulated camera used for testing Android camera stack. -/// -/// This implementation looks forward to have feature parity with existing Android Guest Emulated -/// camera https://cs.android.com/android/platform/superproject/main/+/main:hardware/google/camera/devices/EmulatedCamera/ -pub struct EmulatedCamera { - /// Queue used to send events to the guest. - evt_queue: Q, - /// Host MMAP mapping manager. - mmap_manager: MmapMappingManager, - /// ID of the session with allocated buffers, if any. - /// - /// v4l2-compliance checks that only a single session can have allocated buffers at a given - /// time, since that's how actual hardware works - no two sessions can access a camera at the - /// same time. It will fails if we allow simultaneous sessions to be active, so we need this - /// artificial limitation to make it pass fully. - active_session: Option, -} - -impl EmulatedCamera -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 VirtioMediaDevice for EmulatedCamera -where - Q: VirtioMediaEventQueue, - HM: VirtioMediaHostMemoryMapper, - Reader: ReadFromDescriptorChain, - Writer: WriteToDescriptorChain, -{ - type Session = EmulatedCameraSession; - - fn new_session(&mut self, session_id: u32) -> std::result::Result { - Ok(EmulatedCameraSession { - id: session_id, - iteration: 0, - buffers: Default::default(), - queued_buffers: Default::default(), - streaming: false, - }) - } - - fn close_session(&mut self, session: Self::Session) { - // Nothing to cleanup when `close_session` is called for sessions without - // allocated buffers, hence the early return. - if self.active_session != Some(session.id) { - return; - } - - 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, - ) -> std::result::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) -> std::result::Result<(), i32> { - self.mmap_manager - .remove_mapping(guest_addr) - .map(|_| ()) - .map_err(|_| libc::EINVAL) - } -} - -const FRAME_RATE: u32 = 30; - -const WIDTH: u32 = 640; -const HEIGHT: u32 = 480; -const BYTES_PER_LINE: u32 = WIDTH * 3; - -const PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"MJPG").to_u32(); -const BUFFER_SIZE: u32 = 9040; - -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 = bindings::v4l2_pix_format { - width: WIDTH, - height: HEIGHT, - pixelformat: PIXELFORMAT, - field: bindings::v4l2_field_V4L2_FIELD_NONE, - bytesperline: BYTES_PER_LINE, - sizeimage: BUFFER_SIZE, - colorspace: bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB, - ..Default::default() - }; - - v4l2_format { - type_: queue as u32, - fmt: bindings::v4l2_format__bindgen_ty_1 { pix }, - } -} - -/// Implementations of the ioctls required by a v4l2 CAPTURE device. -impl VirtioMediaIoctlHandler for EmulatedCamera -where - Q: VirtioMediaEventQueue, - HM: VirtioMediaHostMemoryMapper, -{ - type Session = EmulatedCameraSession; - - fn enum_fmt( - &mut self, - _session: &Self::Session, - queue: QueueType, - index: u32, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - 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 { - if queue != QueueType::VideoCapture { - return Err(libc::EINVAL); - } - - Ok(default_fmt(queue)) - } - - fn s_fmt( - &mut self, - _session: &mut Self::Session, - queue: QueueType, - _format: v4l2_format, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - return Err(libc::EINVAL); - } - - Ok(default_fmt(queue)) - } - - fn try_fmt( - &mut self, - _session: &Self::Session, - queue: QueueType, - _format: v4l2_format, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - return Err(libc::EINVAL); - } - - Ok(default_fmt(queue)) - } - - fn enum_framesizes( - &mut self, - _session: &Self::Session, - index: u32, - pixel_format: u32, - ) -> IoctlResult { - if index as usize > 0 { - return Err(libc::EINVAL); - } - if pixel_format != PIXELFORMAT { - 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 { - if index > 0 { - return Err(libc::EINVAL); - } - if pixel_format != PIXELFORMAT { - return Err(libc::EINVAL); - } - if width != WIDTH || height != HEIGHT { - 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() - }) - } - - fn g_parm( - &mut self, - _session: &Self::Session, - queue: QueueType, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - return Err(libc::EINVAL); - }; - - let mut parm = bindings::v4l2_streamparm { - type_: queue as u32, - ..Default::default() - }; - - // SAFETY: The `parm` union is used for the capture type. - 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 { - if parm.type_ != QueueType::VideoCapture as u32 { - return Err(libc::EINVAL); - } - - // We just return the fixed values, ignoring what the user set. - // SAFETY: The `parm` union is used for the capture type. - 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, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - return Err(libc::EINVAL); - } - if memory != MemoryType::Mmap { - return Err(libc::EINVAL); - } - if session.streaming { - return Err(libc::EBUSY); - } - // Buffers cannot be requested on a session if there is already another session with - // allocated buffers. - match self.active_session { - Some(id) if id != session.id => return Err(libc::EBUSY), - _ => (), - } - - // Reqbufs(0) is an implicit streamoff. - if count == 0 { - self.active_session = None; - self.streamoff(session, queue)?; - } else { - // TODO factorize with streamoff. - 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(QueueType::VideoCapture, i, MemoryType::Mmap); - if let V4l2PlanesWithBackingMut::Mmap(mut planes) = - v4l2_buffer.planes_with_backing_iter_mut() - { - // SAFETY: every buffer has at least one plane. - let mut plane = planes.next().unwrap(); - plane.set_mem_offset(offset); - *plane.length = BUFFER_SIZE; - } else { - // SAFETY: we have just set the buffer type to MMAP. Reaching this point means a bug in - // the code. - panic!() - } - v4l2_buffer.set_field(BufferField::None); - v4l2_buffer.set_flags(BufferFlags::TIMESTAMP_MONOTONIC); - - Ok(Buffer::new(v4l2_buffer, fd, offset)) - }) - }) - .collect::>()?; - - Ok(v4l2_requestbuffers { - count, - type_: queue as u32, - memory: memory as u32, - capabilities: (BufferCapabilities::SUPPORTS_MMAP - | BufferCapabilities::SUPPORTS_ORPHANED_BUFS) - .bits(), - ..Default::default() - }) - } - - fn querybuf( - &mut self, - session: &Self::Session, - queue: QueueType, - index: u32, - ) -> IoctlResult { - if queue != QueueType::VideoCapture { - 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>, - ) -> IoctlResult { - let host_buffer = session - .buffers - .get_mut(buffer.index() as usize) - .ok_or(libc::EINVAL)?; - // Attempt to queue already queued buffer. - 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::VideoCapture || 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::VideoCapture { - 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 { - Ok(0) - } - - fn s_input(&mut self, _session: &mut Self::Session, input: i32) -> IoctlResult { - if input != 0 { Err(libc::EINVAL) } else { Ok(0) } - } - - fn enuminput( - &mut self, - _session: &Self::Session, - index: u32, - ) -> IoctlResult { - match INPUTS.get(index as usize) { - Some(&input) => Ok(input), - None => Err(libc::EINVAL), - } - } } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/main.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/main.rs index 8819545fd31..ff46a5e8e0a 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/main.rs +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/emulated_camera_splane/src/main.rs @@ -12,54 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; - use clap::Parser; +use std::sync::{Arc, RwLock}; use vhost_user_backend::VhostUserDaemon; use vhu_media::VhuMediaBackend; -use vhu_media::cli::Error; +use vhu_media::cli::{CmdLineArgs, Config, Error, init_logging, Result}; use virtio_media::protocol::VirtioMediaDeviceConfig; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; mod device; -type Result = std::result::Result; - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct CmdLineArgs { - /// Location of vhost-user Unix domain socket. - #[clap(short, long, value_name = "SOCKET")] - socket_path: PathBuf, - /// Log verbosity, one of Off, Error, Warning, Info, Debug, Trace. - #[clap(short, long, default_value_t = log::LevelFilter::Debug)] - verbosity: log::LevelFilter, -} - -#[derive(PartialEq, Debug)] -struct Config { - socket_path: PathBuf, -} - -impl TryFrom for Config { - type Error = Error; - - fn try_from(args: CmdLineArgs) -> Result { - Ok(Config { - socket_path: args.socket_path, - }) - } -} - -fn init_logging(verbosity: log::LevelFilter) -> Result<()> { - env_logger::builder() - .format_timestamp_secs() - .filter_level(verbosity) - .init(); - Ok(()) -} - const VFL_TYPE_VIDEO: u32 = 0; fn start_backend(config: Config) -> Result<()> { @@ -78,7 +40,12 @@ fn start_backend(config: Config) -> Result<()> { }; let backend = Arc::new(RwLock::new(VhuMediaBackend::new( config, - |event_queue, host_mapper| crate::device::EmulatedCamera::new(event_queue, host_mapper), + |event_queue, host_mapper| { + vhu_media::devices::EmulatedCamera::<_, _, crate::device::SplaneFormat>::new( + event_queue, + host_mapper, + ) + }, ))); let mut daemon = VhostUserDaemon::new( String::from("vhost-user-media-backend"), diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel index 7ce5a0b3ea5..82e76fe25e8 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/BUILD.bazel @@ -5,12 +5,19 @@ package(default_visibility = ["//visibility:public"]) rust_library( name = "vhu_media", - srcs = ["src/lib.rs"], + srcs = [ + "src/cli.rs", + "src/devices.rs", + "src/lib.rs", + ], edition = "2024", deps = crate_deps([ + "clap", + "env_logger", "libc", "log", "thiserror", + "v4l2r", "vhost", "vhost-user-backend", "virtio-bindings", diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml index 21b4a1b78e4..12733dd7ae5 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/Cargo.toml @@ -4,9 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] +clap = { workspace = true } +env_logger = { workspace = true } libc = { workspace = true } log = { workspace = true } thiserror = { workspace = true } +v4l2r = { workspace = true } vhost = { workspace = true } vhost-user-backend = { workspace = true } virtio-bindings = { workspace = true } diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/cli.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/cli.rs new file mode 100644 index 00000000000..cd292141669 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/cli.rs @@ -0,0 +1,62 @@ +// Copyright 2026, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::Parser; +use log::error; +use std::path::PathBuf; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum Error { + #[error("Could not create daemon: {0}")] + CouldNotCreateDaemon(vhost_user_backend::Error), + #[error("Fatal error: {0}")] + ServeFailed(vhost_user_backend::Error), +} + +pub type Result = std::result::Result; + +#[derive(PartialEq, Debug)] +pub struct Config { + pub socket_path: PathBuf, +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct CmdLineArgs { + /// Location of vhost-user Unix domain socket. + #[clap(short, long, value_name = "SOCKET")] + pub socket_path: PathBuf, + /// Log verbosity, one of Off, Error, Warning, Info, Debug, Trace. + #[clap(short, long, default_value_t = log::LevelFilter::Debug)] + pub verbosity: log::LevelFilter, +} + +impl TryFrom for Config { + type Error = Error; + + fn try_from(args: CmdLineArgs) -> Result { + Ok(Config { + socket_path: args.socket_path, + }) + } +} + +pub fn init_logging(verbosity: log::LevelFilter) -> Result<()> { + env_logger::builder() + .format_timestamp_secs() + .filter_level(verbosity) + .init(); + Ok(()) +} diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/devices.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/devices.rs new file mode 100644 index 00000000000..426a6f6b6b6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/devices.rs @@ -0,0 +1,631 @@ +// Copyright 2026, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::VecDeque; +use std::io::Seek; +use std::os::fd::AsFd; +use std::os::fd::BorrowedFd; +use v4l2r::QueueType; +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::V4l2Buffer; +use v4l2r::ioctl::V4l2PlanesWithBackingMut; +use v4l2r::memory::MemoryType; +use virtio_media::VirtioMediaDevice; +use virtio_media::VirtioMediaDeviceSession; +use virtio_media::VirtioMediaEventQueue; +use virtio_media::VirtioMediaHostMemoryMapper; +use virtio_media::io::ReadFromDescriptorChain; +use virtio_media::io::WriteToDescriptorChain; +use virtio_media::ioctl::IoctlResult; +use virtio_media::ioctl::VirtioMediaIoctlHandler; +use virtio_media::ioctl::virtio_media_dispatch_ioctl; +use virtio_media::memfd::MemFdBuffer; +use virtio_media::mmap::MmapMappingManager; +use virtio_media::protocol::DequeueBufferEvent; +use virtio_media::protocol::SgEntry; +use virtio_media::protocol::V4l2Event; +use virtio_media::protocol::V4l2Ioctl; +use virtio_media::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW; + +pub trait CaptureDeviceFormat: Send + Sync + 'static { + const QUEUE_TYPE: QueueType; + const PIXELFORMAT: u32; + const BUFFER_SIZE: u32; + const FRAME_RATE: u32; + const INPUTS: &'static [bindings::v4l2_input]; + const WIDTH: u32; + const HEIGHT: u32; + + fn default_fmt(queue: QueueType) -> v4l2_format; + fn write_pattern(iteration: u64, sink: W) -> IoctlResult<()>; + + fn enum_framesizes(index: u32, pixel_format: u32) -> IoctlResult { + if pixel_format != Self::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: Self::WIDTH, + height: Self::HEIGHT, + }, + }, + ..Default::default() + }) + } + + fn enum_frameintervals( + index: u32, + pixel_format: u32, + width: u32, + height: u32, + ) -> IoctlResult { + if pixel_format != Self::PIXELFORMAT { + return Err(libc::EINVAL); + } + if width != Self::WIDTH || height != Self::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: Self::FRAME_RATE, + }, + }, + ..Default::default() + }) + } +} + +/// Current status of a buffer. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum BufferState { + /// Buffer has just been created (or streamed off) and not been used yet. + New, + /// Buffer has been QBUF'd by the driver but not yet processed. + Incoming, + /// Buffer has been processed and is ready for dequeue. + Outgoing { + /// Sequence of the generated frame. + sequence: u32, + }, +} + +/// Information about a single buffer. +pub struct Buffer { + /// Current state of the buffer. + pub state: BufferState, + /// V4L2 representation of this buffer to be sent to the guest when requested. + pub v4l2_buffer: V4l2Buffer, + /// Backing storage for the buffer. + pub fd: MemFdBuffer, + /// Offset that can be used to map the buffer. + /// + /// Cached from `v4l2_buffer` to avoid doing a match. + pub offset: u32, +} + +impl Buffer { + pub fn new(v4l2_buffer: V4l2Buffer, fd: MemFdBuffer, offset: u32) -> Self { + Self { + state: BufferState::New, + v4l2_buffer, + fd, + offset, + } + } + + /// Update the state of the buffer as well as its V4L2 representation. + pub fn set_state(&mut self, state: BufferState, buffer_size: u32) { + 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 fn set_plane_offset_and_length(v4l2_buffer: &mut V4l2Buffer, offset: u32, buffer_size: u32) { + 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 { + // SAFETY: we have just set the buffer type to MMAP. Reaching this point means a bug in + // the code. + panic!() + } +} + +/// Session data of [`EmulatedCamera`]. +pub struct EmulatedCameraSession { + /// Id of the session. + id: u32, + /// Current iteration of the pattern generation cycle. + iteration: u64, + /// Buffers currently allocated for this session. + buffers: Vec, + /// Queue of buffers awaiting processing. + queued_buffers: VecDeque, + /// Is the session currently streaming? + streaming: bool, + _phantom: std::marker::PhantomData, +} + +impl VirtioMediaDeviceSession for EmulatedCameraSession { + fn poll_fd(&self) -> Option> { + None + } +} + +impl EmulatedCameraSession { + /// Write basic pattern into the queued buffers + fn process_queued_buffers( + &mut self, + evt_queue: &mut Q, + ) -> IoctlResult<()> { + while let Some(buf_id) = self.queued_buffers.pop_front() { + let iteration = self.iteration; + let buffer = self.buffers.get_mut(buf_id).ok_or(libc::EIO)?; + buffer + .fd + .as_file() + .seek(std::io::SeekFrom::Start(0)) + .map_err(|_| libc::EIO)?; + + F::write_pattern(iteration, buffer.fd.as_file())?; + + buffer.set_state( + BufferState::Outgoing { + sequence: iteration as u32, + }, + F::BUFFER_SIZE, + ); + evt_queue.send_event(V4l2Event::DequeueBuffer(DequeueBufferEvent::new( + self.id, + buffer.v4l2_buffer.clone(), + ))); + + self.iteration += 1; + } + + Ok(()) + } +} + +/// Emulated camera used for testing Android camera stack. +pub struct EmulatedCamera< + Q: VirtioMediaEventQueue, + HM: VirtioMediaHostMemoryMapper, + F: CaptureDeviceFormat, +> { + /// Queue used to send events to the guest. + evt_queue: Q, + /// Host MMAP mapping manager. + mmap_manager: MmapMappingManager, + /// ID of the session with allocated buffers, if any. + active_session: Option, + _phantom: std::marker::PhantomData, +} + +impl EmulatedCamera +where + Q: VirtioMediaEventQueue, + HM: VirtioMediaHostMemoryMapper, + F: CaptureDeviceFormat, +{ + pub fn new(evt_queue: Q, mapper: HM) -> Self { + Self { + evt_queue, + mmap_manager: MmapMappingManager::from(mapper), + active_session: None, + _phantom: std::marker::PhantomData, + } + } +} + +impl VirtioMediaDevice for EmulatedCamera +where + Q: VirtioMediaEventQueue, + HM: VirtioMediaHostMemoryMapper, + F: CaptureDeviceFormat, + Reader: ReadFromDescriptorChain, + Writer: WriteToDescriptorChain, +{ + type Session = EmulatedCameraSession; + + fn new_session(&mut self, session_id: u32) -> std::result::Result { + Ok(EmulatedCameraSession { + id: session_id, + iteration: 0, + buffers: Default::default(), + queued_buffers: Default::default(), + streaming: false, + _phantom: std::marker::PhantomData, + }) + } + + fn close_session(&mut self, session: Self::Session) { + if self.active_session != Some(session.id) { + return; + } + + 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, + ) -> std::io::Result<()> { + virtio_media_dispatch_ioctl(self, session, ioctl, reader, writer) + } + + fn do_mmap( + &mut self, + session: &mut Self::Session, + flags: u32, + offset: u32, + ) -> std::result::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) -> std::result::Result<(), i32> { + self.mmap_manager + .remove_mapping(guest_addr) + .map(|_| ()) + .map_err(|_| libc::EINVAL) + } +} + +/// Implementations of the ioctls required by a v4l2 CAPTURE device. +impl VirtioMediaIoctlHandler for EmulatedCamera +where + Q: VirtioMediaEventQueue, + HM: VirtioMediaHostMemoryMapper, + F: CaptureDeviceFormat, +{ + type Session = EmulatedCameraSession; + + fn enum_fmt( + &mut self, + _session: &Self::Session, + queue: QueueType, + index: u32, + ) -> IoctlResult { + if queue != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + if index > 0 { + return Err(libc::EINVAL); + } + + Ok(v4l2_fmtdesc { + index: 0, + type_: queue as u32, + pixelformat: F::PIXELFORMAT, + ..Default::default() + }) + } + + fn g_fmt(&mut self, _session: &Self::Session, queue: QueueType) -> IoctlResult { + if queue != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + Ok(F::default_fmt(queue)) + } + + fn s_fmt( + &mut self, + _session: &mut Self::Session, + queue: QueueType, + _format: v4l2_format, + ) -> IoctlResult { + if queue != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + Ok(F::default_fmt(queue)) + } + + fn try_fmt( + &mut self, + _session: &Self::Session, + queue: QueueType, + _format: v4l2_format, + ) -> IoctlResult { + if queue != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + Ok(F::default_fmt(queue)) + } + + fn g_parm( + &mut self, + _session: &Self::Session, + queue: QueueType, + ) -> IoctlResult { + if queue != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + + let mut parm = bindings::v4l2_streamparm { + type_: queue as u32, + ..Default::default() + }; + + // SAFETY: The `parm` union is used for the capture type. + let capture = unsafe { &mut parm.parm.capture }; + capture.capability = bindings::V4L2_CAP_TIMEPERFRAME; + capture.timeperframe = bindings::v4l2_fract { + numerator: 1, + denominator: F::FRAME_RATE, + }; + + Ok(parm) + } + + fn s_parm( + &mut self, + _session: &mut Self::Session, + mut parm: bindings::v4l2_streamparm, + ) -> IoctlResult { + if parm.type_ != F::QUEUE_TYPE as u32 { + return Err(libc::EINVAL); + } + + // We just return the fixed values, ignoring what the user set. + // SAFETY: The `parm` union is used for the capture type. + let capture = unsafe { &mut parm.parm.capture }; + capture.capability = bindings::V4L2_CAP_TIMEPERFRAME; + capture.timeperframe = bindings::v4l2_fract { + numerator: 1, + denominator: F::FRAME_RATE, + }; + + Ok(parm) + } + + fn reqbufs( + &mut self, + session: &mut Self::Session, + queue: QueueType, + memory: MemoryType, + count: u32, + ) -> IoctlResult { + if queue != F::QUEUE_TYPE { + 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 { + // TODO factorize with streamoff. + session.queued_buffers.clear(); + for buffer in session.buffers.iter_mut() { + buffer.set_state(BufferState::New, F::BUFFER_SIZE); + } + 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(F::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, F::BUFFER_SIZE) + .map_err(|_| libc::EINVAL)?; + + let mut v4l2_buffer = V4l2Buffer::new(queue, i, MemoryType::Mmap); + set_plane_offset_and_length(&mut v4l2_buffer, offset, F::BUFFER_SIZE); + v4l2_buffer.set_field(BufferField::None); + v4l2_buffer.set_flags(BufferFlags::TIMESTAMP_MONOTONIC); + + Ok(Buffer::new(v4l2_buffer, fd, offset)) + }) + }) + .collect::>()?; + + 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 { + if queue != F::QUEUE_TYPE { + 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>, + ) -> IoctlResult { + 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, F::BUFFER_SIZE); + 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 != F::QUEUE_TYPE || 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 != F::QUEUE_TYPE { + return Err(libc::EINVAL); + } + session.streaming = false; + session.queued_buffers.clear(); + for buffer in session.buffers.iter_mut() { + buffer.set_state(BufferState::New, F::BUFFER_SIZE); + } + + Ok(()) + } + + fn g_input(&mut self, _session: &Self::Session) -> IoctlResult { + Ok(0) + } + + fn s_input(&mut self, _session: &mut Self::Session, input: i32) -> IoctlResult { + if input != 0 { Err(libc::EINVAL) } else { Ok(0) } + } + + fn enuminput( + &mut self, + _session: &Self::Session, + index: u32, + ) -> IoctlResult { + F::INPUTS.get(index as usize).map(|&x| x).ok_or(libc::EINVAL) + } + + fn enum_framesizes( + &mut self, + _session: &Self::Session, + index: u32, + pixel_format: u32, + ) -> IoctlResult { + F::enum_framesizes(index, pixel_format) + } + + fn enum_frameintervals( + &mut self, + _session: &Self::Session, + index: u32, + pixel_format: u32, + width: u32, + height: u32, + ) -> IoctlResult { + F::enum_frameintervals(index, pixel_format, width, height) + } +} + + diff --git a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs index 45becbaa92e..9504c34376b 100644 --- a/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs +++ b/base/cvd/cuttlefish/host/commands/vhost_user_media/vhu_media/src/lib.rs @@ -41,17 +41,8 @@ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::event::new_event_consumer_and_notifier; use vmm_sys_util::event::{EventConsumer, EventFlag, EventNotifier}; -pub mod cli { - use thiserror::Error as ThisError; - - #[derive(Debug, ThisError)] - pub enum Error { - #[error("Could not create daemon: {0}")] - CouldNotCreateDaemon(vhost_user_backend::Error), - #[error("Fatal error: {0}")] - ServeFailed(vhost_user_backend::Error), - } -} +pub mod cli; +pub mod devices; #[derive(Debug, ThisError)] /// Errors related to vhost-user-media daemon.