Skip to content

Commit

Permalink
encoder: Add VP9 encoder and vaapi backend
Browse files Browse the repository at this point in the history
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
bgrzesik committed Feb 22, 2024
1 parent 9048c70 commit 71e12ea
Show file tree
Hide file tree
Showing 4 changed files with 914 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/encoder/stateless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::encoder::FrameMetadata;
use crate::BlockingMode;

pub mod h264;
pub mod vp9;

#[derive(Error, Debug)]
pub enum StatelessBackendError {
Expand Down
188 changes: 188 additions & 0 deletions src/encoder/stateless/vp9.rs
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)
}
}
169 changes: 169 additions & 0 deletions src/encoder/stateless/vp9/predictor.rs
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)
}
}
Loading

0 comments on commit 71e12ea

Please sign in to comment.