cros_codecs/
lib.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This crate provides tools to help decode and encode various video codecs, leveraging the
6//! hardware acceleration available on the target.
7//!
8//! The [codec] module contains tools to parse encoded video streams like H.264 or VP9 and extract
9//! the information useful in order to perform e.g. hardware-accelerated decoding.
10//!
11//! The [backend] module contains common backend code. A backend is a provider of some way to
12//! decode or encode a particular codec, like VAAPI.
13//!
14//! The [decoder] module contains decoders that can turn an encoded video stream into a sequence of
15//! decoded frames using the hardware acceleration available on the host.
16//!
17//! The [encoder] module contains encoder that can turn a picture sequence into a compressed
18//! sequence of decodable encoded packets using the hardware acceleration available on the host.
19//!
20//! The [utils] module contains some useful code that is shared between different parts of this
21//! crate and didn't fit any of the modules above.
22
23pub mod backend;
24pub mod codec;
25pub mod decoder;
26pub mod encoder;
27pub mod utils;
28
29use std::str::FromStr;
30
31use byteorder::ByteOrder;
32use byteorder::LittleEndian;
33#[cfg(feature = "vaapi")]
34pub use libva;
35#[cfg(feature = "v4l2")]
36pub use v4l2r;
37
38/// Rounding modes for `Resolution`
39#[derive(Copy, Clone, Debug, PartialEq, Eq)]
40pub enum ResolutionRoundMode {
41    /// Rounds component-wise to the next even value.
42    Even,
43}
44
45/// A frame resolution in pixels.
46#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
47pub struct Resolution {
48    pub width: u32,
49    pub height: u32,
50}
51
52impl Resolution {
53    /// Whether `self` can contain `other`.
54    pub fn can_contain(&self, other: Self) -> bool {
55        self.width >= other.width && self.height >= other.height
56    }
57
58    /// Rounds `self` according to `rnd_mode`.
59    pub fn round(mut self, rnd_mode: ResolutionRoundMode) -> Self {
60        match rnd_mode {
61            ResolutionRoundMode::Even => {
62                if self.width % 2 != 0 {
63                    self.width += 1;
64                }
65
66                if self.height % 2 != 0 {
67                    self.height += 1;
68                }
69            }
70        }
71
72        self
73    }
74}
75
76impl From<(u32, u32)> for Resolution {
77    fn from(value: (u32, u32)) -> Self {
78        Self {
79            width: value.0,
80            height: value.1,
81        }
82    }
83}
84
85impl From<Resolution> for (u32, u32) {
86    fn from(value: Resolution) -> Self {
87        (value.width, value.height)
88    }
89}
90
91/// Wrapper around u32 when they are meant to be a fourcc.
92///
93/// Provides conversion and display/debug implementations useful when dealing with fourcc codes.
94#[derive(Clone, Copy, PartialEq)]
95pub struct Fourcc(u32);
96
97impl From<u32> for Fourcc {
98    fn from(fourcc: u32) -> Self {
99        Self(fourcc)
100    }
101}
102
103impl From<Fourcc> for u32 {
104    fn from(fourcc: Fourcc) -> Self {
105        fourcc.0
106    }
107}
108
109impl From<&[u8; 4]> for Fourcc {
110    fn from(n: &[u8; 4]) -> Self {
111        Self(n[0] as u32 | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24)
112    }
113}
114
115impl From<Fourcc> for [u8; 4] {
116    fn from(n: Fourcc) -> Self {
117        [
118            n.0 as u8,
119            (n.0 >> 8) as u8,
120            (n.0 >> 16) as u8,
121            (n.0 >> 24) as u8,
122        ]
123    }
124}
125
126impl std::fmt::Display for Fourcc {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        let c: [u8; 4] = (*self).into();
129
130        f.write_fmt(format_args!(
131            "{}{}{}{}",
132            c[0] as char, c[1] as char, c[2] as char, c[3] as char
133        ))
134    }
135}
136
137impl std::fmt::Debug for Fourcc {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        f.write_fmt(format_args!("0x{:08x} ({})", self.0, self))
140    }
141}
142
143/// Formats that buffers can be mapped into for the CPU to read.
144///
145/// The conventions here largely follow these of libyuv.
146#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
147pub enum DecodedFormat {
148    /// Y, U and V planes, 4:2:0 sampling, 8 bits per sample.
149    I420,
150    /// One Y and one interleaved UV plane, 4:2:0 sampling, 8 bits per sample.
151    NV12,
152    /// Y, U and V planes, 4:2:2 sampling, 8 bits per sample.
153    I422,
154    /// Y, U and V planes, 4:4:4 sampling, 8 bits per sample.
155    I444,
156    /// Y, U and V planes, 4:2:0 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
157    I010,
158    /// Y, U and V planes, 4:2:0 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
159    I012,
160    /// Y, U and V planes, 4:2:2 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
161    I210,
162    /// Y, U and V planes, 4:2:2 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
163    I212,
164    /// Y, U and V planes, 4:4:4 sampling, 16 bits per sample, LE. Only the 10 LSBs are used.
165    I410,
166    /// Y, U and V planes, 4:4:4 sampling, 16 bits per sample, LE. Only the 12 LSBs are used.
167    I412,
168}
169
170impl FromStr for DecodedFormat {
171    type Err = &'static str;
172
173    fn from_str(s: &str) -> Result<Self, Self::Err> {
174        match s {
175            "i420" | "I420" => Ok(DecodedFormat::I420),
176            "i422" | "I422" => Ok(DecodedFormat::I422),
177            "i444" | "I444" => Ok(DecodedFormat::I444),
178            "nv12" | "NV12" => Ok(DecodedFormat::NV12),
179            "i010" | "I010" => Ok(DecodedFormat::I010),
180            "i012" | "I012" => Ok(DecodedFormat::I012),
181            "i210" | "I210" => Ok(DecodedFormat::I210),
182            "i212" | "I212" => Ok(DecodedFormat::I212),
183            "i410" | "I410" => Ok(DecodedFormat::I410),
184            "i412" | "I412" => Ok(DecodedFormat::I412),
185            _ => {
186                Err("unrecognized output format. Valid values: i420, nv12, i422, i444, i010, i012, i210, i212, i410, i412")
187            }
188        }
189    }
190}
191
192/// Describes the layout of a plane within a frame.
193#[derive(Debug, Clone, PartialEq)]
194pub struct PlaneLayout {
195    /// Index of the memory buffer the plane belongs to.
196    pub buffer_index: usize,
197    /// Start offset of the plane within its buffer.
198    pub offset: usize,
199    /// Distance in bytes between two lines of data in this plane.
200    pub stride: usize,
201}
202
203/// Unambiguously describes the layout of a frame.
204///
205/// A frame can be made of one or several memory buffers, each containing one or several planes.
206/// For a given frame, this structure defines where each plane can be found.
207#[derive(Debug, Clone, PartialEq)]
208pub struct FrameLayout {
209    /// `(Fourcc, modifier)` tuple describing the arrangement of the planes.
210    ///
211    /// This member is enough to infer how many planes and buffers the frame has, and which
212    /// buffer each plane belongs into.
213    pub format: (Fourcc, u64),
214    /// Size in pixels of the frame.
215    pub size: Resolution,
216    /// Layout of each individual plane.
217    pub planes: Vec<PlaneLayout>,
218}
219
220/// Build a frame memory descriptor enum that supports multiple descriptor types.
221///
222/// This is useful for the case where the frames' memory backing is not decided at compile-time.
223/// In this case, this macro can be used to list all the potential types supported at run-time, and
224/// the selected one can be built as the program is run.
225///
226/// # Example
227///
228/// ```
229/// use cros_codecs::multiple_desc_type;
230/// use cros_codecs::utils::DmabufFrame;
231///
232/// /// Frames' memory can be provided either by the backend, or via PRIME DMABUF handles.
233/// multiple_desc_type! {
234///     enum OwnedOrDmaDescriptor {
235///         Owned(()),
236///         Dmabuf(DmabufFrame),
237///     }
238/// }
239/// ```
240#[macro_export]
241macro_rules! multiple_desc_type {
242    (enum $s:ident { $($v:ident($t:ty),)* } ) => {
243        enum $s {
244            $($v($t),)*
245        }
246
247        #[cfg(feature = "vaapi")]
248        impl libva::SurfaceMemoryDescriptor for $s {
249            fn add_attrs(&mut self, attrs: &mut Vec<libva::VASurfaceAttrib>) -> Option<Box<dyn std::any::Any>> {
250                match self {
251                    $($s::$v(desc) => desc.add_attrs(attrs),)*
252                }
253            }
254        }
255    }
256}
257
258/// Copies `src` into `dst` as NV12, removing any extra padding.
259pub fn nv12_copy(
260    src: &[u8],
261    dst: &mut [u8],
262    width: usize,
263    height: usize,
264    strides: [usize; 3],
265    offsets: [usize; 3],
266) {
267    // Copy Y.
268    let src_y_lines = src[offsets[0]..]
269        .chunks(strides[0])
270        .map(|line| &line[..width]);
271    let dst_y_lines = dst.chunks_mut(width);
272
273    for (src_line, dst_line) in src_y_lines.zip(dst_y_lines).take(height) {
274        dst_line.copy_from_slice(src_line);
275    }
276
277    let dst_u_offset = width * height;
278
279    // Align width and height to 2 for UV plane.
280    // 1 sample per 4 pixels, but we have two components per line so width can remain as-is.
281    let uv_width = if width % 2 == 1 { width + 1 } else { width };
282    let uv_height = if height % 2 == 1 { height + 1 } else { height } / 2;
283
284    // Copy UV.
285    let src_uv_lines = src[offsets[1]..]
286        .chunks(strides[1])
287        .map(|line| &line[..uv_width]);
288    let dst_uv_lines = dst[dst_u_offset..].chunks_mut(uv_width);
289    for (src_line, dst_line) in src_uv_lines.zip(dst_uv_lines).take(uv_height) {
290        dst_line.copy_from_slice(src_line);
291    }
292}
293
294/// Copies `src` into `dst` as I4xx (YUV tri-planar).
295///
296/// This function does not change the data layout beyond removing any padding in the source, i.e.
297/// both `src` and `dst` are 3-planar YUV buffers.
298///
299/// `strides` and `offsets` give the stride and starting position of each plane in `src`. In `dst`
300/// each plane will be put sequentially one after the other.
301///
302/// `sub_h` and `sub_v` enable horizontal and vertical sub-sampling, respectively. E.g, if both
303/// `sub_h` and `sub_v` are `true` the data will be `4:2:0`, if only `sub_v` is `true` then it will be
304/// `4:2:2`, and if both are `false` then we have `4:4:4`.
305pub fn i4xx_copy(
306    src: &[u8],
307    dst: &mut [u8],
308    width: usize,
309    height: usize,
310    strides: [usize; 3],
311    offsets: [usize; 3],
312    (sub_h, sub_v): (bool, bool),
313) {
314    // Align width and height of UV planes to 2 if sub-sampling is used.
315    let uv_width = if sub_h { (width + 1) / 2 } else { width };
316    let uv_height = if sub_v { (height + 1) / 2 } else { height };
317
318    let dst_y_size = width * height;
319    let dst_u_size = uv_width * uv_height;
320    let (dst_y_plane, dst_uv_planes) = dst.split_at_mut(dst_y_size);
321    let (dst_u_plane, dst_v_plane) = dst_uv_planes.split_at_mut(dst_u_size);
322
323    // Copy Y.
324    let src_y_lines = src[offsets[0]..]
325        .chunks(strides[0])
326        .map(|line| &line[..width]);
327    let dst_y_lines = dst_y_plane.chunks_mut(width);
328    for (src_line, dst_line) in src_y_lines.zip(dst_y_lines).take(height) {
329        dst_line.copy_from_slice(src_line);
330    }
331
332    // Copy U.
333    let src_u_lines = src[offsets[1]..]
334        .chunks(strides[1])
335        .map(|line| &line[..uv_width]);
336    let dst_u_lines = dst_u_plane.chunks_mut(uv_width);
337    for (src_line, dst_line) in src_u_lines.zip(dst_u_lines).take(uv_height) {
338        dst_line.copy_from_slice(src_line);
339    }
340
341    // Copy V.
342    let src_v_lines = src[offsets[2]..]
343        .chunks(strides[2])
344        .map(|line| &line[..uv_width]);
345    let dst_v_lines = dst_v_plane.chunks_mut(uv_width);
346    for (src_line, dst_line) in src_v_lines.zip(dst_v_lines).take(uv_height) {
347        dst_line.copy_from_slice(src_line);
348    }
349}
350
351/// Returns the size required to store a frame of `format` with size `width`x`height`, without any
352/// padding. This is the minimum size of the destination buffer passed to `nv12_copy` or
353/// `i420_copy`.
354pub fn decoded_frame_size(format: DecodedFormat, width: usize, height: usize) -> usize {
355    match format {
356        DecodedFormat::I420 | DecodedFormat::NV12 => {
357            let u_size = width * height;
358            // U and V planes need to be aligned to 2.
359            let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2;
360
361            u_size + uv_size
362        }
363        DecodedFormat::I422 => {
364            let u_size = width * height;
365            // U and V planes need to be aligned to 2.
366            let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2 * 2;
367
368            u_size + uv_size
369        }
370        DecodedFormat::I444 => (width * height) * 3,
371        DecodedFormat::I010 | DecodedFormat::I012 => {
372            decoded_frame_size(DecodedFormat::I420, width, height) * 2
373        }
374        DecodedFormat::I210 | DecodedFormat::I212 => {
375            let u_size = width * height * 2;
376            // U and V planes need to be aligned to 2.
377            let uv_size = ((width + 1) / 2) * ((height + 1) / 2) * 2 * 2;
378
379            u_size + uv_size
380        }
381        DecodedFormat::I410 | DecodedFormat::I412 => (width * height * 2) * 3,
382    }
383}
384
385/// Copies `src` into `dst` as I410, removing all padding and changing the layout from packed to
386/// triplanar. Also drops the alpha channel.
387fn y410_to_i410(
388    src: &[u8],
389    dst: &mut [u8],
390    width: usize,
391    height: usize,
392    strides: [usize; 3],
393    offsets: [usize; 3],
394) {
395    let src_lines = src[offsets[0]..]
396        .chunks(strides[0])
397        .map(|line| &line[..width * 4]);
398
399    let dst_y_size = width * 2 * height;
400    let dst_u_size = width * 2 * height;
401
402    let (dst_y_plane, dst_uv_planes) = dst.split_at_mut(dst_y_size);
403    let (dst_u_plane, dst_v_plane) = dst_uv_planes.split_at_mut(dst_u_size);
404    let dst_y_lines = dst_y_plane.chunks_mut(width * 2);
405    let dst_u_lines = dst_u_plane.chunks_mut(width * 2);
406    let dst_v_lines = dst_v_plane.chunks_mut(width * 2);
407
408    for (src_line, (dst_y_line, (dst_u_line, dst_v_line))) in src_lines
409        .zip(dst_y_lines.zip(dst_u_lines.zip(dst_v_lines)))
410        .take(height)
411    {
412        for (src, (dst_y, (dst_u, dst_v))) in src_line.chunks(4).zip(
413            dst_y_line
414                .chunks_mut(2)
415                .zip(dst_u_line.chunks_mut(2).zip(dst_v_line.chunks_mut(2))),
416        ) {
417            let y = LittleEndian::read_u16(&[src[1] >> 2 | src[2] << 6, src[2] >> 2 & 0b11]);
418            let u = LittleEndian::read_u16(&[src[0], src[1] & 0b11]);
419            let v = LittleEndian::read_u16(&[src[2] >> 4 | src[3] << 4, src[3] >> 4 & 0b11]);
420            LittleEndian::write_u16(dst_y, y);
421            LittleEndian::write_u16(dst_u, u);
422            LittleEndian::write_u16(dst_v, v);
423        }
424    }
425}
426
427/// Instructs on whether it should block on the operation(s).
428#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
429pub enum BlockingMode {
430    Blocking,
431    #[default]
432    NonBlocking,
433}
434
435#[cfg(test)]
436mod tests {
437    use super::Fourcc;
438
439    const NV12_FOURCC: u32 = 0x3231564E;
440
441    #[test]
442    fn fourcc_u32() {
443        let fourcc = Fourcc::from(NV12_FOURCC);
444        let value: u32 = fourcc.into();
445        assert_eq!(value, NV12_FOURCC);
446    }
447
448    #[test]
449    fn fourcc_u8_4() {
450        let fourcc = Fourcc::from(NV12_FOURCC);
451        let value: [u8; 4] = fourcc.into();
452        assert_eq!(value, *b"NV12");
453    }
454
455    #[test]
456    fn fourcc_display() {
457        let fourcc = Fourcc::from(NV12_FOURCC);
458        assert_eq!(fourcc.to_string(), "NV12");
459    }
460
461    #[test]
462    fn fourcc_debug() {
463        let fourcc = Fourcc::from(NV12_FOURCC);
464        assert_eq!(format!("{:?}", fourcc), "0x3231564e (NV12)");
465    }
466}