// Copyright 2025 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::cell::RefCell; use std::fmt::Debug; #[cfg(feature = "vaapi")] use std::rc::Rc; #[cfg(feature = "v4l2")] use std::sync::Arc; use crate::utils::align_up; use crate::DecodedFormat; use crate::EncodedFormat; use crate::Fourcc; use crate::Resolution; pub mod frame_pool; #[cfg(feature = "backend")] pub mod gbm_video_frame; #[cfg(feature = "backend")] pub mod generic_dma_video_frame; #[cfg(feature = "v4l2")] pub mod v4l2_mmap_video_frame; #[cfg(feature = "vaapi")] use libva::{Display, Surface, SurfaceMemoryDescriptor}; #[cfg(feature = "v4l2")] use v4l2r::bindings::v4l2_plane; #[cfg(feature = "v4l2")] use v4l2r::device::Device; #[cfg(feature = "v4l2")] use v4l2r::ioctl::V4l2Buffer; #[cfg(feature = "v4l2")] use v4l2r::memory::{BufferHandles, Memory, MemoryType, PlaneHandle, PrimitiveBufferHandles}; #[cfg(feature = "v4l2")] use v4l2r::Format; pub const Y_PLANE: usize = 0; pub const UV_PLANE: usize = 1; pub const U_PLANE: usize = 1; pub const V_PLANE: usize = 2; // RAII wrappers for video memory mappings. The Drop method should implement any necessary // munmap()'ing and cache flushing. pub trait ReadMapping<'a> { fn get(&self) -> Vec<&[u8]>; } pub trait WriteMapping<'a> { fn get(&self) -> Vec>; } // Rust doesn't allow type aliases in traits, so we use this stupid hack to accomplish effectively // the same thing. pub trait Equivalent: From + Into { fn from_ref(value: &A) -> &Self; fn into_ref(self: &Self) -> &A; } impl Equivalent for A { fn from_ref(value: &A) -> &Self { value } fn into_ref(self: &Self) -> &A { self } } // Unified abstraction for any kind of frame data that might be sent to the hardware. pub trait VideoFrame: Send + Sync + Sized + Debug + 'static { #[cfg(feature = "v4l2")] type NativeHandle: PlaneHandle; #[cfg(feature = "vaapi")] type MemDescriptor: SurfaceMemoryDescriptor; #[cfg(feature = "vaapi")] type NativeHandle: Equivalent>; fn fourcc(&self) -> Fourcc; // Outputs visible resolution. Use pitch and plane size for coded resolution calculations. fn resolution(&self) -> Resolution; fn is_compressed(&self) -> bool { match self.fourcc().to_string().as_str() { "H264" | "HEVC" | "VP80" | "VP90" | "AV1F" => true, _ => false, } } // Whether or not all the planes are in a contiguous memory allocation. For example, returns // true for NV12 and false for NM12. fn is_contiguous(&self) -> bool { if self.is_compressed() { return false; } // TODO: Add more formats. match self.fourcc().to_string().as_str() { "MM21" | "NM12" => false, _ => true, } } fn decoded_format(&self) -> Result { if self.is_compressed() { return Err("Cannot convert compressed format into decoded format".to_string()); } Ok(DecodedFormat::from(self.fourcc())) } fn encoded_format(&self) -> Result { if !self.is_compressed() { return Err("Cannot convert uncompressed format into encoded format".to_string()); } Ok(EncodedFormat::from(self.fourcc())) } fn num_planes(&self) -> usize { if self.is_compressed() { return 1; } match self.decoded_format().unwrap() { DecodedFormat::I420 | DecodedFormat::I422 | DecodedFormat::I444 | DecodedFormat::I010 | DecodedFormat::I012 | DecodedFormat::I210 | DecodedFormat::I212 | DecodedFormat::I410 | DecodedFormat::I412 => 3, DecodedFormat::NV12 | DecodedFormat::MM21 => 2, } } fn get_horizontal_subsampling(&self) -> Vec { let mut ret: Vec = vec![]; for plane_idx in 0..self.num_planes() { if self.is_compressed() { ret.push(1); } else { ret.push(match self.decoded_format().unwrap() { DecodedFormat::I420 | DecodedFormat::NV12 | DecodedFormat::I422 | DecodedFormat::I010 | DecodedFormat::I012 | DecodedFormat::I210 | DecodedFormat::I212 | DecodedFormat::MM21 => { if plane_idx == 0 { 1 } else { 2 } } DecodedFormat::I444 | DecodedFormat::I410 | DecodedFormat::I412 => 1, }); } } ret } fn get_vertical_subsampling(&self) -> Vec { let mut ret: Vec = vec![]; for plane_idx in 0..self.num_planes() { if self.is_compressed() { ret.push(1); } else { ret.push(match self.decoded_format().unwrap() { DecodedFormat::I420 | DecodedFormat::NV12 | DecodedFormat::I010 | DecodedFormat::I012 | DecodedFormat::MM21 => { if plane_idx == 0 { 1 } else { 2 } } DecodedFormat::I422 | DecodedFormat::I444 | DecodedFormat::I210 | DecodedFormat::I212 | DecodedFormat::I410 | DecodedFormat::I412 => 1, }) } } ret } fn get_bytes_per_element(&self) -> Vec { let mut ret: Vec = vec![]; for plane_idx in 0..self.num_planes() { if self.is_compressed() { ret.push(1); } else { ret.push(match self.decoded_format().unwrap() { DecodedFormat::I420 | DecodedFormat::I422 | DecodedFormat::I444 => 1, DecodedFormat::I010 | DecodedFormat::I012 | DecodedFormat::I210 | DecodedFormat::I212 | DecodedFormat::I410 | DecodedFormat::I412 => 2, DecodedFormat::NV12 | DecodedFormat::MM21 => { if plane_idx == 0 { 1 } else { 2 } } }) } } ret } fn get_plane_size(&self) -> Vec; // Pitch is measured in bytes while stride is measured in pixels. fn get_plane_pitch(&self) -> Vec; fn validate_frame(&self) -> Result<(), String> { if self.is_compressed() { return Ok(()); } let horizontal_subsampling = self.get_horizontal_subsampling(); let vertical_subsampling = self.get_vertical_subsampling(); let bytes_per_element = self.get_bytes_per_element(); let plane_pitch = self.get_plane_pitch(); let plane_size = self.get_plane_size(); for plane in 0..self.num_planes() { let minimum_pitch = align_up(self.resolution().width as usize, horizontal_subsampling[plane]) * bytes_per_element[plane] / horizontal_subsampling[plane]; if plane_pitch[plane] < minimum_pitch { return Err( "Pitch of plane {plane} is insufficient to accomodate format!".to_string() ); } let minimum_size = align_up(self.resolution().height as usize, vertical_subsampling[plane]) / vertical_subsampling[plane] * plane_pitch[plane]; if plane_size[plane] < minimum_size { return Err( "Size of plane {plane} is insufficient to accomodate format!".to_string() ); } } Ok(()) } fn map<'a>(&'a self) -> Result + 'a>, String>; fn map_mut<'a>(&'a mut self) -> Result + 'a>, String>; #[cfg(feature = "v4l2")] fn fill_v4l2_plane(&self, index: usize, plane: &mut v4l2_plane); #[cfg(feature = "v4l2")] fn process_dqbuf(&mut self, device: Arc, format: &Format, buf: &V4l2Buffer); #[cfg(feature = "vaapi")] fn to_native_handle(&self, display: &Rc) -> Result; } // Rust has restrictions about implementing foreign types, so this is a stupid workaround to get // VideoFrame to implement BufferHandles. #[cfg(feature = "v4l2")] #[derive(Debug)] pub struct V4l2VideoFrame(pub V); #[cfg(feature = "v4l2")] impl From for V4l2VideoFrame { fn from(value: V) -> Self { Self(value) } } #[cfg(feature = "v4l2")] impl BufferHandles for V4l2VideoFrame { type SupportedMemoryType = MemoryType; fn len(&self) -> usize { self.0.num_planes() } fn fill_v4l2_plane(&self, index: usize, plane: &mut v4l2_plane) { self.0.fill_v4l2_plane(index, plane) } } #[cfg(feature = "v4l2")] impl PrimitiveBufferHandles for V4l2VideoFrame { type HandleType = V::NativeHandle; const MEMORY_TYPE: Self::SupportedMemoryType = ::Memory::MEMORY_TYPE; }