cros_codecs/
encoder.rs

1// Copyright 2024 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
5use thiserror::Error;
6
7pub mod av1;
8pub mod h264;
9pub mod h265;
10pub mod vp8;
11pub mod vp9;
12
13pub mod stateful;
14pub mod stateless;
15
16use crate::codec::av1::synthesizer::SynthesizerError as AV1SynthesizerError;
17use crate::codec::h264::synthesizer::SynthesizerError as H264SynthesizerError;
18use crate::encoder::stateful::StatefulBackendError;
19use crate::encoder::stateless::StatelessBackendError;
20use crate::FrameLayout;
21
22/// Specifies the encoder operation
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum RateControl {
25    /// The encoder shall maintain the constant bitrate
26    ConstantBitrate(u64),
27
28    /// The encoder shall maintain codec specific quality parameter constant (eg. QP for H.264)
29    /// disregarding bitrate.
30    ConstantQuality(u32),
31}
32
33impl RateControl {
34    pub(crate) fn is_same_variant(left: &Self, right: &Self) -> bool {
35        std::mem::discriminant(left) == std::mem::discriminant(right)
36    }
37
38    pub(crate) fn bitrate_target(&self) -> Option<u64> {
39        match self {
40            RateControl::ConstantBitrate(target) => Some(*target),
41            RateControl::ConstantQuality(_) => None,
42        }
43    }
44}
45
46#[derive(Clone)]
47pub enum PredictionStructure {
48    /// Simplest prediction structure, suitable eg. for RTC. Interframe is produced at the start of
49    /// the stream and every time when `limit` frames are reached. Following interframe frames
50    /// are frames relying solely on the last frame.
51    LowDelay { limit: u16 },
52}
53
54/// Dynamic parameters of the encoded stream that client may choose to change during the encoding
55/// session without recreating the entire encoder instance.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct Tunings {
58    /// The stream's [`RateControl`]
59    pub rate_control: RateControl,
60    /// Stream framerate in frames per second
61    pub framerate: u32,
62    /// Minimum value of codec specific quality parameter constant (eg. QP for H.264)
63    pub min_quality: u32,
64    /// Maximum value of codec specific quality parameter constant (eg. QP for H.264)
65    pub max_quality: u32,
66}
67
68impl Default for Tunings {
69    fn default() -> Self {
70        Self {
71            rate_control: RateControl::ConstantBitrate(200_000),
72            framerate: 30,
73            min_quality: 0,
74            max_quality: u32::MAX,
75        }
76    }
77}
78
79/// Encoder's input metadata
80#[derive(Debug, Clone)]
81pub struct FrameMetadata {
82    pub timestamp: u64,
83    pub layout: FrameLayout,
84    pub force_keyframe: bool,
85}
86
87/// Encoder's coded output with contained frame.
88pub struct CodedBitstreamBuffer {
89    /// [`FrameMetadata`] of the frame that is compressed in [`Self::bitstream`]
90    pub metadata: FrameMetadata,
91
92    /// Bitstream with compressed frame together with optionally other compressed control messages
93    pub bitstream: Vec<u8>,
94}
95
96impl CodedBitstreamBuffer {
97    pub fn new(metadata: FrameMetadata, bitstream: Vec<u8>) -> Self {
98        Self {
99            metadata,
100            bitstream,
101        }
102    }
103}
104
105impl From<CodedBitstreamBuffer> for Vec<u8> {
106    fn from(value: CodedBitstreamBuffer) -> Self {
107        value.bitstream
108    }
109}
110
111#[derive(Error, Debug)]
112pub enum EncodeError {
113    #[error("unsupported")]
114    Unsupported,
115    #[error("invalid internal state. This is likely a bug.")]
116    InvalidInternalState,
117    #[error(transparent)]
118    StatelessBackendError(#[from] StatelessBackendError),
119    #[error(transparent)]
120    StatefulBackendError(#[from] StatefulBackendError),
121    #[error(transparent)]
122    H264SynthesizerError(#[from] H264SynthesizerError),
123    #[error(transparent)]
124    AV1SynthesizerError(#[from] AV1SynthesizerError),
125}
126
127pub type EncodeResult<T> = Result<T, EncodeError>;
128
129/// Generic video encoder interface.
130pub trait VideoEncoder<Handle> {
131    /// Changes dynamic parameters (aka [`Tunings`]) of the encoded stream. The change may not be
132    /// effective right away. Depending on the used prediction structure, the `Predictor` may
133    /// choose to delay the change until entire or a some part of the structure had been encoded.
134    ///
135    /// Note: Currently changing the variant of [`RateControl`] is not supported.
136    fn tune(&mut self, tunings: Tunings) -> EncodeResult<()>;
137
138    /// Enqueues the frame for encoding. The implementation will drop the handle after it is no
139    /// longer be needed. The encoder is not required to immediately start processing the frame
140    /// and yield output bitstream. It is allowed to hold frames until certain conditions are met
141    /// eg. for specified prediction structures or referencing in order to further optimize
142    /// the compression rate of the bitstream.
143    fn encode(&mut self, meta: FrameMetadata, handle: Handle) -> Result<(), EncodeError>;
144
145    /// Drains the encoder. This means that encoder is required to finish processing of all the
146    /// frames in the internal queue and yield output bitstream by the end of the call. The output
147    /// bitstream then can be polled using [`Self::poll`] function.
148    ///
149    /// Drain does not enforce the flush of the internal state, ie. the enqueued frame handles
150    /// do not have to be returned to user (dropped) and key frame is not enforced on the next
151    /// frame.
152    fn drain(&mut self) -> EncodeResult<()>;
153
154    /// Polls on the encoder for the available output bitstream with compressed frames that where
155    /// submitted with [`Self::encode`].
156    ///
157    /// The call may also trigger a further processing aside of returning output. Therefore it
158    /// *recommended* that this function is called frequently.
159    fn poll(&mut self) -> EncodeResult<Option<CodedBitstreamBuffer>>;
160}
161
162pub fn simple_encode_loop<H>(
163    encoder: &mut impl VideoEncoder<H>,
164    frame_producer: &mut impl Iterator<Item = (FrameMetadata, H)>,
165    mut coded_consumer: impl FnMut(CodedBitstreamBuffer),
166) -> EncodeResult<()> {
167    for (meta, handle) in frame_producer.by_ref() {
168        encoder.encode(meta, handle)?;
169        while let Some(coded) = encoder.poll()? {
170            coded_consumer(coded);
171        }
172    }
173
174    encoder.drain()?;
175    while let Some(coded) = encoder.poll()? {
176        coded_consumer(coded);
177    }
178
179    Ok(())
180}
181
182#[cfg(test)]
183pub(crate) mod tests {
184    #[cfg(feature = "v4l2")]
185    use crate::encoder::FrameMetadata;
186    #[cfg(feature = "v4l2")]
187    use crate::utils::UserPtrFrame;
188    #[cfg(feature = "v4l2")]
189    use crate::Fourcc;
190    #[cfg(feature = "v4l2")]
191    use crate::FrameLayout;
192
193    pub fn get_test_frame_t(ts: u64, max_ts: u64) -> f32 {
194        2.0 * std::f32::consts::PI * (ts as f32) / (max_ts as f32)
195    }
196
197    pub fn gen_test_frame<F>(frame_width: usize, frame_height: usize, t: f32, mut set_pix: F)
198    where
199        F: FnMut(usize, usize, [f32; 3]),
200    {
201        let width = frame_width as f32;
202        let height = frame_height as f32;
203        let (sin, cos) = f32::sin_cos(t);
204        let (sin2, cos2) = (sin.powi(2), cos.powi(2));
205
206        // Pick the dot position
207        let dot_col = height * (1.1 + 2.0 * sin * cos) / 2.2;
208        let dot_row = width * (1.1 + sin) / 2.2;
209        let dot_size2 = (width.min(height) * 0.05).powi(2);
210
211        // Luma
212        for frame_row in 0..frame_height {
213            #[allow(clippy::needless_range_loop)]
214            for frame_col in 0..frame_width {
215                let row = frame_row as f32;
216                let col = frame_col as f32;
217
218                let dist = (dot_col - col).powi(2) + (dot_row - row).powi(2);
219
220                let y = if dist < dot_size2 {
221                    0.0
222                } else {
223                    (row + col) / (width + height)
224                };
225
226                let (u, v) = if dist < dot_size2 {
227                    (0.5, 0.5)
228                } else {
229                    ((row / width) * sin2, (col / height) * cos2)
230                };
231
232                set_pix(frame_col, frame_row, [y, u, v]);
233            }
234        }
235    }
236
237    pub fn fill_test_frame_nm12(
238        width: usize,
239        height: usize,
240        strides: [usize; 2],
241        t: f32,
242        y_plane: &mut [u8],
243        uv_plane: &mut [u8],
244    ) {
245        gen_test_frame(width, height, t, |col, row, yuv| {
246            /// Maximum value of color component for NV12
247            const MAX_COMP_VAL: f32 = 0xff as f32;
248
249            let (y, u, v) = (
250                (yuv[0] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u8,
251                (yuv[1] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u8,
252                (yuv[2] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u8,
253            );
254            let y_pos = row * strides[0] + col;
255
256            y_plane[y_pos] = y;
257
258            // Subsample with upper left pixel
259            if col % 2 == 0 && row % 2 == 0 {
260                let u_pos = (row / 2) * strides[1] + col;
261                let v_pos = u_pos + 1;
262
263                uv_plane[u_pos] = u;
264                uv_plane[v_pos] = v;
265            }
266        });
267    }
268
269    pub fn fill_test_frame_nv12(
270        width: usize,
271        height: usize,
272        strides: [usize; 2],
273        offsets: [usize; 2],
274        t: f32,
275        raw: &mut [u8],
276    ) {
277        let (y_plane, uv_plane) = raw.split_at_mut(offsets[1]);
278        let y_plane = &mut y_plane[offsets[0]..];
279
280        fill_test_frame_nm12(width, height, strides, t, y_plane, uv_plane)
281    }
282
283    pub fn fill_test_frame_p010(
284        width: usize,
285        height: usize,
286        strides: [usize; 2],
287        offsets: [usize; 2],
288        t: f32,
289        raw: &mut [u8],
290    ) {
291        gen_test_frame(width, height, t, |col, row, yuv| {
292            /// Maximum value of color component for P010
293            const MAX_COMP_VAL: f32 = 0x3ff as f32;
294
295            let (y, u, v) = (
296                (yuv[0] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u16,
297                (yuv[1] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u16,
298                (yuv[2] * MAX_COMP_VAL).clamp(0.0, MAX_COMP_VAL) as u16,
299            );
300            let y_pos = offsets[0] + row * strides[0] + 2 * col;
301
302            raw[y_pos] = ((y << 6) & 0xa0) as u8;
303            raw[y_pos + 1] = (y >> 2) as u8;
304
305            // Subsample with upper left pixel
306            if col % 2 == 0 && row % 2 == 0 {
307                let u_pos = offsets[1] + (row / 2) * strides[1] + 2 * col;
308                let v_pos = u_pos + 2;
309
310                raw[u_pos] = ((u << 6) & 0xa0) as u8;
311                raw[u_pos + 1] = (u >> 2) as u8;
312                raw[v_pos] = ((v << 6) & 0xa0) as u8;
313                raw[v_pos + 1] = (v >> 2) as u8;
314            }
315        });
316    }
317
318    #[cfg(feature = "v4l2")]
319    pub fn userptr_test_frame_generator(
320        frame_count: u64,
321        layout: FrameLayout,
322        buffer_size: usize,
323    ) -> impl Iterator<Item = (FrameMetadata, UserPtrFrame)> {
324        (0..frame_count).map(move |timestamp| {
325            let frame = UserPtrFrame::alloc(layout.clone(), buffer_size);
326
327            let t = get_test_frame_t(timestamp, frame_count);
328
329            if (frame.layout.format.0 == Fourcc::from(b"NM12")
330                || frame.layout.format.0 == Fourcc::from(b"NV12"))
331                && frame.layout.planes.len() == 2
332                && frame.buffers.len() == 2
333            {
334                // SAFETY: Slice matches the allocation
335                let y_plane = unsafe {
336                    std::slice::from_raw_parts_mut(
337                        frame.buffers[frame.layout.planes[0].buffer_index],
338                        frame.mem_layout.size(),
339                    )
340                };
341                // SAFETY: Slice matches the allocation
342                let uv_plane = unsafe {
343                    std::slice::from_raw_parts_mut(
344                        frame.buffers[frame.layout.planes[1].buffer_index],
345                        frame.mem_layout.size(),
346                    )
347                };
348
349                fill_test_frame_nm12(
350                    frame.layout.size.width as usize,
351                    frame.layout.size.height as usize,
352                    [frame.layout.planes[0].stride, frame.layout.planes[1].stride],
353                    t,
354                    y_plane,
355                    uv_plane,
356                );
357            } else if frame.layout.format.0 == Fourcc::from(b"NV12")
358                && frame.layout.planes.len() == 1
359                && frame.buffers.len() == 1
360            {
361                // SAFETY: Slice matches the allocation
362                let raw = unsafe {
363                    std::slice::from_raw_parts_mut(
364                        frame.buffers[frame.layout.planes[0].buffer_index]
365                            .add(frame.layout.planes[0].offset),
366                        frame.mem_layout.size(),
367                    )
368                };
369
370                let uv_stride = frame.layout.planes[0].stride;
371                let uv_offset = frame.layout.planes[0].stride * (frame.layout.size.height as usize);
372
373                fill_test_frame_nv12(
374                    frame.layout.size.width as usize,
375                    frame.layout.size.height as usize,
376                    [frame.layout.planes[0].stride, uv_stride],
377                    [frame.layout.planes[0].offset, uv_offset],
378                    t,
379                    raw,
380                );
381            } else {
382                panic!("Unrecognized frame layout used during test");
383            }
384
385            let meta = FrameMetadata {
386                timestamp,
387                layout: frame.layout.clone(),
388                force_keyframe: false,
389            };
390
391            (meta, frame)
392        })
393    }
394}