-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
encoder: Add VP9 encoder and vaapi backend
This change adds VP9 encoding support and vaapi backend for it. The StatelessEncoder implementation is a copy from H264 implementation. Both will be merge in the following change.
- Loading branch information
Showing
4 changed files
with
914 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// 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 crate::codec::vp9::parser::Header; | ||
use crate::encoder::stateless::vp9::predictor::LowDelay; | ||
pub use crate::encoder::stateless::vp9::predictor::PredictionStructure; | ||
use crate::encoder::stateless::BackendPromise; | ||
use crate::encoder::stateless::EncodeResult; | ||
use crate::encoder::stateless::Predictor; | ||
use crate::encoder::stateless::StatelessBackendResult; | ||
use crate::encoder::stateless::StatelessCodec; | ||
use crate::encoder::stateless::StatelessCodecSpecific; | ||
use crate::encoder::stateless::StatelessEncoder; | ||
use crate::encoder::stateless::StatelessEncoderExecute; | ||
use crate::encoder::stateless::StatelessVideoEncoderBackend; | ||
use crate::encoder::Bitrate; | ||
use crate::encoder::CodedBitstreamBuffer; | ||
use crate::encoder::FrameMetadata; | ||
use crate::BlockingMode; | ||
use crate::Resolution; | ||
|
||
mod predictor; | ||
|
||
#[cfg(feature = "vaapi")] | ||
pub mod vaapi; | ||
|
||
#[derive(Clone)] | ||
pub struct EncoderConfig { | ||
pub bitrate: Bitrate, | ||
pub framerate: u32, | ||
pub resolution: Resolution, | ||
pub pred_structure: PredictionStructure, | ||
} | ||
|
||
impl Default for EncoderConfig { | ||
fn default() -> Self { | ||
// Artificially encoder configuration with intent to be widely supported. | ||
Self { | ||
bitrate: Bitrate::Constant(30_000_000), | ||
framerate: 30, | ||
resolution: Resolution { | ||
width: 320, | ||
height: 240, | ||
}, | ||
pred_structure: PredictionStructure::LowDelay { limit: 2048 }, | ||
} | ||
} | ||
} | ||
|
||
/// Determines how reference frame shall be used | ||
pub enum ReferenceUse { | ||
Single, | ||
Compound, | ||
Hybrid, | ||
} | ||
|
||
pub struct BackendRequest<P, R> { | ||
header: Header, | ||
|
||
/// Input frame to be encoded | ||
input: P, | ||
|
||
/// Input frame metadata | ||
input_meta: FrameMetadata, | ||
|
||
/// Reference frames | ||
last_frame_ref: Option<(Rc<R>, ReferenceUse)>, | ||
golden_frame_ref: Option<(Rc<R>, ReferenceUse)>, | ||
altref_frame_ref: Option<(Rc<R>, ReferenceUse)>, | ||
|
||
/// Current encoder config. The backend may peek into config to find bitrate and framerate | ||
/// settings. | ||
config: Rc<EncoderConfig>, | ||
|
||
/// Container for the request output. [`StatelessVP9EncoderBackend`] impl shall move it and | ||
/// append the slice data to it. This prevents unnecessary copying of bitstream around. | ||
coded_output: Vec<u8>, | ||
} | ||
|
||
/// Wrapper type for [`BackendPromise<Output = Vec<u8>>`], with additional | ||
/// metadata. | ||
pub struct FramePromise<P> | ||
where | ||
P: BackendPromise<Output = Vec<u8>>, | ||
{ | ||
/// Slice data and reconstructed surface promise | ||
bitstream: P, | ||
|
||
/// Input frame metadata, for [`CodedBitstreamBuffer`] | ||
meta: FrameMetadata, | ||
} | ||
|
||
impl<P> BackendPromise for FramePromise<P> | ||
where | ||
P: BackendPromise<Output = Vec<u8>>, | ||
{ | ||
type Output = CodedBitstreamBuffer; | ||
|
||
fn is_ready(&self) -> bool { | ||
self.bitstream.is_ready() | ||
} | ||
|
||
fn sync(self) -> StatelessBackendResult<Self::Output> { | ||
let coded_data = self.bitstream.sync()?; | ||
|
||
log::trace!("synced bitstream size={}", coded_data.len()); | ||
|
||
Ok(CodedBitstreamBuffer::new(self.meta, coded_data)) | ||
} | ||
} | ||
|
||
pub struct VP9; | ||
|
||
pub struct VP9Specific<Backend> | ||
where | ||
Backend: StatelessVideoEncoderBackend<VP9>, | ||
{ | ||
_phantom: std::marker::PhantomData<Backend>, | ||
} | ||
|
||
impl<Backend> StatelessCodecSpecific<VP9, Backend> for VP9Specific<Backend> | ||
where | ||
Backend: StatelessVideoEncoderBackend<VP9>, | ||
{ | ||
type Reference = Backend::Reconstructed; | ||
|
||
type Request = BackendRequest<Backend::Picture, Backend::Reconstructed>; | ||
|
||
type CodedPromise = FramePromise<Backend::CodedPromise>; | ||
|
||
type ReferencePromise = Backend::ReconPromise; | ||
} | ||
|
||
impl StatelessCodec for VP9 { | ||
type Specific<Backend> = VP9Specific<Backend> | ||
where Backend : StatelessVideoEncoderBackend<Self>; | ||
} | ||
|
||
pub trait StatelessVP9EncoderBackend<H>: StatelessVideoEncoderBackend<VP9> { | ||
fn encode_frame( | ||
&mut self, | ||
request: BackendRequest<Self::Picture, Self::Reconstructed>, | ||
) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)>; | ||
} | ||
|
||
impl<Handle, Backend> StatelessEncoderExecute<VP9, Handle, Backend> | ||
for StatelessEncoder<VP9, Handle, Backend> | ||
where | ||
Backend: StatelessVP9EncoderBackend<Handle>, | ||
{ | ||
fn execute( | ||
&mut self, | ||
request: BackendRequest<Backend::Picture, Backend::Reconstructed>, | ||
) -> EncodeResult<()> { | ||
let meta = request.input_meta.clone(); | ||
|
||
// The [`BackendRequest`] has a frame from predictor. Decresing internal counter. | ||
self.predictor_frame_count -= 1; | ||
|
||
log::trace!("submitting new request"); | ||
let (recon, bitstream) = self.backend.encode_frame(request)?; | ||
|
||
// Wrap promise from backend with headers and metadata | ||
let slice_promise = FramePromise { bitstream, meta }; | ||
|
||
self.output_queue.add_promise(slice_promise); | ||
|
||
self.recon_queue.add_promise(recon); | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
impl<Handle, Backend> StatelessEncoder<VP9, Handle, Backend> | ||
where | ||
Backend: StatelessVP9EncoderBackend<Handle>, | ||
{ | ||
fn new_vp9(backend: Backend, config: EncoderConfig, mode: BlockingMode) -> EncodeResult<Self> { | ||
let predictor: Box<dyn Predictor<_, _, _>> = match config.pred_structure { | ||
PredictionStructure::LowDelay { .. } => Box::new(LowDelay::new(config)), | ||
}; | ||
|
||
Self::new(backend, mode, predictor) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// 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 super::BackendRequest; | ||
use super::EncoderConfig; | ||
use crate::codec::vp9::parser::FrameType; | ||
use crate::codec::vp9::parser::Header; | ||
use crate::encoder::stateless::vp9::ReferenceUse; | ||
use crate::encoder::stateless::EncodeError; | ||
use crate::encoder::stateless::EncodeResult; | ||
use crate::encoder::stateless::Predictor; | ||
use crate::encoder::FrameMetadata; | ||
|
||
#[derive(Clone)] | ||
pub enum PredictionStructure { | ||
/// Simplest prediction structure, suitable eg. for RTC. Interframe is produced at the start of | ||
/// the stream and every time when [`limit`] frames are reached. Following interframe frames | ||
/// are frames relaying solely on the last frame. | ||
LowDelay { limit: u16 }, | ||
} | ||
|
||
/// See [`PredictionStructure::LowDelay`] | ||
pub(super) struct LowDelay<P, R> { | ||
queue: VecDeque<(P, FrameMetadata)>, | ||
|
||
references: VecDeque<Rc<R>>, | ||
|
||
counter: usize, | ||
|
||
/// Encoder config | ||
config: Rc<EncoderConfig>, | ||
} | ||
|
||
impl<P, R> LowDelay<P, R> { | ||
pub(super) fn new(config: EncoderConfig) -> Self { | ||
Self { | ||
queue: Default::default(), | ||
references: Default::default(), | ||
counter: 0, | ||
config: Rc::new(config), | ||
} | ||
} | ||
|
||
fn request_keyframe( | ||
&mut self, | ||
input: P, | ||
input_meta: FrameMetadata, | ||
) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
log::trace!("Requested keyframe timestamp={}", input_meta.timestamp); | ||
|
||
let header = Header { | ||
frame_type: FrameType::KeyFrame, | ||
show_frame: true, | ||
error_resilient_mode: true, | ||
width: self.config.resolution.width, | ||
height: self.config.resolution.height, | ||
render_and_frame_size_different: false, | ||
render_width: self.config.resolution.width, | ||
render_height: self.config.resolution.height, | ||
intra_only: true, | ||
refresh_frame_flags: 0x01, | ||
ref_frame_idx: [0, 0, 0], | ||
|
||
..Default::default() | ||
}; | ||
|
||
let request = BackendRequest { | ||
header, | ||
input, | ||
input_meta, | ||
last_frame_ref: None, | ||
golden_frame_ref: None, | ||
altref_frame_ref: None, | ||
config: self.config.clone(), | ||
coded_output: Vec::new(), | ||
}; | ||
|
||
self.counter += 1; | ||
|
||
Ok(vec![request]) | ||
} | ||
|
||
fn request_interframe( | ||
&mut self, | ||
input: P, | ||
input_meta: FrameMetadata, | ||
) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
log::trace!("Requested interframe timestamp={}", input_meta.timestamp); | ||
|
||
let header = Header { | ||
frame_type: FrameType::InterFrame, | ||
show_frame: true, | ||
error_resilient_mode: true, | ||
width: self.config.resolution.width, | ||
height: self.config.resolution.height, | ||
render_and_frame_size_different: false, | ||
render_width: self.config.resolution.width, | ||
render_height: self.config.resolution.height, | ||
intra_only: false, | ||
refresh_frame_flags: 0x01, | ||
ref_frame_idx: [0; 3], | ||
|
||
..Default::default() | ||
}; | ||
|
||
let ref_frame = self.references.pop_front().unwrap(); | ||
|
||
let request = BackendRequest { | ||
header, | ||
input, | ||
input_meta, | ||
last_frame_ref: Some((ref_frame, ReferenceUse::Single)), | ||
golden_frame_ref: None, | ||
altref_frame_ref: None, | ||
config: self.config.clone(), | ||
coded_output: Vec::new(), | ||
}; | ||
|
||
self.counter += 1; | ||
self.references.clear(); | ||
|
||
Ok(vec![request]) | ||
} | ||
|
||
fn next_request(&mut self) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
match self.queue.pop_front() { | ||
// Nothing to do. Quit. | ||
None => Ok(Vec::new()), | ||
// If first frame in the sequence or forced IDR then create IDR request. | ||
Some((input, meta)) if self.counter == 0 || meta.force_keyframe => { | ||
self.request_keyframe(input, meta) | ||
} | ||
// There is no enough frames reconstructed | ||
Some((input, meta)) if self.references.is_empty() => { | ||
self.queue.push_front((input, meta)); | ||
Ok(Vec::new()) | ||
} | ||
|
||
Some((input, meta)) => self.request_interframe(input, meta), | ||
} | ||
} | ||
} | ||
|
||
impl<P, R> Predictor<P, R, BackendRequest<P, R>> for LowDelay<P, R> { | ||
fn new_frame( | ||
&mut self, | ||
input: P, | ||
frame_metadata: FrameMetadata, | ||
) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
// 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, recon: R) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
// Add new reconstructed surface and request next encoding if possible | ||
self.references.push_back(Rc::new(recon)); | ||
self.next_request() | ||
} | ||
|
||
fn drain(&mut self) -> EncodeResult<Vec<BackendRequest<P, R>>> { | ||
// [`LowDelay`] will not hold any frames, therefore the drain function shall never be called. | ||
Err(EncodeError::InvalidInternalState) | ||
} | ||
} |
Oops, something went wrong.