cros-codecs 0.0.6

Hardware-accelerated codecs for Linux
Documentation
// 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<RefCell<&'a mut [u8]>>;
}

// Rust doesn't allow type aliases in traits, so we use this stupid hack to accomplish effectively
// the same thing.
pub trait Equivalent<A>: From<A> + Into<A> {
    fn from_ref(value: &A) -> &Self;
    fn into_ref(self: &Self) -> &A;
}

impl<A> Equivalent<A> 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<Surface<Self::MemDescriptor>>;

    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<DecodedFormat, String> {
        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<EncodedFormat, String> {
        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<usize> {
        let mut ret: Vec<usize> = 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<usize> {
        let mut ret: Vec<usize> = 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<usize> {
        let mut ret: Vec<usize> = 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<usize>;

    // Pitch is measured in bytes while stride is measured in pixels.
    fn get_plane_pitch(&self) -> Vec<usize>;

    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<Box<dyn ReadMapping<'a> + 'a>, String>;

    fn map_mut<'a>(&'a mut self) -> Result<Box<dyn WriteMapping<'a> + '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<Device>, format: &Format, buf: &V4l2Buffer);

    #[cfg(feature = "vaapi")]
    fn to_native_handle(&self, display: &Rc<Display>) -> Result<Self::NativeHandle, String>;
}

// 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<V: VideoFrame>(pub V);

#[cfg(feature = "v4l2")]
impl<V: VideoFrame> From<V> for V4l2VideoFrame<V> {
    fn from(value: V) -> Self {
        Self(value)
    }
}

#[cfg(feature = "v4l2")]
impl<V: VideoFrame> BufferHandles for V4l2VideoFrame<V> {
    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<V: VideoFrame> PrimitiveBufferHandles for V4l2VideoFrame<V> {
    type HandleType = V::NativeHandle;
    const MEMORY_TYPE: Self::SupportedMemoryType =
        <V::NativeHandle as PlaneHandle>::Memory::MEMORY_TYPE;
}