Path: blob/main/devices/src/virtio/media/decoder_adapter.rs
5394 views
// Copyright 2024 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34//! An adapter from the virtio-media protocol to the video devices originally used with5//! virtio-video.6//!7//! This allows to reuse already-existing crosvm virtio-video devices with8//! virtio-media.910use std::os::fd::BorrowedFd;11use std::sync::Arc;1213use base::AsRawDescriptor;14use base::SharedMemory;15use base::WaitContext;16use sync::Mutex;17use virtio_media::devices::video_decoder::StreamParams;18use virtio_media::devices::video_decoder::VideoDecoderBackend;19use virtio_media::devices::video_decoder::VideoDecoderBackendEvent;20use virtio_media::devices::video_decoder::VideoDecoderBackendSession;21use virtio_media::devices::video_decoder::VideoDecoderBufferBacking;22use virtio_media::devices::video_decoder::VideoDecoderSession;23use virtio_media::ioctl::IoctlResult;24use virtio_media::v4l2r;25use virtio_media::v4l2r::bindings;26use virtio_media::v4l2r::ioctl::V4l2MplaneFormat;27use virtio_media::v4l2r::PixelFormat;28use virtio_media::v4l2r::QueueClass;29use virtio_media::v4l2r::QueueDirection;30use virtio_media::v4l2r::QueueType;3132use crate::virtio::video::decoder::backend::DecoderEvent;33use crate::virtio::video::decoder::backend::DecoderSession;34use crate::virtio::video::decoder::capability::Capability;35use crate::virtio::video::decoder::DecoderBackend;36use crate::virtio::video::format::Format;37use crate::virtio::video::format::FramePlane;38use crate::virtio::video::format::PlaneFormat;39use crate::virtio::video::resource::GuestMemArea;40use crate::virtio::video::resource::GuestMemHandle;41use crate::virtio::video::resource::GuestResource;42use crate::virtio::video::resource::GuestResourceHandle;4344/// Use 1 MB input buffers by default. This should probably be resolution-dependent.45const INPUT_BUFFER_SIZE: u32 = 1024 * 1024;4647/// Returns the V4L2 `(bytesperline, sizeimage)` for the given `format` and `coded_size`.48fn buffer_sizes_for_format(format: Format, coded_size: (u32, u32)) -> (u32, u32) {49match PlaneFormat::get_plane_layout(format, coded_size.0, coded_size.1) {50None => (0, INPUT_BUFFER_SIZE),51Some(layout) => (52layout.first().map(|p| p.stride).unwrap_or(0),53layout.iter().map(|p| p.plane_size).sum(),54),55}56}5758impl From<Format> for PixelFormat {59fn from(format: Format) -> Self {60PixelFormat::from_fourcc(match format {61Format::NV12 => b"NV12",62Format::YUV420 => b"YV12",63Format::H264 => b"H264",64Format::Hevc => b"HEVC",65Format::VP8 => b"VP80",66Format::VP9 => b"VP90",67})68}69}7071impl TryFrom<PixelFormat> for Format {72type Error = ();7374fn try_from(pixelformat: PixelFormat) -> Result<Self, Self::Error> {75match &pixelformat.to_fourcc() {76b"NV12" => Ok(Format::NV12),77b"YV12" => Ok(Format::YUV420),78b"H264" => Ok(Format::H264),79b"HEVC" => Ok(Format::Hevc),80b"VP80" => Ok(Format::VP8),81b"VP90" => Ok(Format::VP9),82_ => Err(()),83}84}85}8687pub struct VirtioVideoAdapterBuffer {88// Plane backing memory, for MMAP buffers only.89shm: Vec<SharedMemory>,90// Switched to `true` once the buffer's backing memory has been registered with91// `use_output_buffer`.92registered: bool,93}9495impl VideoDecoderBufferBacking for VirtioVideoAdapterBuffer {96fn new(queue: QueueType, index: u32, sizes: &[usize]) -> IoctlResult<Self> {97Ok(Self {98shm: sizes99.iter()100.enumerate()101.map(|(plane, size)| {102SharedMemory::new(103format!("virtio_media {:?} {}-{}", queue.direction(), index, plane),104*size as u64,105)106})107.collect::<Result<_, base::Error>>()108.map_err(|_| libc::ENOMEM)?,109registered: false,110})111}112113fn fd_for_plane(&self, plane_idx: usize) -> Option<BorrowedFd> {114self.shm.get(plane_idx).map(|shm|115// SAFETY: the shm'd fd is valid and `BorrowedFd` ensures its use won't outlive us.116unsafe { BorrowedFd::borrow_raw(shm.descriptor.as_raw_descriptor())})117}118}119120enum EosBuffer {121/// No EOS buffer queued yet and no EOS pending.122None,123/// EOS buffer is available, and no EOS pending.124Available(u32),125/// EOS is pending but we have no buffer queued yet.126Awaiting,127}128129pub struct VirtioVideoAdapterSession<D: DecoderBackend> {130/// Session ID.131id: u32,132133/// The backend session can only be created once we know the input format.134backend_session: Option<D::Session>,135136/// Proxy poller. We need this because this backend creates the actual session with a delay,137/// but the device needs the session FD as soon as it is created.138poller: WaitContext<u32>,139140/// Shared reference to the device backend. Required so we can create the session lazily.141backend: Arc<Mutex<D>>,142143input_format: Format,144output_format: Format,145/// Coded size currently set for the CAPTURE buffers.146coded_size: (u32, u32),147/// Stream parameters, as obtained from the stream itself.148stream_params: StreamParams,149150/// Whether the initial DRC event has been received. The backend will ignore CAPTURE buffers151/// queued before that.152initial_drc_received: bool,153/// Whether the `set_output_parameters` of the backend needs to be called before a CAPTURE154/// buffer is sent.155need_set_output_params: bool,156157/// Indices of CAPTURE buffers that are queued but not send to the backend.158pending_output_buffers: Vec<(u32, Option<GuestResource>)>,159160/// Index of the capture buffer we kept in order to signal EOS.161eos_capture_buffer: EosBuffer,162163/// Number of output buffers that have been allocated, as reported by the decoder device.164///165/// This is required to call `set_output_parameters` on the virtio-video session.166num_output_buffers: usize,167}168169impl<D: DecoderBackend> VirtioVideoAdapterSession<D> {170/// Get the currently existing backend session if it exists, or create it using the current171/// input format if it doesn't.172fn get_or_create_session(&mut self) -> IoctlResult<&mut D::Session> {173let backend_session = match &mut self.backend_session {174Some(session) => session,175b @ None => {176let backend_session =177self.backend178.lock()179.new_session(self.input_format)180.map_err(|e| {181base::error!(182"{:#}",183anyhow::anyhow!("while creating backend session: {:#}", e)184);185libc::EIO186})?;187self.poller188.add(backend_session.event_pipe(), self.id)189.map_err(|e| {190base::error!("failed to listen to events of session {}: {:#}", self.id, e);191libc::EIO192})?;193b.get_or_insert(backend_session)194}195};196197Ok(backend_session)198}199200fn try_send_pending_output_buffers(&mut self) -> IoctlResult<()> {201// We cannot send output buffers until the initial DRC event is received.202if !self.initial_drc_received {203return Ok(());204}205206let _ = self.get_or_create_session();207// Will always succeed before `get_or_create_session` has been called. Ideally we would use208// its result directly, but this borrows a mutable reference to `self` which would prevent209// other borrows later in this method.210let Some(backend_session) = &mut self.backend_session else {211unreachable!()212};213214// Leave early if we have no pending output buffers. This ensures we only set the215// parameters right before the first buffer is queued.216if self.pending_output_buffers.is_empty() {217return Ok(());218}219220// Set the output parameters if this is the first CAPTURE buffer we queue after a221// resolution change event.222if self.need_set_output_params {223let output_format = self.output_format;224225backend_session226.set_output_parameters(self.num_output_buffers, output_format)227.map_err(|_| libc::EIO)?;228self.need_set_output_params = false;229}230231for (index, resource) in self.pending_output_buffers.drain(..) {232match resource {233Some(resource) => backend_session234.use_output_buffer(index as i32, resource)235.map_err(|_| libc::EIO)?,236None => backend_session237.reuse_output_buffer(index as i32)238.map_err(|_| libc::EIO)?,239}240}241242Ok(())243}244}245246impl<D: DecoderBackend> VideoDecoderBackendSession for VirtioVideoAdapterSession<D> {247type BufferStorage = VirtioVideoAdapterBuffer;248249fn current_format(&self, direction: QueueDirection) -> V4l2MplaneFormat {250let format = match direction {251QueueDirection::Output => self.input_format,252QueueDirection::Capture => self.output_format,253};254let (bytesperline, sizeimage) = buffer_sizes_for_format(format, self.coded_size);255256let mut plane_fmt: [bindings::v4l2_plane_pix_format; bindings::VIDEO_MAX_PLANES as usize] =257Default::default();258plane_fmt[0] = bindings::v4l2_plane_pix_format {259bytesperline,260sizeimage,261reserved: Default::default(),262};263264let pix_mp = bindings::v4l2_pix_format_mplane {265width: self.coded_size.0,266height: self.coded_size.1,267pixelformat: PixelFormat::from(format).to_u32(),268field: bindings::v4l2_field_V4L2_FIELD_NONE,269plane_fmt,270num_planes: 1,271flags: 0,272..Default::default()273};274275V4l2MplaneFormat::from((direction, pix_mp))276}277278fn stream_params(&self) -> StreamParams {279self.stream_params.clone()280}281282fn drain(&mut self) -> IoctlResult<()> {283if let Some(backend_session) = &mut self.backend_session {284backend_session.flush().map_err(|_| libc::EIO)285} else {286Ok(())287}288}289290fn clear_output_buffers(&mut self) -> IoctlResult<()> {291self.pending_output_buffers.clear();292self.eos_capture_buffer = EosBuffer::None;293if let Some(session) = &mut self.backend_session {294session.clear_output_buffers().map_err(|_| libc::EIO)295} else {296Ok(())297}298}299300fn next_event(&mut self) -> Option<VideoDecoderBackendEvent> {301if let Some(backend_session) = &mut self.backend_session {302let event = backend_session.read_event().unwrap();303304match event {305DecoderEvent::NotifyEndOfBitstreamBuffer(id) => {306Some(VideoDecoderBackendEvent::InputBufferDone {307buffer_id: id,308error: 0,309})310}311DecoderEvent::ProvidePictureBuffers {312min_num_buffers,313width,314height,315visible_rect,316} => {317self.stream_params = StreamParams {318// Add one extra buffer to keep one on the side in order to signal EOS.319min_output_buffers: min_num_buffers + 1,320coded_size: (width as u32, height as u32),321visible_rect: v4l2r::Rect {322left: visible_rect.left,323top: visible_rect.top,324width: visible_rect.right.saturating_sub(visible_rect.left) as u32,325height: visible_rect.bottom.saturating_sub(visible_rect.top) as u32,326},327};328self.coded_size = self.stream_params.coded_size;329self.need_set_output_params = true;330self.initial_drc_received = true;331332// We can queue pending picture buffers now.333if self.try_send_pending_output_buffers().is_err() {334base::error!(335"failed to send pending output buffers after resolution change event"336);337}338339Some(VideoDecoderBackendEvent::StreamFormatChanged)340}341DecoderEvent::PictureReady {342picture_buffer_id,343timestamp,344..345} => {346let timestamp = bindings::timeval {347tv_sec: (timestamp / 1_000_000) as i64,348tv_usec: (timestamp % 1_000_000) as i64,349};350let bytes_used = buffer_sizes_for_format(self.output_format, self.coded_size).1;351352Some(VideoDecoderBackendEvent::FrameCompleted {353buffer_id: picture_buffer_id as u32,354timestamp,355bytes_used: vec![bytes_used],356is_last: false,357})358}359DecoderEvent::FlushCompleted(res) => {360if let Err(err) = res {361base::error!("flush command failed: {:#}", err);362}363364// Regardless of the result, the flush has completed.365// Use the buffer we kept aside for signaling EOS, or wait until the next366// buffer is queued so we can use it for that purpose.367match self.eos_capture_buffer {368EosBuffer::Available(buffer_id) => {369Some(VideoDecoderBackendEvent::FrameCompleted {370buffer_id,371timestamp: Default::default(),372bytes_used: vec![0],373is_last: true,374})375}376_ => {377self.eos_capture_buffer = EosBuffer::Awaiting;378None379}380}381}382DecoderEvent::ResetCompleted(_) => todo!(),383DecoderEvent::NotifyError(_) => todo!(),384}385} else {386None387}388}389390fn buffers_allocated(&mut self, direction: QueueDirection, num_buffers: u32) {391if matches!(direction, QueueDirection::Capture) {392self.num_output_buffers = num_buffers as usize;393}394}395396fn poll_fd(&self) -> Option<BorrowedFd> {397// SAFETY: safe because WaitContext has a valid FD and BorrowedFd ensures its use doesn't398// outlive us.399Some(unsafe { BorrowedFd::borrow_raw(self.poller.as_raw_descriptor()) })400}401402fn decode(403&mut self,404input: &Self::BufferStorage,405index: u32,406timestamp: bindings::timeval,407bytes_used: u32,408) -> IoctlResult<()> {409let backend_session = self.get_or_create_session()?;410let timestamp = timestamp.tv_sec as u64 * 1_000_000 + timestamp.tv_usec as u64;411let resource = GuestResourceHandle::GuestPages(GuestMemHandle {412desc: input.shm[0]413.descriptor414.try_clone()415.map_err(|_| libc::ENOMEM)?,416mem_areas: vec![GuestMemArea {417offset: 0,418length: input.shm[0].size as usize,419}],420});421422backend_session423.decode(index, timestamp, resource, 0, bytes_used)424.map_err(|_| libc::EIO)425}426427fn use_as_output(&mut self, index: u32, backing: &mut Self::BufferStorage) -> IoctlResult<()> {428match self.eos_capture_buffer {429EosBuffer::None => {430self.eos_capture_buffer = EosBuffer::Available(index);431Ok(())432}433EosBuffer::Awaiting => {434// TODO: mmm how do we do that?435// Answer: we don't! We just return a buffer event with the LAST flag set, and the436// higher-level event handler takes care of sending eos!437//438// ... and how do we trigger the event for that buffer?439//440// self.send_eos(self, v4l2_buffer.index());441Ok(())442}443EosBuffer::Available(_) => {444let resource = if !backing.registered {445let resource = GuestResourceHandle::GuestPages(GuestMemHandle {446desc: backing.shm[0]447.descriptor448.try_clone()449.map_err(|_| libc::ENOMEM)?,450mem_areas: vec![GuestMemArea {451offset: 0,452length: backing.shm[0].size as usize,453}],454});455456let plane_formats = PlaneFormat::get_plane_layout(457self.output_format,458self.coded_size.0,459self.coded_size.1,460)461.ok_or_else(|| {462base::error!("could not obtain plane layout for output buffer");463libc::EINVAL464})?;465466let mut buffer_offset = 0;467let resource_handle = GuestResource {468handle: resource,469planes: plane_formats470.into_iter()471.map(|p| {472let plane_offset = buffer_offset;473buffer_offset += p.plane_size;474475FramePlane {476offset: plane_offset as usize,477stride: p.stride as usize,478size: p.plane_size as usize,479}480})481.collect(),482width: self.coded_size.0,483height: self.coded_size.1,484format: self.output_format,485guest_cpu_mappable: false,486};487488backing.registered = true;489490Some(resource_handle)491} else {492None493};494495self.pending_output_buffers.push((index, resource));496497self.try_send_pending_output_buffers()498}499}500}501}502503pub struct VirtioVideoAdapter<D: DecoderBackend> {504backend: Arc<Mutex<D>>,505capability: Capability,506}507508impl<D: DecoderBackend> VirtioVideoAdapter<D> {509pub fn new(backend: D) -> Self {510let capability = backend.get_capabilities();511Self {512backend: Arc::new(Mutex::new(backend)),513capability,514}515}516}517518impl<D: DecoderBackend> VideoDecoderBackend for VirtioVideoAdapter<D> {519type Session = VirtioVideoAdapterSession<D>;520521fn new_session(&mut self, id: u32) -> IoctlResult<Self::Session> {522let first_input_format = self523.capability524.input_formats()525.first()526.ok_or(libc::ENODEV)?;527let first_output_format = self528.capability529.output_formats()530.first()531.ok_or(libc::ENODEV)?;532533let coded_size = first_input_format534.frame_formats535.first()536.map(|f| (f.width.min, f.height.min))537.unwrap_or((0, 0));538539Ok(VirtioVideoAdapterSession {540id,541backend_session: None,542poller: WaitContext::new().map_err(|_| libc::EIO)?,543backend: Arc::clone(&self.backend),544input_format: first_input_format.format,545coded_size,546output_format: first_output_format.format,547stream_params: StreamParams {548min_output_buffers: 1,549coded_size,550visible_rect: v4l2r::Rect {551left: 0,552top: 0,553width: coded_size.0,554height: coded_size.1,555},556},557initial_drc_received: false,558need_set_output_params: false,559pending_output_buffers: Default::default(),560num_output_buffers: 0,561eos_capture_buffer: EosBuffer::None,562})563}564565fn close_session(&mut self, _session: Self::Session) {}566567fn enum_formats(568&self,569_session: &VideoDecoderSession<Self::Session>,570direction: QueueDirection,571index: u32,572) -> Option<bindings::v4l2_fmtdesc> {573let formats = match direction {574QueueDirection::Output => self.capability.input_formats(),575QueueDirection::Capture => self.capability.output_formats(),576};577let fmt = formats.get(index as usize)?;578579Some(bindings::v4l2_fmtdesc {580index,581type_: QueueType::from_dir_and_class(direction, QueueClass::VideoMplane) as u32,582pixelformat: PixelFormat::from(fmt.format).to_u32(),583..Default::default()584})585}586587fn frame_sizes(&self, pixel_format: u32) -> Option<bindings::v4l2_frmsize_stepwise> {588let format = Format::try_from(PixelFormat::from_u32(pixel_format)).ok()?;589let frame_sizes = self590.capability591.input_formats()592.iter()593.chain(self.capability.output_formats().iter())594.find(|f| f.format == format)595.and_then(|f| f.frame_formats.first())?;596597Some(bindings::v4l2_frmsize_stepwise {598min_width: frame_sizes.width.min,599max_width: frame_sizes.width.max,600step_width: frame_sizes.width.step,601min_height: frame_sizes.height.min,602max_height: frame_sizes.height.max,603step_height: frame_sizes.height.step,604})605}606607fn adjust_format(608&self,609session: &Self::Session,610direction: QueueDirection,611format: V4l2MplaneFormat,612) -> V4l2MplaneFormat {613let pix_mp: &bindings::v4l2_pix_format_mplane = format.as_ref();614615let available_formats = match direction {616QueueDirection::Output => self.capability.input_formats(),617QueueDirection::Capture => self.capability.output_formats(),618};619620let pixel_format = Format::try_from(PixelFormat::from_u32(pix_mp.pixelformat)).ok();621622// If the received pixel format is valid, find the format in our capabilities that matches,623// otherwise fall back to the first format.624let Some(matching_format) = pixel_format625.and_then(|format| available_formats.iter().find(|f| f.format == format))626.or_else(|| available_formats.first())627else {628// There are no formats defined at all for the device, so return a bogus one like a629// V4L2 device would do.630return V4l2MplaneFormat::from((direction, Default::default()));631};632633// Now check that the requested resolution is within the supported range, or fallback to634// an arbitrary one.635let (mut width, mut height) = matching_format636.frame_formats637.iter()638.find(|format| {639let width = pix_mp.width;640let height = pix_mp.height;641(format.width.min..format.width.max).contains(&width)642&& (format.height.min..format.height.max).contains(&height)643})644.map(|_| (pix_mp.width, pix_mp.height))645.unwrap_or_else(|| {646matching_format647.frame_formats648.first()649.map(|format| {650(651std::cmp::min(format.width.max, pix_mp.width),652(std::cmp::min(format.height.max, pix_mp.height)),653)654})655.unwrap_or((0, 0))656});657658// CAPTURE resolution cannot be lower than OUTPUT one.659if direction == QueueDirection::Capture {660width = std::cmp::max(width, session.coded_size.0);661height = std::cmp::max(height, session.coded_size.1);662}663664// We only support one plane per buffer for now.665let num_planes = 1;666let (bytesperline, sizeimage) =667buffer_sizes_for_format(matching_format.format, (width, height));668let mut plane_fmt: [bindings::v4l2_plane_pix_format; 8] = Default::default();669plane_fmt[0] = bindings::v4l2_plane_pix_format {670bytesperline,671sizeimage,672reserved: Default::default(),673};674675V4l2MplaneFormat::from((676direction,677bindings::v4l2_pix_format_mplane {678width,679height,680pixelformat: PixelFormat::from(matching_format.format).to_u32(),681field: bindings::v4l2_field_V4L2_FIELD_NONE,682plane_fmt,683num_planes,684flags: 0,685..Default::default()686},687))688}689690fn apply_format(691&self,692session: &mut Self::Session,693direction: QueueDirection,694format: &V4l2MplaneFormat,695) {696match direction {697QueueDirection::Output => {698session.input_format =699Format::try_from(format.pixelformat()).unwrap_or_else(|_| {700self.capability701.input_formats()702.first()703.map(|f| f.format)704.unwrap_or(Format::H264)705});706let coded_size = format.size();707// Setting the resolution manually affects the stream parameters.708if coded_size != (0, 0) {709let coded_size = format.size();710session.coded_size = coded_size;711}712}713QueueDirection::Capture => {714session.output_format =715Format::try_from(format.pixelformat()).unwrap_or_else(|_| {716self.capability717.output_formats()718.first()719.map(|f| f.format)720.unwrap_or(Format::NV12)721});722}723}724}725}726727728