blob: b20a60d79d204d4016b059e63462e1de3321b8dd [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This crate provides tools to help decode and encode various video codecs, leveraging the
//! hardware acceleration available on the target.
//!
//! The [codec] module contains tools to parse encoded video streams like H.264 or VP9 and extract
//! the information useful in order to perform e.g. hardware-accelerated decoding.
//!
//! The [backend] module contains common backend code. A backend is a provider of some way to
//! decode or encode a particular codec, like VAAPI.
//!
//! The [decoder] module contains decoders that can turn an encoded video stream into a sequence of
//! decoded frames using the hardware acceleration available on the host.
//!
//! The [encoder] module contains encoder that can turn a picture sequence into a compressed
//! sequence of decodable encoded packets using the hardware acceleration available on the host.
//!
//! The [utils] module contains some useful code that is shared between different parts of this
//! crate and didn't fit any of the modules above.
pub mod backend;
pub mod codec;
pub mod decoder;
pub mod encoder;
pub mod utils;
use std::str::FromStr;
use byteorder::ByteOrder;
use byteorder::LittleEndian;
#[cfg(feature = "vaapi")]
pub use libva;
#[cfg(feature = "v4l2")]
pub use v4l2r;
/// Rounding modes for `Resolution`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ResolutionRoundMode {
/// Rounds component-wise to the next even value.
Even,
}
/// A frame resolution in pixels.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Resolution {
pub width: u32,
pub height: u32,
}
impl Resolution {
/// Whether `self` can contain `other`.
pub fn can_contain(&self, other: Self) -> bool {
self.width >= other.width && self.height >= other.height
}
/// Rounds `self` according to `rnd_mode`.
pub fn round(mut self, rnd_mode: ResolutionRoundMode) -> Self {
match rnd_mode {
ResolutionRoundMode::Even => {
if self.width % 2 != 0 {
self.width += 1;
}
if self.height % 2 != 0 {
self.height += 1;
}
}
}
self
}
}
impl From<(u32, u32)> for Resolution {
fn from(value: (u32, u32)) -> Self {
Self {
width: value.0,
height: value.1,
}
}
}
impl From<Resolution> for (u32, u32) {
fn from(value: Resolution) -> Self {
(value.width, value.height)
}
}
/// Wrapper around u32 when they are meant to be a fourcc.
///
/// Provides conversion and display/debug implementations useful when dealing with fourcc codes.
#[derive(Clone, Copy, PartialEq)]
pub struct Fourcc(u32);
impl From<u32> for Fourcc {
fn from(fourcc: u32) -> Self {
Self(fourcc)
}
}
impl From<Fourcc> for u32 {
fn from(fourcc: Fourcc) -> Self {
fourcc.0
}
}
impl From<&[u8; 4]> for Fourcc {
fn from(n: &[u8; 4]) -> Self {
Self(n[0] as u32 | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24)
}
}
impl From<Fourcc> for [u8; 4] {
fn from(n: Fourcc) -> Self {
[
n.0 as u8,
(n.0 >> 8) as u8,
(n.0 >> 16) as u8,
(n.0 >> 24) as u8,
]
}
}
impl std::fmt::Display for Fourcc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let c: [u8; 4] = (*self).into();
f.write_fmt(format_args!(
"{}{}{}{}",
c[0] as char, c[1] as char, c[2] as char, c[3] as char
))
}
}
impl std::fmt::Debug for Fourcc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("0x{:08x} ({})", self.0, self))
}
}
/// Formats that buffers can be mapped into for the CPU to read.
///
/// The conventions here largely follow these of libyuv.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum DecodedFormat {
/// Y, U and V planes, 4:2:0 sampling, 8 bits per sample.
I420,
/// One Y and one interleaved UV plane, 4:2:0 sampling, 8 bits per sample.
NV12,
/// Y, U and V planes, 4:2:2 sampling, 8 bits per sample.
I422,
/// Y, U and V planes, 4:4:4 sampling, 8 bits per sample.
I444,
/// Y, U and V planes, 4:2:0 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
I010,
/// Y, U and V planes, 4:2:0 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
I012,
/// Y, U and V planes, 4:2:2 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
I210,
/// Y, U and V planes, 4:2:2 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
I212,
/// Y, U and V planes, 4:4:4 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
I410,
/// Y, U and V planes, 4:4:4 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
I412,
}
impl FromStr for DecodedFormat {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"i420" | "I420" => Ok(DecodedFormat::I420),
"i422" | "I422" => Ok(DecodedFormat::I422),
"i444" | "I444" => Ok(DecodedFormat::I444),
"nv12" | "NV12" => Ok(DecodedFormat::NV12),
"i010" | "I010" => Ok(DecodedFormat::I010),
"i012" | "I012" => Ok(DecodedFormat::I012),
"i210" | "I210" => Ok(DecodedFormat::I210),
"i212" | "I212" => Ok(DecodedFormat::I212),
"i410" | "I410" => Ok(DecodedFormat::I410),
"i412" | "I412" => Ok(DecodedFormat::I412),
_ => {
Err("unrecognized output format. Valid values: i420, nv12, i422, i444, i010, i012, i210, i212, i410, i412")
}
}
}
}
/// Describes the layout of a plane within a frame.
#[derive(Debug, Clone, PartialEq)]
pub struct PlaneLayout {
/// Index of the memory buffer the plane belongs to.
pub buffer_index: usize,
/// Start offset of the plane within its buffer.
pub offset: usize,
/// Distance in bytes between two lines of data in this plane.
pub stride: usize,
}
/// Unambiguously describes the layout of a frame.
///
/// A frame can be made of one or several memory buffers, each containing one or several planes.
/// For a given frame, this structure defines where each plane can be found.
#[derive(Debug, Clone, PartialEq)]
pub struct FrameLayout {
/// `(Fourcc, modifier)` tuple describing the arrangement of the planes.
///
/// This member is enough to infer how many planes and buffers the frame has, and which
/// buffer each plane belongs into.
pub format: (Fourcc, u64),
/// Size in pixels of the frame.
pub size: Resolution,
/// Layout of each individual plane.
pub planes: Vec<PlaneLayout>,
}
/// Build a frame memory descriptor enum that supports multiple descriptor types.
///
/// This is useful for the case where the frames' memory backing is not decided at compile-time.
/// In this case, this macro can be used to list all the potential types supported at run-time, and
/// the selected one can be built as the program is run.
///
/// # Example
///
/// ```
/// use cros_codecs::multiple_desc_type;
/// use cros_codecs::utils::DmabufFrame;
///
/// /// Frames' memory can be provided either by the backend, or via PRIME DMABUF handles.
/// multiple_desc_type! {
/// enum OwnedOrDmaDescriptor {
/// Owned(()),
/// Dmabuf(DmabufFrame),
/// }
/// }
/// ```
#[macro_export]
macro_rules! multiple_desc_type {
(enum $s:ident { $($v:ident($t:ty),)* } ) => {
enum $s {
$($v($t),)*
}
#[cfg(feature = "vaapi")]
impl libva::SurfaceMemoryDescriptor for $s {
fn add_attrs(&mut self, attrs: &mut Vec<libva::VASurfaceAttrib>) -> Option<Box<dyn std::any::Any>> {
match self {
$($s::$v(desc) => desc.add_attrs(attrs),)*
}
}
}
}
}
/// Copies `src` into `dst` as NV12, removing any extra padding.
pub fn nv12_copy(
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
strides: [usize; 3],
offsets: [usize; 3],
) {
// Copy Y.
let src_y_lines = src[offsets[0]..]
.chunks(strides[0])
.map(|line| &line[..width]);
let dst_y_lines = dst.chunks_mut(width);
for (src_line, dst_line) in src_y_lines.zip(dst_y_lines).take(height) {
dst_line.copy_from_slice(src_line);
}
let dst_u_offset = width * height;
// Align width and height to 2 for UV plane.
// 1 sample per 4 pixels, but we have two components per line so width can remain as-is.
let uv_width = if width % 2 == 1 { width + 1 } else { width };
let uv_height = if height % 2 == 1 { height + 1 } else { height } / 2;
// Copy UV.
let src_uv_lines = src[offsets[1]..]
.chunks(strides[1])
.map(|line| &line[..uv_width]);
let dst_uv_lines = dst[dst_u_offset..].chunks_mut(uv_width);
for (src_line, dst_line) in src_uv_lines.zip(dst_uv_lines).take(uv_height) {
dst_line.copy_from_slice(src_line);
}
}
/// Copies `src` into `dst` as I4xx (YUV tri-planar).
///
/// This function does not change the data layout beyond removing any padding in the source, i.e.
/// both `src` and `dst` are 3-planar YUV buffers.
///
/// `strides` and `offsets` give the stride and starting position of each plane in `src`. In `dst`
/// each plane will be put sequentially one after the other.
///
/// `sub_h` and `sub_v` enable horizontal and vertical sub-sampling, respectively. E.g, if both
/// `sub_h` and `sub_v` are `true` the data will be `4:2:0`, if only `sub_v` is `true` then it will be
/// `4:2:2`, and if both are `false` then we have `4:4:4`.
pub fn i4xx_copy(
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
strides: [usize; 3],
offsets: [usize; 3],
(sub_h, sub_v): (bool, bool),
) {
// Align width and height of UV planes to 2 if sub-sampling is used.
let uv_width = if sub_h { (width + 1) / 2 } else { width };
let uv_height = if sub_v { (height + 1) / 2 } else { height };
let dst_y_size = width * height;
let dst_u_size = uv_width * uv_height;
let (dst_y_plane, dst_uv_planes) = dst.split_at_mut(dst_y_size);
let (dst_u_plane, dst_v_plane) = dst_uv_planes.split_at_mut(dst_u_size);
// Copy Y.
let src_y_lines = src[offsets[0]..]
.chunks(strides[0])
.map(|line| &line[..width]);
let dst_y_lines = dst_y_plane.chunks_mut(width);
for (src_line, dst_line) in src_y_lines.zip(dst_y_lines).take(height) {
dst_line.copy_from_slice(src_line);
}
// Copy U.
let src_u_lines = src[offsets[1]..]
.chunks(strides[1])
.map(|line| &line[..uv_width]);
let dst_u_lines = dst_u_plane.chunks_mut(uv_width);
for (src_line, dst_line) in src_u_lines.zip(dst_u_lines).take(uv_height) {
dst_line.copy_from_slice(src_line);
}
// Copy V.
let src_v_lines = src[offsets[2]..]
.chunks(strides[2])
.map(|line| &line[..uv_width]);
let dst_v_lines = dst_v_plane.chunks_mut(uv_width);
for (src_line, dst_line) in src_v_lines.zip(dst_v_lines).take(uv_height) {
dst_line.copy_from_slice(src_line);
}
}
/// Returns the size required to store a frame of `format` with size `width`x`height`, without any
/// padding. This is the minimum size of the destination buffer passed to `nv12_copy` or
/// `i420_copy`.
pub fn decoded_frame_size(format: DecodedFormat, width: usize, height: usize) -> usize {
match format {
DecodedFormat::I420 | DecodedFormat::NV12 => {
let u_size = width * height;
// U and V planes need to be aligned to 2.
let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2;
u_size + uv_size
}
DecodedFormat::I422 => {
let u_size = width * height;
// U and V planes need to be aligned to 2.
let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2 * 2;
u_size + uv_size
}
DecodedFormat::I444 => (width * height) * 3,
DecodedFormat::I010 | DecodedFormat::I012 => {
decoded_frame_size(DecodedFormat::I420, width, height) * 2
}
DecodedFormat::I210 | DecodedFormat::I212 => {
let u_size = width * height * 2;
// U and V planes need to be aligned to 2.
let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2 * 2;
u_size + uv_size
}
DecodedFormat::I410 | DecodedFormat::I412 => (width * height * 2) * 3,
}
}
/// Copies `src` into `dst` as I410, removing all padding and changing the layout from packed to
/// triplanar. Also drops the alpha channel.
fn y410_to_i410(
src: &[u8],
dst: &mut [u8],
width: usize,
height: usize,
strides: [usize; 3],
offsets: [usize; 3],
) {
let src_lines = src[offsets[0]..]
.chunks(strides[0])
.map(|line| &line[..width * 4]);
let dst_y_size = width * 2 * height;
let dst_u_size = width * 2 * height;
let (dst_y_plane, dst_uv_planes) = dst.split_at_mut(dst_y_size);
let (dst_u_plane, dst_v_plane) = dst_uv_planes.split_at_mut(dst_u_size);
let dst_y_lines = dst_y_plane.chunks_mut(width * 2);
let dst_u_lines = dst_u_plane.chunks_mut(width * 2);
let dst_v_lines = dst_v_plane.chunks_mut(width * 2);
for (src_line, (dst_y_line, (dst_u_line, dst_v_line))) in src_lines
.zip(dst_y_lines.zip(dst_u_lines.zip(dst_v_lines)))
.take(height)
{
for (src, (dst_y, (dst_u, dst_v))) in src_line.chunks(4).zip(
dst_y_line
.chunks_mut(2)
.zip(dst_u_line.chunks_mut(2).zip(dst_v_line.chunks_mut(2))),
) {
let y = LittleEndian::read_u16(&[src[1] >> 2 | src[2] << 6, src[2] >> 2 & 0b11]);
let u = LittleEndian::read_u16(&[src[0], src[1] & 0b11]);
let v = LittleEndian::read_u16(&[src[2] >> 4 | src[3] << 4, src[3] >> 4 & 0b11]);
LittleEndian::write_u16(dst_y, y);
LittleEndian::write_u16(dst_u, u);
LittleEndian::write_u16(dst_v, v);
}
}
}
/// Instructs on whether it should block on the operation(s).
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockingMode {
Blocking,
#[default]
NonBlocking,
}
#[cfg(test)]
mod tests {
use super::Fourcc;
const NV12_FOURCC: u32 = 0x3231564E;
#[test]
fn fourcc_u32() {
let fourcc = Fourcc::from(NV12_FOURCC);
let value: u32 = fourcc.into();
assert_eq!(value, NV12_FOURCC);
}
#[test]
fn fourcc_u8_4() {
let fourcc = Fourcc::from(NV12_FOURCC);
let value: [u8; 4] = fourcc.into();
assert_eq!(value, *b"NV12");
}
#[test]
fn fourcc_display() {
let fourcc = Fourcc::from(NV12_FOURCC);
assert_eq!(fourcc.to_string(), "NV12");
}
#[test]
fn fourcc_debug() {
let fourcc = Fourcc::from(NV12_FOURCC);
assert_eq!(format!("{:?}", fourcc), "0x3231564e (NV12)");
}
}