| // 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::collections::VecDeque; |
| use std::rc::Rc; |
| |
| use crate::encoder::stateless::Predictor; |
| use crate::encoder::EncodeError; |
| use crate::encoder::EncodeResult; |
| use crate::encoder::FrameMetadata; |
| use crate::encoder::RateControl; |
| use crate::encoder::Tunings; |
| |
| /// Implementation of [`LowDelay`] prediction structure. See [`LowDelay`] for details. |
| /// |
| /// [`LowDelay`]: crate::encoder::PredictionStructure::LowDelay |
| pub(crate) struct LowDelay<Picture, Reference, Delegate, Request> { |
| /// Pending frames for encoding |
| pub(super) queue: VecDeque<(Picture, FrameMetadata)>, |
| |
| /// Availabe frames for references |
| pub(super) references: VecDeque<Rc<Reference>>, |
| |
| /// Current frame counter |
| pub(super) counter: usize, |
| |
| /// The number of frames between intra frames |
| pub(super) limit: u16, |
| |
| /// Codec specific delegate. Holds codec specific state. Is also used to differentiate |
| /// [`LowDelay`] implementations between codecs. |
| pub(super) delegate: Delegate, |
| |
| /// Currently set tunings for the stream |
| pub(super) tunings: Tunings, |
| |
| /// Pending [`Tunings`] to be applied with `counter` value when to set them. |
| /// In this way we ensure that the requested frames prior tuning request are encoded using |
| /// previous tunings. This is especially important for bitrate, this way we ensure the bitrate |
| /// changes when requested. |
| pub(super) tunings_queue: VecDeque<(usize, Tunings)>, |
| |
| pub(super) _phantom: std::marker::PhantomData<Request>, |
| } |
| |
| /// Helper trait enabling forcing [`LowDelay`] to implement codec specific functions. |
| pub(crate) trait LowDelayDelegate<Picture, Reference, Request> { |
| /// Creates keyframe or IDR request for the codec backend |
| fn request_keyframe( |
| &mut self, |
| input: Picture, |
| input_meta: FrameMetadata, |
| idr: bool, |
| ) -> EncodeResult<Request>; |
| |
| /// Creates interframe request for the codec backend |
| fn request_interframe( |
| &mut self, |
| input: Picture, |
| input_meta: FrameMetadata, |
| ) -> EncodeResult<Request>; |
| |
| /// Checks if the `_tunings` can be applied |
| fn try_tunings(&self, _tunings: &Tunings) -> EncodeResult<()> { |
| Err(EncodeError::Unsupported) |
| } |
| |
| /// Applies `_tunings` |
| fn apply_tunings(&mut self, _tunings: &Tunings) -> EncodeResult<()> { |
| Err(EncodeError::Unsupported) |
| } |
| } |
| |
| impl<Picture, Reference, Delegate, Request> LowDelay<Picture, Reference, Delegate, Request> |
| where |
| Self: LowDelayDelegate<Picture, Reference, Request>, |
| { |
| fn pop_tunings(&mut self) -> EncodeResult<()> { |
| while let Some((when_counter, _)) = self.tunings_queue.front() { |
| if self.counter < *when_counter { |
| log::trace!( |
| "Pending tuning skipped counter={} scheduled={}", |
| self.counter, |
| when_counter |
| ); |
| break; |
| } |
| |
| // SAFETY: checked in loop condition |
| let (_, tunings) = self.tunings_queue.pop_front().unwrap(); |
| log::info!("Applying tuning {tunings:?}"); |
| self.apply_tunings(&tunings)?; |
| self.tunings = tunings; |
| } |
| |
| Ok(()) |
| } |
| |
| fn next_request(&mut self) -> EncodeResult<Vec<Request>> { |
| log::trace!("Pending frames in the queue: {}", self.queue.len()); |
| |
| let mut requests = Vec::new(); |
| while let Some((input, meta)) = self.queue.pop_front() { |
| self.pop_tunings()?; |
| |
| if self.counter == 0 || meta.force_keyframe { |
| log::trace!("Requesting keyframe/IDR for timestamp={}", meta.timestamp); |
| // If first frame in the sequence or forced IDR then clear references and create |
| // keyframe request. |
| // TODO: Maybe don't clear references on just keyframe (!= IDR) |
| self.references.clear(); |
| |
| let request = self.request_keyframe(input, meta, self.counter == 0)?; |
| |
| requests.push(request); |
| self.counter = self.counter.wrapping_add(1) % (self.limit as usize); |
| } else if self.references.is_empty() { |
| log::trace!("Awaiting more reconstructed frames"); |
| // There is no enough frames reconstructed |
| self.queue.push_front((input, meta)); |
| break; |
| } else { |
| log::trace!("Requesting interframe for timestamp={}", meta.timestamp); |
| let request = self.request_interframe(input, meta)?; |
| |
| requests.push(request); |
| self.counter = self.counter.wrapping_add(1) % (self.limit as usize); |
| |
| break; |
| } |
| } |
| |
| Ok(requests) |
| } |
| } |
| |
| impl<Picture, Reference, Delegate, Request> Predictor<Picture, Reference, Request> |
| for LowDelay<Picture, Reference, Delegate, Request> |
| where |
| Self: LowDelayDelegate<Picture, Reference, Request>, |
| { |
| fn new_frame( |
| &mut self, |
| input: Picture, |
| frame_metadata: FrameMetadata, |
| ) -> EncodeResult<Vec<Request>> { |
| log::trace!("New frame added to queue timestamp={}", frame_metadata.timestamp); |
| // Add new frame in the request queue and request new encoding if possible |
| self.queue.push_back((input, frame_metadata)); |
| self.next_request() |
| } |
| |
| fn reconstructed(&mut self, reference: Reference) -> EncodeResult<Vec<Request>> { |
| log::trace!("A frame was reconstructed"); |
| // Add new reconstructed surface and request next encoding if possible |
| self.references.push_back(Rc::new(reference)); |
| self.next_request() |
| } |
| |
| fn tune(&mut self, tunings: Tunings) -> EncodeResult<()> { |
| log::trace!("Tuning requested with {tunings:?}"); |
| if !RateControl::is_same_variant(&self.tunings.rate_control, &tunings.rate_control) { |
| // TODO(bgrzesik): consider enabling switching between RateControl variants |
| log::error!("Changing RateControl variant is not supported at the moment"); |
| return Err(EncodeError::Unsupported); |
| } |
| |
| // Check if the tunings are or will be the same, in such case we skip. |
| let skip = match self.tunings_queue.front() { |
| Some((_, preceeding_tunnings)) if preceeding_tunnings == &tunings => true, |
| None if self.tunings == tunings => true, |
| _ => false, |
| }; |
| |
| if skip { |
| log::debug!("Tuning skipped, the requested values are the same."); |
| return Ok(()); |
| } |
| |
| // Check if applying tunings will succeed. |
| self.try_tunings(&tunings)?; |
| |
| let when_counter = self.counter + self.queue.len(); |
| self.tunings_queue.push_back((when_counter, tunings)); |
| |
| Ok(()) |
| } |
| |
| fn drain(&mut self) -> EncodeResult<Vec<Request>> { |
| // [`LowDelay`] will not hold any frames, therefore the drain function shall never be called. |
| Err(EncodeError::InvalidInternalState) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::Fourcc; |
| |
| use super::*; |
| |
| #[derive(Debug, PartialEq, Eq)] |
| enum MockRequest { |
| KeyFrameRequest(u32, Tunings), |
| InterframerRequest(u32, Tunings), |
| } |
| |
| struct MockDelegate; |
| |
| impl LowDelayDelegate<u32, u32, MockRequest> for LowDelay<u32, u32, MockDelegate, MockRequest> { |
| fn request_interframe( |
| &mut self, |
| input: u32, |
| _input_meta: FrameMetadata, |
| ) -> EncodeResult<MockRequest> { |
| Ok(MockRequest::InterframerRequest(input, self.tunings.clone())) |
| } |
| |
| fn request_keyframe( |
| &mut self, |
| input: u32, |
| _input_meta: FrameMetadata, |
| _idr: bool, |
| ) -> EncodeResult<MockRequest> { |
| Ok(MockRequest::KeyFrameRequest(input, self.tunings.clone())) |
| } |
| |
| fn try_tunings(&self, _tunings: &Tunings) -> EncodeResult<()> { |
| Ok(()) |
| } |
| |
| fn apply_tunings(&mut self, _tunings: &Tunings) -> EncodeResult<()> { |
| Ok(()) |
| } |
| } |
| |
| fn dummy_frame_meta(timestamp: u64, force_keyframe: bool) -> FrameMetadata { |
| FrameMetadata { |
| timestamp, |
| layout: crate::FrameLayout { |
| format: (Fourcc::from(b"NV12"), 0), |
| size: crate::Resolution { width: 0, height: 0 }, |
| planes: vec![], |
| }, |
| force_keyframe, |
| } |
| } |
| |
| /// This test ensures that Tunings change applies to only frames following the change |
| #[test] |
| fn test_tuning_delay() { |
| let _ = env_logger::try_init(); |
| |
| let tunings_prev = Tunings { framerate: 1, ..Default::default() }; |
| |
| let mut predictor: LowDelay<u32, u32, MockDelegate, MockRequest> = LowDelay { |
| queue: Default::default(), |
| references: Default::default(), |
| counter: 0, |
| limit: 1028, |
| delegate: MockDelegate, |
| tunings: tunings_prev.clone(), |
| tunings_queue: Default::default(), |
| _phantom: Default::default(), |
| }; |
| |
| let mut requests = Vec::new(); |
| |
| requests.extend(predictor.new_frame(0, dummy_frame_meta(0, false)).unwrap()); |
| requests.extend(predictor.new_frame(1, dummy_frame_meta(1, false)).unwrap()); |
| requests.extend(predictor.new_frame(2, dummy_frame_meta(2, false)).unwrap()); |
| requests.extend(predictor.new_frame(3, dummy_frame_meta(3, false)).unwrap()); |
| |
| let tunings_next = Tunings { framerate: 2, ..Default::default() }; |
| |
| predictor.tune(tunings_next.clone()).unwrap(); |
| |
| assert_eq!(predictor.tunings, tunings_prev); |
| |
| requests.extend(predictor.new_frame(4, dummy_frame_meta(4, false)).unwrap()); |
| |
| requests.extend(predictor.reconstructed(0).unwrap()); |
| requests.extend(predictor.reconstructed(1).unwrap()); |
| requests.extend(predictor.reconstructed(2).unwrap()); |
| |
| assert_eq!(predictor.tunings, tunings_prev); |
| requests.extend(predictor.reconstructed(3).unwrap()); |
| |
| assert_eq!(predictor.tunings, tunings_next); |
| requests.extend(predictor.reconstructed(4).unwrap()); |
| |
| assert_eq!( |
| requests, |
| vec![ |
| MockRequest::KeyFrameRequest(0, tunings_prev.clone()), |
| MockRequest::InterframerRequest(1, tunings_prev.clone()), |
| MockRequest::InterframerRequest(2, tunings_prev.clone()), |
| MockRequest::InterframerRequest(3, tunings_prev.clone()), |
| MockRequest::InterframerRequest(4, tunings_next.clone()), |
| ] |
| ); |
| } |
| |
| #[test] |
| fn test_keyframes() { |
| const FRAME_COUNT: u32 = 1028; |
| const IDR_PERIOD: u16 = 37; |
| const KEYFRAME_REQUEST_PERIOD: u32 = 31; |
| |
| let _ = env_logger::try_init(); |
| |
| let tunings = Tunings::default(); |
| let mut predictor: LowDelay<u32, u32, MockDelegate, MockRequest> = LowDelay { |
| queue: Default::default(), |
| references: Default::default(), |
| counter: 0, |
| limit: IDR_PERIOD, |
| delegate: MockDelegate, |
| tunings: tunings.clone(), |
| tunings_queue: Default::default(), |
| _phantom: Default::default(), |
| }; |
| |
| let mut requests = Vec::new(); |
| |
| for i in 0..FRAME_COUNT { |
| let keyframe = i % KEYFRAME_REQUEST_PERIOD == 0; |
| requests.extend(predictor.new_frame(i, dummy_frame_meta(i as u64, keyframe)).unwrap()); |
| } |
| |
| for i in 0..FRAME_COUNT { |
| requests.extend(predictor.reconstructed(i).unwrap()); |
| } |
| |
| let mut expected = Vec::new(); |
| for i in 0..FRAME_COUNT { |
| if (i % IDR_PERIOD as u32 == 0) || (i % KEYFRAME_REQUEST_PERIOD) == 0 { |
| expected.push(MockRequest::KeyFrameRequest(i, tunings.clone())); |
| } else { |
| expected.push(MockRequest::InterframerRequest(i, tunings.clone())); |
| } |
| } |
| |
| assert_eq!(requests, expected); |
| } |
| } |