// Copyright 2024 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::rc::Rc; use log::trace; use crate::codec::h264::parser::Level; use crate::codec::h264::parser::Pps; use crate::codec::h264::parser::PpsBuilder; use crate::codec::h264::parser::Profile; use crate::codec::h264::parser::SliceHeaderBuilder; use crate::codec::h264::parser::SliceType; use crate::codec::h264::parser::Sps; use crate::codec::h264::parser::SpsBuilder; use crate::codec::h264::synthesizer::Synthesizer; use crate::encoder::stateless::h264::BackendRequest; use crate::encoder::stateless::h264::DpbEntry; use crate::encoder::stateless::h264::DpbEntryMeta; use crate::encoder::stateless::h264::EncoderConfig; use crate::encoder::stateless::h264::IsReference; use crate::encoder::stateless::predictor::LowDelay; use crate::encoder::stateless::predictor::LowDelayDelegate; use crate::encoder::stateless::FrameMetadata; use crate::encoder::EncodeError; use crate::encoder::EncodeResult; use crate::encoder::RateControl; use crate::encoder::Tunings; pub(crate) const MIN_QP: u8 = 1; pub(crate) const MAX_QP: u8 = 51; pub(crate) struct LowDelayH264Delegate { /// Current sequence SPS sps: Option>, /// Current sequence PPS pps: Option>, // True if SPS or PPS changed and should reappear in the bitstream update_params_sets: bool, /// Encoder config config: EncoderConfig, } pub(crate) type LowDelayH264 = LowDelay< Picture, DpbEntry, LowDelayH264Delegate, BackendRequest, >; impl LowDelayH264 { pub(super) fn new(config: EncoderConfig, limit: u16) -> Self { Self { queue: Default::default(), references: Default::default(), counter: 0, limit, tunings: config.initial_tunings.clone(), delegate: LowDelayH264Delegate { config, update_params_sets: false, sps: None, pps: None, }, tunings_queue: Default::default(), _phantom: Default::default(), } } fn new_sequence(&mut self) { trace!("beginning new sequence"); let config = &self.delegate.config; let mut sps = SpsBuilder::new().seq_parameter_set_id(0).profile_idc(config.profile); // H.264 Table 6-1 sps = match config.profile { // 4:2:2 subsampling Profile::High422P => sps.chroma_format_idc(2), // 4:2:0 subsampling _ => sps.chroma_format_idc(1), }; let sps = sps .level_idc(config.level) .max_frame_num(self.limit as u32) .pic_order_cnt_type(0) .max_pic_order_cnt_lsb(self.limit as u32 * 2) .max_num_ref_frames(1) .frame_mbs_only_flag(true) // H264 spec Table A-4 .direct_8x8_inference_flag(config.level >= Level::L3) .resolution(config.resolution.width, config.resolution.height) .bit_depth_luma(8) .bit_depth_chroma(8) .aspect_ratio(1, 1) .timing_info(1, self.tunings.framerate * 2, false) .build(); let min_qp = self.tunings.min_quality.max(MIN_QP as u32); let max_qp = self.tunings.max_quality.min(MAX_QP as u32); let init_qp = if let RateControl::ConstantQuality(init_qp) = self.tunings.rate_control { // Limit QP to valid values init_qp.clamp(min_qp, max_qp) as u8 } else { // Pick middle QP for default qp ((min_qp + max_qp) / 2) as u8 }; let pps = PpsBuilder::new(Rc::clone(&sps)) .pic_parameter_set_id(0) .pic_init_qp(init_qp) .deblocking_filter_control_present_flag(true) .num_ref_idx_l0_default_active(1) // Unused, P frame relies only on list0 .num_ref_idx_l1_default_active_minus1(0) .build(); self.delegate.sps = Some(sps); self.delegate.pps = Some(pps); self.delegate.update_params_sets = true; } } impl LowDelayDelegate, BackendRequest> for LowDelayH264 { fn request_keyframe( &mut self, input: Picture, input_meta: FrameMetadata, idr: bool, ) -> EncodeResult> { if idr { // Begin new sequence and start with I frame and no references. self.new_sequence(); } let sps = self.delegate.sps.clone().ok_or(EncodeError::InvalidInternalState)?; let pps = self.delegate.pps.clone().ok_or(EncodeError::InvalidInternalState)?; let dpb_meta = DpbEntryMeta { poc: ((self.counter * 2) & 0xffff) as u16, frame_num: self.counter as u32, is_reference: IsReference::ShortTerm, }; let header = SliceHeaderBuilder::new(&pps) .slice_type(SliceType::I) .first_mb_in_slice(0) .pic_order_cnt_lsb(dpb_meta.poc) .build(); let mut headers = vec![]; if idr || self.delegate.update_params_sets { Synthesizer::>::synthesize(3, &sps, &mut headers, true)?; Synthesizer::>::synthesize(3, &pps, &mut headers, true)?; self.delegate.update_params_sets = false; } let num_macroblocks = (sps.pic_width_in_mbs_minus1 as usize + 1) * (sps.pic_height_in_map_units_minus1 as usize + 1); let request = BackendRequest { sps, pps, header, input, input_meta, dpb_meta, // This frame is IDR, therefore it has no references ref_list_0: vec![], ref_list_1: vec![], // I frame is every `self.limit` is requested intra_period: self.limit as u32, // There is no B frames between I and P frames ip_period: 0, num_macroblocks, is_idr: idr, tunings: self.tunings.clone(), coded_output: headers, }; Ok(request) } fn request_interframe( &mut self, input: Picture, input_meta: FrameMetadata, ) -> EncodeResult> { let mut ref_list_0 = vec![]; // Use all avaiable reference frames in DPB. Their number is limited by the parameter for reference in self.references.iter().rev() { ref_list_0.push(Rc::clone(reference)); } let sps = self.delegate.sps.clone().ok_or(EncodeError::InvalidInternalState)?; let pps = self.delegate.pps.clone().ok_or(EncodeError::InvalidInternalState)?; let dpb_meta = DpbEntryMeta { poc: ((self.counter * 2) & 0xffff) as u16, frame_num: self.counter as u32, is_reference: IsReference::ShortTerm, }; let header = SliceHeaderBuilder::new(&pps) .slice_type(SliceType::P) .first_mb_in_slice(0) .pic_order_cnt_lsb(dpb_meta.poc) .build(); let mut headers = Vec::new(); if self.delegate.update_params_sets { Synthesizer::>::synthesize(3, &sps, &mut headers, true)?; Synthesizer::>::synthesize(3, &pps, &mut headers, true)?; self.delegate.update_params_sets = false; } let num_macroblocks = (sps.pic_width_in_mbs_minus1 as usize + 1) * (sps.pic_height_in_map_units_minus1 as usize + 1); let request = BackendRequest { sps, pps, header, input, input_meta, dpb_meta, ref_list_0, ref_list_1: vec![], // No future references // I frame is every `self.limit` is requested intra_period: self.limit as u32, // There is no B frames between I and P frames ip_period: 0, num_macroblocks, is_idr: false, tunings: self.tunings.clone(), coded_output: headers, }; self.references.clear(); Ok(request) } fn try_tunings(&self, _tunings: &Tunings) -> EncodeResult<()> { Ok(()) } fn apply_tunings(&mut self, _tunings: &Tunings) -> EncodeResult<()> { self.new_sequence(); Ok(()) } }