less_avc/
encoder.rs

1// Copyright 2022-2023 Andrew D. Straw.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
5// or http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! High-level encoder to take frames into and emit NAL units.
9
10#[cfg(feature = "backtrace")]
11use std::backtrace::Backtrace;
12
13use super::nal_unit::*;
14use super::*;
15
16/// Convert input images [YCbCrImage] into H.264 NAL units [NalUnit].
17///
18/// This high-level type brings together the steps of initiating an h.264
19/// encoding session with a sequence parameter set and a picture parameter set
20/// and then repeatedly appending pictures.
21pub struct LessEncoder {
22    width: u32,
23    height: u32,
24    mbs_width: usize,
25    mbs_height: usize,
26    sps: Sps,
27    pps: Pps,
28}
29
30impl LessEncoder {
31    /// Initialize an encoder and encode first frame.
32    ///
33    /// The sequence parameter set and picture parameter set are inferred from
34    /// the input [YCbCrImage].
35    pub fn new(y4m_frame: &YCbCrImage) -> Result<(InitialNalUnits, Self)> {
36        let width = y4m_frame.width;
37        let height = y4m_frame.height;
38
39        let bit_depth = y4m_frame.luma_bit_depth();
40
41        match (&y4m_frame.planes, &bit_depth, width % 4 == 0) {
42            (Planes::YCbCr(_), BitDepth::Depth12, false) => {
43                return Err(Error::DataShapeProblem {
44                    msg: "for bit depth 12 color, width must be divisible by 4",
45                    #[cfg(feature = "backtrace")]
46                    backtrace: Backtrace::capture(),
47                });
48            }
49            _ => {}
50        }
51
52        match (&y4m_frame.planes, &bit_depth, width % 2 == 0) {
53            (Planes::Mono(_), BitDepth::Depth8, false) | (_, _, true) => {}
54            (_, _, false) => {
55                return Err(Error::DataShapeProblem {
56                    msg: "width must be divisible by 2 (except mono8)",
57                    #[cfg(feature = "backtrace")]
58                    backtrace: Backtrace::capture(),
59                });
60            }
61        }
62
63        #[allow(non_snake_case)]
64        let (profile_idc, SubWidthC, SubHeightC) = match (&y4m_frame.planes, bit_depth) {
65            (Planes::YCbCr(_), BitDepth::Depth8) => (ProfileIdc::baseline(), 2, 2),
66            (Planes::Mono(_), BitDepth::Depth8) => (
67                ProfileIdc::high(ChromaFormatIdc::Monochrome(bit_depth)),
68                1,
69                1,
70            ),
71            (Planes::Mono(_), BitDepth::Depth12) => (
72                ProfileIdc::high444pp(ChromaFormatIdc::Monochrome(bit_depth)),
73                1,
74                1,
75            ),
76            (Planes::YCbCr(_), BitDepth::Depth12) => (
77                ProfileIdc::high444pp(ChromaFormatIdc::Chroma420(bit_depth)),
78                2,
79                2,
80            ),
81        };
82
83        let pic_width_in_mbs_minus1 = div_ceil(width, 16) - 1;
84        let pic_height_in_map_units_minus1 = div_ceil(height, 16) - 1;
85
86        let frame_cropping = if ((pic_width_in_mbs_minus1 + 1) * 16 != width)
87            || ((pic_height_in_map_units_minus1 + 1) * 16 != height)
88        {
89            // full size of allocated space
90            let padded_width = (pic_width_in_mbs_minus1 + 1) * 16;
91            let padded_height = (pic_height_in_map_units_minus1 + 1) * 16;
92
93            let lr_pad = padded_width - width;
94            let tb_pad = padded_height - height;
95
96            let lpad = 0;
97            let tpad = 0;
98            let rpad = lr_pad / SubWidthC;
99            let bpad = tb_pad / SubHeightC;
100
101            if (lpad * SubWidthC + width + rpad * SubWidthC != padded_width)
102                || (tpad * SubHeightC + bpad * SubHeightC + height != padded_height)
103            {
104                return Err(crate::Error::UnsupportedImageSize {
105                    #[cfg(feature = "backtrace")]
106                    backtrace: Backtrace::capture(),
107                });
108            }
109
110            Some([lpad, rpad, tpad, bpad])
111        } else {
112            None
113        };
114
115        // SPS
116        let sps = Sps::new(
117            profile_idc,
118            pic_width_in_mbs_minus1,
119            pic_height_in_map_units_minus1,
120            frame_cropping,
121            Some(Vui::new(true)),
122        );
123        let sps_nal_unit = NalUnit::new(
124            NalRefIdc::Three,
125            NalUnitType::SequenceParameterSet,
126            sps.to_rbsp(),
127        );
128
129        // PPS
130        let pps = Pps::new(0);
131        let pps_nal_unit = NalUnit::new(
132            NalRefIdc::Three,
133            NalUnitType::PictureParameterSet,
134            pps.to_rbsp(),
135        );
136
137        let mbs_width = (pic_width_in_mbs_minus1 + 1).try_into().unwrap();
138        let mbs_height = (pic_height_in_map_units_minus1 + 1).try_into().unwrap();
139
140        let mut self_ = Self {
141            width,
142            height,
143            mbs_width,
144            mbs_height,
145            sps,
146            pps,
147        };
148
149        let frame_nal_unit = self_.encode(y4m_frame)?;
150        let nal_units = InitialNalUnits {
151            sps: sps_nal_unit,
152            pps: pps_nal_unit,
153            frame: frame_nal_unit,
154        };
155        Ok((nal_units, self_))
156    }
157
158    /// Encode a frame, converting an input image [YCbCrImage] into [NalUnit].
159    pub fn encode(&mut self, y4m_frame: &YCbCrImage) -> Result<NalUnit> {
160        y4m_frame.check_sizes()?;
161
162        debug_assert_eq!(self.width, y4m_frame.width);
163        debug_assert_eq!(self.height, y4m_frame.height);
164
165        let mut slice_data = SliceHeader::new().to_rbsp(&self.sps, &self.pps);
166
167        let luma_only = self.sps.profile_idc.is_monochrome();
168
169        let num_macroblocks = self.mbs_height * self.mbs_width;
170
171        // reserve space for frame without requiring reallocation
172        let row_sz = match y4m_frame.luma_bit_depth() {
173            BitDepth::Depth8 => 16,
174            BitDepth::Depth12 => 24,
175        };
176        let orig_len = slice_data.data.len();
177
178        // space for macroblock data
179        let mut reserve_size = if luma_only {
180            // luma only in output
181            num_macroblocks * row_sz * 16
182        } else {
183            // 4:2:0
184            num_macroblocks * row_sz * 16 * 3 / 2
185        };
186
187        // space for header and final slice stop bit
188        reserve_size +=
189            (num_macroblocks - 1) * MacroblockType::I_PCM.as_encoded_macroblock_header().len() + 1;
190
191        slice_data.data.reserve(reserve_size);
192
193        for mbs_row in 0..self.mbs_height {
194            for mbs_col in 0..self.mbs_width {
195                // todo: look at mb_skip_flag and mb_skip_run for luma only images.
196                macroblock(mbs_row, mbs_col, &mut slice_data, y4m_frame, luma_only);
197            }
198        }
199
200        slice_data.data.push(0x80); // slice stop bit
201
202        let final_len = slice_data.data.len();
203        let should_have_reserved = final_len - orig_len;
204
205        debug_assert_eq!(should_have_reserved, reserve_size);
206
207        Ok(NalUnit::new(
208            NalRefIdc::One,
209            NalUnitType::CodedSliceOfAnIDRPicture,
210            slice_data,
211        ))
212    }
213}