From 9dab456bb984234d03b4045155ff32c1a2447e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Thu, 22 Feb 2024 14:51:35 +0100 Subject: [PATCH 1/7] encoder: Export codec specific types using trait This is necessary for the next commits --- src/encoder/stateless.rs | 23 ++++++++++++++++++++++- src/encoder/stateless/h264.rs | 20 +++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index d97aa0dc..ef30d96e 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -179,7 +179,28 @@ pub trait StatelessEncoderBackendImport { ) -> StatelessBackendResult; } -pub trait StatelessCodec {} +/// Trait helping contain all codec specific and backend specific types +pub trait StatelessCodecSpecific: StatelessCodec +where + Backend: StatelessVideoEncoderBackend, +{ + /// Codec specific representation of frame reference wrapping a backend reference type + /// containing a codec specific frame metadata + type Reference; + + /// A request type that will be delivered to codec specific stateless encoder backend + type Request; + + /// Codec specific [`BackendPromise`] for [`CodedBitstreamBuffer`] wrapping a backend specific + /// [`StatelessVideoEncoderBackend::CodedPromise`] + type CodedPromise: BackendPromise; + + /// Codec specific [`BackendPromise`] for [`StatelessCodecSpecific::Reference`] wrapping a + /// backend speficic [`StatelessVideoEncoderBackend::ReconPromise`] + type ReferencePromise: BackendPromise; +} + +pub trait StatelessCodec: Sized {} /// Stateless video encoder interface. pub trait StatelessVideoEncoder { diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs index 7b1b0a63..72e4b079 100644 --- a/src/encoder/stateless/h264.rs +++ b/src/encoder/stateless/h264.rs @@ -19,6 +19,7 @@ use crate::encoder::stateless::OutputQueue; use crate::encoder::stateless::Predictor; use crate::encoder::stateless::StatelessBackendResult; use crate::encoder::stateless::StatelessCodec; +use crate::encoder::stateless::StatelessCodecSpecific; use crate::encoder::stateless::StatelessEncoderBackendImport; use crate::encoder::stateless::StatelessVideoEncoder; use crate::encoder::stateless::StatelessVideoEncoderBackend; @@ -81,7 +82,7 @@ pub(crate) struct DpbEntryMeta { /// Frame structure used in the backend representing currently encoded frame or references used /// for its encoding. -pub(crate) struct DpbEntry { +pub struct DpbEntry { /// Reconstructed picture recon_pic: R, /// Decoded picture buffer entry metadata @@ -123,7 +124,7 @@ pub struct BackendRequest { /// Wrapper type for [`BackendPromise>`], with additional /// metadata. -struct SlicePromise

+pub struct SlicePromise

where P: BackendPromise>, { @@ -155,7 +156,7 @@ where /// Wrapper type for [`BackendPromise`], with additional /// metadata. -struct ReferencePromise

+pub struct ReferencePromise

where P: BackendPromise, { @@ -190,6 +191,19 @@ where pub struct H264; +impl StatelessCodecSpecific for H264 +where + Backend: StatelessVideoEncoderBackend, +{ + type Reference = DpbEntry; + + type Request = BackendRequest; + + type CodedPromise = SlicePromise; + + type ReferencePromise = ReferencePromise; +} + impl StatelessCodec for H264 {} /// Trait for stateless encoder backend for H.264 From 18e098b1c7c6c637c3a8e6cf5fbd40ec7fd5c1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Wed, 21 Feb 2024 16:33:10 +0100 Subject: [PATCH 2/7] encoder: Make StatelessEncoder codec agnostic --- examples/ccenc.rs | 5 +- src/backend/vaapi/encoder.rs | 2 +- src/encoder/stateless.rs | 172 +++++++++++++++++++++++++++- src/encoder/stateless/h264.rs | 149 +++--------------------- src/encoder/stateless/h264/vaapi.rs | 17 +-- 5 files changed, 195 insertions(+), 150 deletions(-) diff --git a/examples/ccenc.rs b/examples/ccenc.rs index 11ce4fb5..551aa3c1 100644 --- a/examples/ccenc.rs +++ b/examples/ccenc.rs @@ -14,7 +14,8 @@ use cros_codecs::backend::vaapi::surface_pool::VaSurfacePool; use cros_codecs::codec::h264::parser::Profile; use cros_codecs::decoder::FramePool; use cros_codecs::encoder::stateless::h264::EncoderConfig; -use cros_codecs::encoder::stateless::h264::StatelessEncoder; +use cros_codecs::encoder::stateless::h264::H264; +use cros_codecs::encoder::stateless::StatelessEncoder; use cros_codecs::encoder::stateless::StatelessVideoEncoder; use cros_codecs::encoder::Bitrate; use cros_codecs::encoder::FrameMetadata; @@ -157,7 +158,7 @@ fn main() { let display = libva::Display::open().unwrap(); let fourcc = b"NV12".into(); - let mut encoder = StatelessEncoder::new_vaapi( + let mut encoder = StatelessEncoder::::new_vaapi( Rc::clone(&display), config, fourcc, diff --git a/src/backend/vaapi/encoder.rs b/src/backend/vaapi/encoder.rs index 5f65c9e3..dfc3d632 100644 --- a/src/backend/vaapi/encoder.rs +++ b/src/backend/vaapi/encoder.rs @@ -61,7 +61,7 @@ impl Reconstructed { pub struct VaapiBackend where M: SurfaceMemoryDescriptor, - H: std::borrow::Borrow>, + H: std::borrow::Borrow> + 'static, { /// VA config. #[allow(dead_code)] diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index ef30d96e..c5a3d95f 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -155,7 +155,7 @@ where /// an input frame in internal backend's representation. /// /// [`import_picture`]: StatelessEncoderBackendImport::import_picture - type Picture; + type Picture: 'static; /// Backend's reconstructed frame handle. type Reconstructed: 'static; @@ -170,8 +170,6 @@ where pub trait StatelessEncoderBackendImport { /// Imports the input [`Handle`] from client and transforms into [`Picture`] - /// - /// [`Picture`]: StatelessVideoEncoderBackend::Picture fn import_picture( &mut self, metadata: &FrameMetadata, @@ -203,13 +201,13 @@ where pub trait StatelessCodec: Sized {} /// Stateless video encoder interface. -pub trait StatelessVideoEncoder { +pub trait StatelessVideoEncoder { /// Enqueues the frame for encoding. The implementation will drop the handle after it is no /// longer be needed. The encoder is not required to immediately start processing the frame /// and yield output bitstream. It is allowed to hold frames until certain conditions are met /// eg. for specified prediction structures or referencing in order to further optimize /// the compression rate of the bitstream. - fn encode(&mut self, meta: FrameMetadata, handle: H) -> Result<(), EncodeError>; + fn encode(&mut self, meta: FrameMetadata, handle: Handle) -> Result<(), EncodeError>; /// Drains the encoder. This means that encoder is required to finish processing of all the /// frames in the internal queue and yield output bitstream by the end of the call. The output @@ -255,3 +253,167 @@ where Ok(()) } + +/// Helper aliases for codec and backend specific types +type Picture = >::Picture; + +type Reference = >::Reference; + +type Request = >::Request; + +type CodedPromise = >::CodedPromise; + +type ReferencePromise = + >::ReferencePromise; + +type BoxPredictor = Box, Reference, Request>>; + +pub struct StatelessEncoder +where + Backend: StatelessVideoEncoderBackend, + Codec: StatelessCodec + StatelessCodecSpecific, +{ + /// Pending frame output promise queue + output_queue: OutputQueue>, + + /// Pending reconstructed pictures promise queue + recon_queue: OutputQueue>, + + /// [`Predictor`] instance responsible for the encoder decision making + predictor: BoxPredictor, + + // predictor: Box>, + coded_queue: VecDeque, + + /// Number of the currently held frames by the predictor + predictor_frame_count: usize, + + /// [`StatelessVP9EncoderBackend`] instance to delegate [`BackendRequest`] to + backend: Backend, + + _phantom: std::marker::PhantomData, +} + +/// A bridge trait between [`StatelessEncoder`] and codec specific backend trait (eg. +/// [`h264::StatelessH264EncoderBackend`] or [`vp9::StatelessVP9EncoderBackend`]). +/// Accepts [`Request`] and is responsible for adding resutling [`BackendPromise`] to +/// [`StatelessEncoder`] internal queues and decrementing the internal predictor frame counter if +/// the backend moved the frame outside predictor ownership. +pub trait StatelessEncoderExecute +where + Backend: StatelessVideoEncoderBackend, + Codec: StatelessCodec + StatelessCodecSpecific, +{ + fn execute(&mut self, request: Request) -> EncodeResult<()>; +} + +impl StatelessEncoder +where + Codec: StatelessCodec + StatelessCodecSpecific, + Backend: StatelessVideoEncoderBackend, + Self: StatelessEncoderExecute, +{ + fn new( + backend: Backend, + mode: BlockingMode, + predictor: BoxPredictor, + ) -> EncodeResult { + Ok(Self { + backend, + predictor, + predictor_frame_count: 0, + coded_queue: Default::default(), + output_queue: OutputQueue::new(mode), + recon_queue: OutputQueue::new(mode), + _phantom: Default::default(), + }) + } + + fn poll_pending(&mut self, mode: BlockingMode) -> EncodeResult<()> { + // Poll the output queue once and then continue polling while new promise is submitted + while let Some(coded) = self.output_queue.poll(mode)? { + self.coded_queue.push_back(coded); + } + + while let Some(recon) = self.recon_queue.poll(mode)? { + let requests = self.predictor.reconstructed(recon)?; + if requests.is_empty() { + // No promise was submitted, therefore break + break; + } + + for request in requests { + self.execute(request)?; + } + } + + Ok(()) + } +} + +impl StatelessVideoEncoder + for StatelessEncoder +where + Codec: StatelessCodec + StatelessCodecSpecific, + Backend: StatelessVideoEncoderBackend, + Backend: StatelessEncoderBackendImport, + Self: StatelessEncoderExecute, +{ + fn encode(&mut self, metadata: FrameMetadata, handle: Handle) -> EncodeResult<()> { + log::trace!( + "encode: timestamp={} layout={:?}", + metadata.timestamp, + metadata.layout + ); + + // Import `handle` to backends representation + let backend_pic = self.backend.import_picture(&metadata, handle)?; + + // Increase the number of frames that predictor holds, before handing one to it + self.predictor_frame_count += 1; + + // Ask predictor to decide on the next move and execute it + let requests = self.predictor.new_frame(backend_pic, metadata)?; + for request in requests { + self.execute(request)?; + } + + Ok(()) + } + + fn drain(&mut self) -> EncodeResult<()> { + log::trace!("currently predictor holds {}", self.predictor_frame_count); + + // Drain the predictor + while self.predictor_frame_count > 0 || !self.recon_queue.is_empty() { + if self.output_queue.is_empty() && self.recon_queue.is_empty() { + // The OutputQueue is empty and predictor holds frames, force it to yield a request + // to empty it's internal queue. + let requests = self.predictor.drain()?; + if requests.is_empty() { + log::error!("failed to drain predictor, no request was returned"); + return Err(EncodeError::InvalidInternalState); + } + + for request in requests { + self.execute(request)?; + } + } + + self.poll_pending(BlockingMode::Blocking)?; + } + + // There are still some requests being processed. Continue on polling them. + while !self.output_queue.is_empty() { + self.poll_pending(BlockingMode::Blocking)?; + } + + Ok(()) + } + + fn poll(&mut self) -> EncodeResult> { + // Poll on output queue without blocking and try to dueue from coded queue + self.poll_pending(BlockingMode::NonBlocking)?; + Ok(self.coded_queue.pop_front()) + } +} diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs index 72e4b079..d44a4d89 100644 --- a/src/encoder/stateless/h264.rs +++ b/src/encoder/stateless/h264.rs @@ -2,7 +2,6 @@ // 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::codec::h264::parser::Level; @@ -15,13 +14,13 @@ use crate::encoder::stateless::h264::predictor::PredictionStructure; use crate::encoder::stateless::BackendPromise; use crate::encoder::stateless::EncodeResult; use crate::encoder::stateless::FrameMetadata; -use crate::encoder::stateless::OutputQueue; 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::StatelessEncoderBackendImport; -use crate::encoder::stateless::StatelessVideoEncoder; +use crate::encoder::stateless::StatelessEncoderExecute; use crate::encoder::stateless::StatelessVideoEncoderBackend; use crate::encoder::Bitrate; use crate::encoder::CodedBitstreamBuffer; @@ -73,7 +72,7 @@ pub enum IsReference { } #[derive(Clone, Debug)] -pub(crate) struct DpbEntryMeta { +pub struct DpbEntryMeta { /// Picture order count poc: u16, frame_num: u32, @@ -216,64 +215,14 @@ pub trait StatelessH264EncoderBackend: StatelessVideoEncoderBackend { ) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)>; } -pub struct StatelessEncoder +impl StatelessEncoderExecute + for StatelessEncoder where - B: StatelessH264EncoderBackend, - B::Picture: 'static, - B::Reconstructed: 'static, + Backend: StatelessH264EncoderBackend, { - /// Pending slice output promise queue - output_queue: OutputQueue>, - - /// Pending reconstructed pictures promise queue - recon_queue: OutputQueue>, - - /// [`Predictor`] instance responsible for the encoder decision making - predictor: Box< - dyn Predictor< - B::Picture, - DpbEntry, - BackendRequest, - >, - >, - - /// Pending [`CodedBitstreamBuffer`]s to be polled by the user - coded_queue: VecDeque, - - /// Number of the currently held frames by the predictor - predictor_frame_count: usize, - - /// [`StatelessH264EncoderBackend`] instance to delegate [`BackendRequest`] to - backend: B, - - _phantom: std::marker::PhantomData, -} - -impl StatelessEncoder -where - B: StatelessH264EncoderBackend, - B::Picture: 'static, - B::Reconstructed: 'static, -{ - fn new(backend: B, config: EncoderConfig, mode: BlockingMode) -> EncodeResult { - let predictor: Box> = match config.pred_structure { - PredictionStructure::LowDelay { .. } => Box::new(LowDelay::new(config)), - }; - - Ok(Self { - backend, - predictor, - predictor_frame_count: 0, - coded_queue: Default::default(), - output_queue: OutputQueue::new(mode), - recon_queue: OutputQueue::new(mode), - _phantom: Default::default(), - }) - } - fn execute( &mut self, - request: BackendRequest, + request: BackendRequest, ) -> EncodeResult<()> { let meta = request.input_meta.clone(); let dpb_meta = request.dpb_meta.clone(); @@ -295,86 +244,18 @@ where Ok(()) } - - fn poll_pending(&mut self, mode: BlockingMode) -> EncodeResult<()> { - // Poll the output queue once and then continue polling while new promise is submitted - while let Some(coded) = self.output_queue.poll(mode)? { - self.coded_queue.push_back(coded); - } - - while let Some(recon) = self.recon_queue.poll(mode)? { - let requests = self.predictor.reconstructed(recon)?; - if requests.is_empty() { - // No promise was submitted, therefore break - break; - } - - for request in requests { - self.execute(request)?; - } - } - - Ok(()) - } } -impl StatelessVideoEncoder for StatelessEncoder +impl StatelessEncoder where - B: StatelessH264EncoderBackend, - B: StatelessEncoderBackendImport, + Backend: StatelessH264EncoderBackend, + Backend: StatelessEncoderBackendImport, { - fn encode(&mut self, metadata: FrameMetadata, handle: H) -> EncodeResult<()> { - log::trace!( - "encode: timestamp={} layout={:?}", - metadata.timestamp, - metadata.layout - ); - - // Import `handle` to backends representation - let backend_pic = self.backend.import_picture(&metadata, handle)?; - - // Increase the number of frames that predictor holds, before handing one to it - self.predictor_frame_count += 1; - - // Ask predictor to decide on the next move and execute it - let requests = self.predictor.new_frame(backend_pic, metadata)?; - for request in requests { - self.execute(request)?; - } - - Ok(()) - } - - fn drain(&mut self) -> EncodeResult<()> { - log::trace!("currently predictor holds {}", self.predictor_frame_count); - - // Drain the predictor - while self.predictor_frame_count > 0 || !self.recon_queue.is_empty() { - if self.output_queue.is_empty() && self.recon_queue.is_empty() { - // The OutputQueue is empty and predictor holds frames, force it to yield a request - // to empty it's internal queue. - let requests = self.predictor.drain()?; - self.predictor_frame_count -= requests.len(); - - for request in requests { - self.execute(request)?; - } - } - - self.poll_pending(BlockingMode::Blocking)?; - } - - // There are still some requests being processed. Continue on polling them. - while !self.output_queue.is_empty() { - self.poll_pending(BlockingMode::Blocking)?; - } - - Ok(()) - } + fn new_h264(backend: Backend, config: EncoderConfig, mode: BlockingMode) -> EncodeResult { + let predictor: Box> = match config.pred_structure { + PredictionStructure::LowDelay { .. } => Box::new(LowDelay::new(config)), + }; - fn poll(&mut self) -> EncodeResult> { - // Poll on output queue without blocking and try to dueue from coded queue - self.poll_pending(BlockingMode::NonBlocking)?; - Ok(self.coded_queue.pop_front()) + Self::new(backend, mode, predictor) } } diff --git a/src/encoder/stateless/h264/vaapi.rs b/src/encoder/stateless/h264/vaapi.rs index 7f2a460b..3d00bcad 100644 --- a/src/encoder/stateless/h264/vaapi.rs +++ b/src/encoder/stateless/h264/vaapi.rs @@ -37,7 +37,6 @@ use crate::codec::h264::parser::Profile; use crate::codec::h264::parser::SliceHeader; use crate::codec::h264::parser::Sps; use crate::encoder::stateless::h264::BackendRequest; -use crate::encoder::stateless::h264::Bitrate; use crate::encoder::stateless::h264::DpbEntry; use crate::encoder::stateless::h264::DpbEntryMeta; use crate::encoder::stateless::h264::EncoderConfig; @@ -50,6 +49,7 @@ use crate::encoder::stateless::ReadyPromise; use crate::encoder::stateless::StatelessBackendError; use crate::encoder::stateless::StatelessBackendResult; use crate::encoder::stateless::StatelessVideoEncoderBackend; +use crate::encoder::Bitrate; use crate::BlockingMode; use crate::Fourcc; use crate::Resolution; @@ -59,7 +59,7 @@ type Request<'l, H> = BackendRequest; impl StatelessVideoEncoderBackend for VaapiBackend where M: SurfaceMemoryDescriptor, - H: std::borrow::Borrow>, + H: std::borrow::Borrow> + 'static, { type Picture = H; type Reconstructed = Reconstructed; @@ -70,7 +70,7 @@ where impl VaapiBackend where M: SurfaceMemoryDescriptor, - H: std::borrow::Borrow>, + H: std::borrow::Borrow> + 'static, { /// Builds an invalid [`libva::PictureH264`]. This is usually a place /// holder to fill staticly sized array. @@ -360,7 +360,7 @@ where impl StatelessH264EncoderBackend for VaapiBackend where M: SurfaceMemoryDescriptor, - H: Borrow>, + H: Borrow> + 'static, { fn encode_slice( &mut self, @@ -423,10 +423,10 @@ where } } -impl StatelessEncoder> +impl StatelessEncoder> where M: SurfaceMemoryDescriptor, - H: Borrow>, + H: Borrow> + 'static, { pub fn new_vaapi( display: Rc, @@ -455,7 +455,8 @@ where bitrate_control, low_power, )?; - Self::new(backend, config, blocking_mode) + + Self::new_h264(backend, config, blocking_mode) } } @@ -637,7 +638,7 @@ pub(super) mod tests { #[ignore] fn test_vaapi_encoder() { type VaapiH264Encoder<'l> = - StatelessEncoder, VaapiBackend<(), PooledVaSurface<()>>>; + StatelessEncoder, VaapiBackend<(), PooledVaSurface<()>>>; const WIDTH: usize = 512; const HEIGHT: usize = 512; From 3c53cb37f013a903deabd20a8f033f8aae230aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Thu, 22 Feb 2024 16:24:03 +0100 Subject: [PATCH 3/7] encoder: h264: Export SlicePromise as BitstreamPromise --- src/encoder/stateless.rs | 32 +++++++++++++++++++++++++++++ src/encoder/stateless/h264.rs | 38 +++-------------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index c5a3d95f..6610b3c6 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -70,6 +70,38 @@ impl BackendPromise for ReadyPromise { } } +/// Wrapper type for [`BackendPromise>`], with additional +/// metadata. +pub struct BitstreamPromise

+where + P: BackendPromise>, +{ + /// Slice data and reconstructed surface promise + bitstream: P, + + /// Input frame metadata, for [`CodedBitstreamBuffer`] + meta: FrameMetadata, +} + +impl

BackendPromise for BitstreamPromise

+where + P: BackendPromise>, +{ + type Output = CodedBitstreamBuffer; + + fn is_ready(&self) -> bool { + self.bitstream.is_ready() + } + + fn sync(self) -> StatelessBackendResult { + let coded_data = self.bitstream.sync()?; + + log::trace!("synced bitstream size={}", coded_data.len()); + + Ok(CodedBitstreamBuffer::new(self.meta, coded_data)) + } +} + /// Internal structure representing all current processing represented using promises and allowing /// polling for finished promises. pub(crate) struct OutputQueue diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs index d44a4d89..2e6cbef6 100644 --- a/src/encoder/stateless/h264.rs +++ b/src/encoder/stateless/h264.rs @@ -12,6 +12,7 @@ use crate::codec::h264::parser::Sps; use crate::encoder::stateless::h264::predictor::LowDelay; use crate::encoder::stateless::h264::predictor::PredictionStructure; use crate::encoder::stateless::BackendPromise; +use crate::encoder::stateless::BitstreamPromise; use crate::encoder::stateless::EncodeResult; use crate::encoder::stateless::FrameMetadata; use crate::encoder::stateless::Predictor; @@ -23,7 +24,6 @@ use crate::encoder::stateless::StatelessEncoderBackendImport; use crate::encoder::stateless::StatelessEncoderExecute; use crate::encoder::stateless::StatelessVideoEncoderBackend; use crate::encoder::Bitrate; -use crate::encoder::CodedBitstreamBuffer; use crate::BlockingMode; use crate::Resolution; @@ -121,38 +121,6 @@ pub struct BackendRequest { coded_output: Vec, } -/// Wrapper type for [`BackendPromise>`], with additional -/// metadata. -pub struct SlicePromise

-where - P: BackendPromise>, -{ - /// Slice data and reconstructed surface promise - bitstream: P, - - /// Input frame metadata, for [`CodedBitstreamBuffer`] - meta: FrameMetadata, -} - -impl

BackendPromise for SlicePromise

-where - P: BackendPromise>, -{ - type Output = CodedBitstreamBuffer; - - fn is_ready(&self) -> bool { - self.bitstream.is_ready() - } - - fn sync(self) -> StatelessBackendResult { - let coded_data = self.bitstream.sync()?; - - log::trace!("synced bitstream size={}", coded_data.len()); - - Ok(CodedBitstreamBuffer::new(self.meta, coded_data)) - } -} - /// Wrapper type for [`BackendPromise`], with additional /// metadata. pub struct ReferencePromise

@@ -198,7 +166,7 @@ where type Request = BackendRequest; - type CodedPromise = SlicePromise; + type CodedPromise = BitstreamPromise; type ReferencePromise = ReferencePromise; } @@ -234,7 +202,7 @@ where let (recon, bitstream) = self.backend.encode_slice(request)?; // Wrap promise from backend with headers and metadata - let slice_promise = SlicePromise { bitstream, meta }; + let slice_promise = BitstreamPromise { bitstream, meta }; self.output_queue.add_promise(slice_promise); From bf3c1abb2a6509f39ba7eb65901b0b4cfceaf700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Wed, 21 Feb 2024 16:34:10 +0100 Subject: [PATCH 4/7] 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. --- src/encoder/stateless.rs | 1 + src/encoder/stateless/vp9.rs | 147 +++++++ src/encoder/stateless/vp9/predictor.rs | 169 ++++++++ src/encoder/stateless/vp9/vaapi.rs | 556 +++++++++++++++++++++++++ 4 files changed, 873 insertions(+) create mode 100644 src/encoder/stateless/vp9.rs create mode 100644 src/encoder/stateless/vp9/predictor.rs create mode 100644 src/encoder/stateless/vp9/vaapi.rs diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index 6610b3c6..33ba0cef 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -12,6 +12,7 @@ use crate::encoder::FrameMetadata; use crate::BlockingMode; pub mod h264; +pub mod vp9; #[derive(Error, Debug)] pub enum StatelessBackendError { diff --git a/src/encoder/stateless/vp9.rs b/src/encoder/stateless/vp9.rs new file mode 100644 index 00000000..5a47aff9 --- /dev/null +++ b/src/encoder/stateless/vp9.rs @@ -0,0 +1,147 @@ +// 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::BitstreamPromise; +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::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 { + /// The frame will be used for single prediction + Single, + /// The frame will be used for compound prediction + Compound, + /// The frame will be used for both single and compound prediction + Hybrid, +} + +pub struct BackendRequest { + header: Header, + + /// Input frame to be encoded + input: P, + + /// Input frame metadata + input_meta: FrameMetadata, + + /// Reference frames + last_frame_ref: Option<(Rc, ReferenceUse)>, + golden_frame_ref: Option<(Rc, ReferenceUse)>, + altref_frame_ref: Option<(Rc, ReferenceUse)>, + + /// Current expected bitrate + bitrate: Bitrate, + + /// 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, +} + +pub struct VP9; + +impl StatelessCodecSpecific for VP9 +where + Backend: StatelessVideoEncoderBackend, +{ + type Reference = Backend::Reconstructed; + + type Request = BackendRequest; + + type CodedPromise = BitstreamPromise; + + type ReferencePromise = Backend::ReconPromise; +} + +impl StatelessCodec for VP9 {} + +pub trait StatelessVP9EncoderBackend: StatelessVideoEncoderBackend { + fn encode_frame( + &mut self, + request: BackendRequest, + ) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)>; +} + +impl StatelessEncoderExecute + for StatelessEncoder +where + Backend: StatelessVP9EncoderBackend, +{ + fn execute( + &mut self, + request: BackendRequest, + ) -> 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 = BitstreamPromise { bitstream, meta }; + + self.output_queue.add_promise(slice_promise); + + self.recon_queue.add_promise(recon); + + Ok(()) + } +} + +impl StatelessEncoder +where + Backend: StatelessVP9EncoderBackend, +{ + fn new_vp9(backend: Backend, config: EncoderConfig, mode: BlockingMode) -> EncodeResult { + let predictor: Box> = match config.pred_structure { + PredictionStructure::LowDelay { .. } => Box::new(LowDelay::new(config)), + }; + + Self::new(backend, mode, predictor) + } +} diff --git a/src/encoder/stateless/vp9/predictor.rs b/src/encoder/stateless/vp9/predictor.rs new file mode 100644 index 00000000..0648b51b --- /dev/null +++ b/src/encoder/stateless/vp9/predictor.rs @@ -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 relying solely on the last frame. + LowDelay { limit: u16 }, +} + +/// See [`PredictionStructure::LowDelay`] +pub(super) struct LowDelay { + queue: VecDeque<(P, FrameMetadata)>, + + references: VecDeque>, + + counter: usize, + + /// Encoder config + config: EncoderConfig, +} + +impl LowDelay { + pub(super) fn new(config: EncoderConfig) -> Self { + Self { + queue: Default::default(), + references: Default::default(), + counter: 0, + config, + } + } + + fn request_keyframe( + &mut self, + input: P, + input_meta: FrameMetadata, + ) -> EncodeResult>> { + 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, + bitrate: self.config.bitrate.clone(), + coded_output: Vec::new(), + }; + + self.counter += 1; + + Ok(vec![request]) + } + + fn request_interframe( + &mut self, + input: P, + input_meta: FrameMetadata, + ) -> EncodeResult>> { + 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, + bitrate: self.config.bitrate.clone(), + coded_output: Vec::new(), + }; + + self.counter += 1; + self.references.clear(); + + Ok(vec![request]) + } + + fn next_request(&mut self) -> EncodeResult>> { + 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 Predictor> for LowDelay { + fn new_frame( + &mut self, + input: P, + frame_metadata: FrameMetadata, + ) -> EncodeResult>> { + // 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>> { + // 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>> { + // [`LowDelay`] will not hold any frames, therefore the drain function shall never be called. + Err(EncodeError::InvalidInternalState) + } +} diff --git a/src/encoder/stateless/vp9/vaapi.rs b/src/encoder/stateless/vp9/vaapi.rs new file mode 100644 index 00000000..9aa2ee83 --- /dev/null +++ b/src/encoder/stateless/vp9/vaapi.rs @@ -0,0 +1,556 @@ +// 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::any::Any; +use std::borrow::Borrow; +use std::rc::Rc; + +use anyhow::Context; +use libva::constants::VA_INVALID_SURFACE; +use libva::BufferType; +use libva::Display; +use libva::EncPictureParameter; +use libva::EncPictureParameterBufferVP9; +use libva::EncSequenceParameter; +use libva::EncSequenceParameterBufferVP9; +use libva::Picture; +use libva::Surface; +use libva::SurfaceMemoryDescriptor; +use libva::VAProfile::VAProfileVP9Profile0; +use libva::VP9EncPicFlags; +use libva::VP9EncRefFlags; + +use crate::backend::vaapi::encoder::CodedOutputPromise; +use crate::backend::vaapi::encoder::Reconstructed; +use crate::backend::vaapi::encoder::VaapiBackend; +use crate::codec::vp9::parser::FrameType; +use crate::codec::vp9::parser::InterpolationFilter; +use crate::codec::vp9::parser::ALTREF_FRAME; +use crate::codec::vp9::parser::GOLDEN_FRAME; +use crate::codec::vp9::parser::LAST_FRAME; +use crate::codec::vp9::parser::NUM_REF_FRAMES; +use crate::encoder::stateless::vp9::BackendRequest; +use crate::encoder::stateless::vp9::EncoderConfig; +use crate::encoder::stateless::vp9::ReferenceUse; +use crate::encoder::stateless::vp9::StatelessEncoder; +use crate::encoder::stateless::vp9::StatelessVP9EncoderBackend; +use crate::encoder::stateless::vp9::VP9; +use crate::encoder::stateless::EncodeResult; +use crate::encoder::stateless::ReadyPromise; +use crate::encoder::stateless::StatelessBackendResult; +use crate::encoder::stateless::StatelessVideoEncoderBackend; +use crate::encoder::Bitrate; +use crate::BlockingMode; +use crate::Fourcc; +use crate::Resolution; + +impl StatelessVideoEncoderBackend for VaapiBackend +where + M: SurfaceMemoryDescriptor, + Handle: Borrow>, +{ + type Picture = Handle; + type Reconstructed = Reconstructed; + type CodedPromise = CodedOutputPromise; + type ReconPromise = ReadyPromise; +} + +impl StatelessVP9EncoderBackend for VaapiBackend +where + M: SurfaceMemoryDescriptor, + Handle: Borrow>, +{ + fn encode_frame( + &mut self, + request: BackendRequest, + ) -> StatelessBackendResult<(Self::ReconPromise, Self::CodedPromise)> { + // Coded buffer size multiplier. It's inteded to give head room for the encoder. + const CODED_SIZE_MUL: usize = 2; + + let coded_buf = self + .context() + .create_enc_coded(CODED_SIZE_MUL * request.bitrate.target() as usize)?; + + let recon = self.new_scratch_picture()?; + + let seq_param = BufferType::EncSequenceParameter(EncSequenceParameter::VP9( + EncSequenceParameterBufferVP9::new( + request.input_meta.display_resolution.width, + request.input_meta.display_resolution.height, + 0, + 10, + 2000, + request.bitrate.target() as u32, + 1024, + ), + )); + + // From va_enc_vp9.h `ref_frame_ctrl_l0` documentation + const LAST_FRAME_AS_REF: u32 = 0x01; + const GOLDEN_FRAME_AS_REF: u32 = 0x02; + const ALTREF_FRAME_AS_REF: u32 = 0x04; + + let mut references = Vec::>::new(); + let mut reference_frames = [VA_INVALID_SURFACE; NUM_REF_FRAMES]; + + let mut ref_frame_ctrl_l0 = 0; + let mut ref_frame_ctrl_l1 = 0; + + let refs = [ + (&request.last_frame_ref, LAST_FRAME - 1, LAST_FRAME_AS_REF), + ( + &request.golden_frame_ref, + GOLDEN_FRAME - 1, + GOLDEN_FRAME_AS_REF, + ), + ( + &request.altref_frame_ref, + ALTREF_FRAME - 1, + ALTREF_FRAME_AS_REF, + ), + ]; + + for (r, ref_idx, ref_ctrl) in refs { + let Some((ref_frame, ref_use)) = r else { + continue; + }; + + reference_frames[request.header.ref_frame_idx[ref_idx] as usize] = + ref_frame.surface_id(); + references.push(ref_frame.clone()); + + match ref_use { + ReferenceUse::Single => ref_frame_ctrl_l0 |= ref_ctrl, + ReferenceUse::Compound => ref_frame_ctrl_l1 |= ref_ctrl, + ReferenceUse::Hybrid => { + ref_frame_ctrl_l0 |= ref_ctrl; + ref_frame_ctrl_l1 |= ref_ctrl; + } + } + } + + let force_kf = + request.header.frame_type == FrameType::KeyFrame || request.input_meta.force_keyframe; + + let ref_flags = VP9EncRefFlags::new( + // Force keyframe if requested + force_kf as u32, + ref_frame_ctrl_l0, + ref_frame_ctrl_l1, + request.header.ref_frame_idx[LAST_FRAME - 1] as u32, + request.header.ref_frame_sign_bias[LAST_FRAME] as u32, + request.header.ref_frame_idx[GOLDEN_FRAME - 1] as u32, + request.header.ref_frame_sign_bias[GOLDEN_FRAME] as u32, + request.header.ref_frame_idx[ALTREF_FRAME - 1] as u32, + request.header.ref_frame_sign_bias[ALTREF_FRAME] as u32, + 0, + ); + + // From va_enc_vp9.h `mcomp_filter_type` documentation + let mcomp_filter_type = match request.header.interpolation_filter { + InterpolationFilter::EightTap => 0, + InterpolationFilter::EightTapSmooth => 1, + InterpolationFilter::EightTapSharp => 2, + InterpolationFilter::Bilinear => 3, + InterpolationFilter::Switchable => 4, + }; + + // TODO: show_existing_frame + assert!(!request.header.show_existing_frame); + + // From va_enc_vp9.h `comp_prediction_mode` documentation + const PRED_MODE_SINGLE: u32 = 0x00; + // const PRED_MODE_COMPOUND: u32 = 0x01; + const PRED_MODE_HYBRID: u32 = 0x02; + + let comp_prediction_mode = if ref_frame_ctrl_l1 != 0 { + // Use hybrid prediction mode if any future reference frame are enabled + PRED_MODE_HYBRID + } else { + PRED_MODE_SINGLE + }; + + let pic_flags = VP9EncPicFlags::new( + request.header.frame_type as u32, + request.header.show_frame as u32, + request.header.error_resilient_mode as u32, + request.header.intra_only as u32, + request.header.allow_high_precision_mv as u32, + mcomp_filter_type, + request.header.frame_parallel_decoding_mode as u32, + request.header.reset_frame_context as u32, + request.header.refresh_frame_context as u32, + request.header.frame_context_idx as u32, + request.header.seg.enabled as u32, + request.header.seg.temporal_update as u32, + request.header.seg.update_map as u32, + request.header.lossless as u32, + comp_prediction_mode, + 1, + 0, + ); + + let pic_param = BufferType::EncPictureParameter(EncPictureParameter::VP9( + EncPictureParameterBufferVP9::new( + request.header.width, + request.header.height, + request.header.render_width, + request.header.render_height, + recon.surface_id(), + reference_frames, + coded_buf.id(), + &ref_flags, + &pic_flags, + request.header.refresh_frame_flags, + request.header.quant.base_q_idx, + request.header.quant.delta_q_y_dc, + request.header.quant.delta_q_uv_ac, + request.header.quant.delta_q_uv_dc, + request.header.lf.level, + request.header.lf.sharpness, + request.header.lf.ref_deltas, + request.header.lf.mode_deltas, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + request.header.tile_rows_log2, + request.header.tile_cols_log2, + // Don't skip frames + 0, + 0, + 0, + ), + )); + + let mut picture = Picture::new( + request.input_meta.timestamp, + Rc::clone(self.context()), + request.input, + ); + + picture.add_buffer(self.context().create_buffer(seq_param)?); + picture.add_buffer(self.context().create_buffer(pic_param)?); + + // Start processing the picture encoding + let picture = picture.begin().context("picture begin")?; + let picture = picture.render().context("picture render")?; + let picture = picture.end().context("picture end")?; + + // libva will handle the synchronization of reconstructed surface with implicit fences. + // Therefore return the reconstructed frame immediately. + let reference_promise = ReadyPromise::from(recon); + + let bitstream_promise = + CodedOutputPromise::new(picture, references, coded_buf, request.coded_output); + + Ok((reference_promise, bitstream_promise)) + } +} + +impl StatelessEncoder> +where + M: SurfaceMemoryDescriptor, + Handle: Borrow>, +{ + pub fn new_vaapi( + display: Rc, + config: EncoderConfig, + fourcc: Fourcc, + coded_size: Resolution, + low_power: bool, + blocking_mode: BlockingMode, + ) -> EncodeResult { + let bitrate_control = match config.bitrate { + Bitrate::Constant(_) => libva::constants::VA_RC_CBR, + }; + + let backend = VaapiBackend::new( + display, + VAProfileVP9Profile0, + fourcc, + coded_size, + bitrate_control, + low_power, + )?; + Self::new_vp9(backend, config, blocking_mode) + } +} + +#[cfg(test)] +pub(super) mod tests { + use std::rc::Rc; + + use libva::constants::VA_RT_FORMAT_YUV420; + use libva::Display; + use libva::UsageHint; + use libva::VAEntrypoint::VAEntrypointEncSliceLP; + use libva::VAProfile::VAProfileVP9Profile0; + + use super::*; + use crate::backend::vaapi::encoder::tests::upload_test_frame; + use crate::backend::vaapi::encoder::tests::TestFrameGenerator; + use crate::backend::vaapi::encoder::VaapiBackend; + use crate::backend::vaapi::surface_pool::PooledVaSurface; + use crate::backend::vaapi::surface_pool::VaSurfacePool; + use crate::codec::vp9::parser::Header; + use crate::decoder::FramePool; + use crate::encoder::stateless::simple_encode_loop; + use crate::encoder::stateless::vp9::BackendRequest; + use crate::encoder::stateless::vp9::EncoderConfig; + use crate::encoder::stateless::vp9::StatelessEncoder; + use crate::encoder::stateless::BackendPromise; + use crate::encoder::stateless::StatelessEncoderBackendImport; + use crate::encoder::FrameMetadata; + use crate::utils::IvfFileHeader; + use crate::utils::IvfFrameHeader; + use crate::FrameLayout; + use crate::PlaneLayout; + use crate::Resolution; + + #[test] + // Ignore this test by default as it requires libva-compatible hardware. + #[ignore] + fn test_simple_encode_frame() { + type Descriptor = (); + type Surface = libva::Surface; + const WIDTH: u32 = 256; + const HEIGHT: u32 = 256; + let fourcc = b"NV12".into(); + + let frame_layout = FrameLayout { + format: (fourcc, 0), + size: Resolution { + width: WIDTH, + height: HEIGHT, + }, + planes: vec![ + PlaneLayout { + buffer_index: 0, + offset: 0, + stride: WIDTH as usize, + }, + PlaneLayout { + buffer_index: 0, + offset: (WIDTH * HEIGHT) as usize, + stride: WIDTH as usize, + }, + ], + }; + + let display = Display::open().unwrap(); + let entrypoints = display + .query_config_entrypoints(VAProfileVP9Profile0) + .unwrap(); + let low_power = entrypoints.contains(&VAEntrypointEncSliceLP); + + let mut backend = VaapiBackend::::new( + Rc::clone(&display), + VAProfileVP9Profile0, + fourcc, + Resolution { + width: WIDTH, + height: HEIGHT, + }, + libva::constants::VA_RC_CBR, + low_power, + ) + .unwrap(); + + let mut surfaces = display + .create_surfaces( + VA_RT_FORMAT_YUV420, + Some(frame_layout.format.0 .0), + WIDTH, + HEIGHT, + Some(UsageHint::USAGE_HINT_ENCODER), + vec![()], + ) + .unwrap(); + + let surface = surfaces.pop().unwrap(); + + upload_test_frame(&display, &surface, 0.0); + + let input_meta = FrameMetadata { + display_resolution: Resolution { + width: WIDTH, + height: HEIGHT, + }, + layout: frame_layout, + force_keyframe: false, + timestamp: 0, + }; + + let pic = backend.import_picture(&input_meta, surface).unwrap(); + + let header = Header { + frame_type: FrameType::KeyFrame, + show_frame: true, + error_resilient_mode: false, + width: WIDTH, + height: HEIGHT, + render_and_frame_size_different: false, + render_width: WIDTH, + render_height: HEIGHT, + intra_only: true, + refresh_frame_flags: 0x01, + ref_frame_idx: [0, 0, 0], + + ..Default::default() + }; + + let request = BackendRequest { + header, + input: pic, + input_meta, + last_frame_ref: None, + golden_frame_ref: None, + altref_frame_ref: None, + bitrate: Bitrate::Constant(30_000), + coded_output: Vec::new(), + }; + + let (_, output) = backend.encode_frame(request).unwrap(); + let output = output.sync().unwrap(); + + let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true"); + if write_to_file { + use std::io::Write; + + let mut out = std::fs::File::create("test_simple_encode_frame.vp9.ivf").unwrap(); + + let file_header = + IvfFileHeader::new(IvfFileHeader::CODEC_VP9, WIDTH as u16, HEIGHT as u16, 30, 1); + + let frame_header = IvfFrameHeader { + frame_size: output.len() as u32, + timestamp: 0, + }; + + file_header.writo_into(&mut out).unwrap(); + frame_header.writo_into(&mut out).unwrap(); + + out.write_all(&output).unwrap(); + out.flush().unwrap(); + } + } + + #[test] + // Ignore this test by default as it requires libva-compatible hardware. + #[ignore] + fn test_vaapi_encoder() { + type VaapiVp9Encoder<'l> = + StatelessEncoder, VaapiBackend<(), PooledVaSurface<()>>>; + + const WIDTH: usize = 512; + const HEIGHT: usize = 512; + const FRAME_COUNT: u64 = 100; + + let _ = env_logger::try_init(); + + let display = libva::Display::open().unwrap(); + let entrypoints = display + .query_config_entrypoints(VAProfileVP9Profile0) + .unwrap(); + let low_power = entrypoints.contains(&VAEntrypointEncSliceLP); + + let config = EncoderConfig { + bitrate: Bitrate::Constant(200_000), + framerate: 30, + resolution: Resolution { + width: WIDTH as u32, + height: HEIGHT as u32, + }, + ..Default::default() + }; + + let frame_layout = FrameLayout { + format: (b"NV12".into(), 0), + size: Resolution { + width: WIDTH as u32, + height: HEIGHT as u32, + }, + planes: vec![ + PlaneLayout { + buffer_index: 0, + offset: 0, + stride: WIDTH, + }, + PlaneLayout { + buffer_index: 0, + offset: WIDTH * HEIGHT, + stride: WIDTH, + }, + ], + }; + + let mut encoder = VaapiVp9Encoder::new_vaapi( + Rc::clone(&display), + config, + frame_layout.format.0, + frame_layout.size, + low_power, + BlockingMode::Blocking, + ) + .unwrap(); + + let mut pool = VaSurfacePool::new( + Rc::clone(&display), + VA_RT_FORMAT_YUV420, + Some(UsageHint::USAGE_HINT_ENCODER), + Resolution { + width: WIDTH as u32, + height: HEIGHT as u32, + }, + ); + + pool.add_frames(vec![(); 16]).unwrap(); + + let mut frame_producer = TestFrameGenerator::new( + FRAME_COUNT, + display, + pool, + Resolution { + width: WIDTH as u32, + height: HEIGHT as u32, + }, + frame_layout, + ); + + let mut bitstream = Vec::new(); + + let file_header = IvfFileHeader::new( + IvfFileHeader::CODEC_VP9, + WIDTH as u16, + HEIGHT as u16, + 30, + FRAME_COUNT as u32, + ); + + file_header.writo_into(&mut bitstream).unwrap(); + + simple_encode_loop(&mut encoder, &mut frame_producer, |coded| { + let header = IvfFrameHeader { + timestamp: coded.metadata.timestamp, + frame_size: coded.bitstream.len() as u32, + }; + + header.writo_into(&mut bitstream).unwrap(); + bitstream.extend(coded.bitstream); + }) + .unwrap(); + + let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true"); + if write_to_file { + use std::io::Write; + let mut out = std::fs::File::create("test_vaapi_encoder.vp9.ivf").unwrap(); + out.write_all(&bitstream).unwrap(); + out.flush().unwrap(); + } + } +} From a1c99e8fc95287f3596eb4b5b0c70e906126f8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Tue, 13 Feb 2024 16:08:54 +0100 Subject: [PATCH 5/7] ccenc: Add VP9 support This commit adds VP9 encoding option to ccenc --- examples/ccenc.rs | 139 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 17 deletions(-) diff --git a/examples/ccenc.rs b/examples/ccenc.rs index 551aa3c1..63c331d8 100644 --- a/examples/ccenc.rs +++ b/examples/ccenc.rs @@ -8,23 +8,47 @@ use std::io::Read; use std::io::Write; use std::path::PathBuf; use std::rc::Rc; +use std::str::FromStr; use argh::FromArgs; +use cros_codecs::backend::vaapi::encoder::VaapiBackend; +use cros_codecs::backend::vaapi::surface_pool::PooledVaSurface; use cros_codecs::backend::vaapi::surface_pool::VaSurfacePool; -use cros_codecs::codec::h264::parser::Profile; use cros_codecs::decoder::FramePool; -use cros_codecs::encoder::stateless::h264::EncoderConfig; +use cros_codecs::encoder::stateless::h264; use cros_codecs::encoder::stateless::h264::H264; +use cros_codecs::encoder::stateless::vp9; +use cros_codecs::encoder::stateless::vp9::VP9; use cros_codecs::encoder::stateless::StatelessEncoder; use cros_codecs::encoder::stateless::StatelessVideoEncoder; -use cros_codecs::encoder::Bitrate; use cros_codecs::encoder::FrameMetadata; +use cros_codecs::utils::IvfFileHeader; +use cros_codecs::utils::IvfFrameHeader; use cros_codecs::BlockingMode; use cros_codecs::Fourcc; use cros_codecs::FrameLayout; use cros_codecs::PlaneLayout; use cros_codecs::Resolution; +#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] +enum Codec { + #[default] + H264, + VP9, +} + +impl FromStr for Codec { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "h264" | "H264" => Ok(Self::H264), + "vp9" | "VP9" => Ok(Self::VP9), + _ => Err("unrecognized codec. Valid values: h264, vp9"), + } + } +} + /// Simple encoder #[derive(Debug, FromArgs)] struct Args { @@ -44,6 +68,10 @@ struct Args { #[argh(option)] count: usize, + /// codec + #[argh(option)] + codec: Option, + /// default quantization parameter #[argh(option)] default_qp: Option, @@ -127,24 +155,20 @@ fn upload_img( } } -fn main() { - env_logger::init(); - - let args: Args = argh::from_env(); - - let mut input = File::open(args.input).expect("error opening input file"); +type VaapiEncoder = + StatelessEncoder, VaapiBackend<(), PooledVaSurface<()>>>; +fn new_h264_vaapi_encoder( + args: &Args, + display: &Rc, +) -> Box>> { let resolution = Resolution { width: args.width, height: args.height, }; - let mut config = EncoderConfig { - bitrate: Bitrate::Constant(2_000_000_000), - profile: Profile::Baseline, - framerate: 30, + let mut config = h264::EncoderConfig { resolution, - ..Default::default() }; @@ -156,10 +180,41 @@ fn main() { config.framerate = framerate; } - let display = libva::Display::open().unwrap(); let fourcc = b"NV12".into(); - let mut encoder = StatelessEncoder::::new_vaapi( - Rc::clone(&display), + let encoder = VaapiEncoder::::new_vaapi( + Rc::clone(display), + config, + fourcc, + resolution, + args.low_power, + BlockingMode::Blocking, + ) + .expect("Unable to crate encoder"); + + Box::new(encoder) +} + +fn new_vp9_vaapi_encoder( + args: &Args, + display: &Rc, +) -> Box>> { + let resolution = Resolution { + width: args.width, + height: args.height, + }; + + let mut config = vp9::EncoderConfig { + resolution, + ..Default::default() + }; + + if let Some(framerate) = args.framerate { + config.framerate = framerate; + } + + let fourcc = b"NV12".into(); + let encoder = VaapiEncoder::::new_vaapi( + Rc::clone(display), config, fourcc, resolution, @@ -168,6 +223,25 @@ fn main() { ) .expect("Unable to crate encoder"); + Box::new(encoder) +} + +fn main() { + env_logger::init(); + + let args: Args = argh::from_env(); + + let mut input = File::open(&args.input).expect("error opening input file"); + + let display = libva::Display::open().unwrap(); + + let codec = args.codec.unwrap_or_default(); + + let mut encoder = match codec { + Codec::H264 => new_h264_vaapi_encoder(&args, &display), + Codec::VP9 => new_vp9_vaapi_encoder(&args, &display), + }; + let mut pool = VaSurfacePool::new( Rc::clone(&display), libva::constants::VA_RT_FORMAT_YUV420, @@ -184,6 +258,19 @@ fn main() { let mut output = args.output.map(|output| File::create(output).unwrap()); + if let Some(ref mut output) = output { + if codec == Codec::VP9 { + let hdr = IvfFileHeader::new( + IvfFileHeader::CODEC_VP9, + args.width as u16, + args.height as u16, + 30, + args.count as u32, + ); + hdr.writo_into(output).unwrap(); + } + } + let mut buf = vec![0u8; frame_size]; for i in 0..args.count { input.read_exact(&mut buf[..]).unwrap(); @@ -203,6 +290,15 @@ fn main() { encoder.encode(input_frame, handle).unwrap(); while let Some(coded) = encoder.poll().unwrap() { if let Some(ref mut output) = output { + if codec == Codec::VP9 { + let hdr = IvfFrameHeader { + timestamp: coded.metadata.timestamp, + frame_size: coded.bitstream.len() as u32, + }; + + hdr.writo_into(output).unwrap(); + } + output.write_all(&coded.bitstream).unwrap(); } } @@ -211,6 +307,15 @@ fn main() { encoder.drain().unwrap(); while let Some(coded) = encoder.poll().unwrap() { if let Some(ref mut output) = output { + if codec == Codec::VP9 { + let hdr = IvfFrameHeader { + timestamp: coded.metadata.timestamp, + frame_size: coded.bitstream.len() as u32, + }; + + hdr.writo_into(output).unwrap(); + } + output.write_all(&coded.bitstream).unwrap(); } } From a1208866fb39e55ecea96ec4b03c16eb15c3f96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Thu, 22 Feb 2024 15:49:00 +0100 Subject: [PATCH 6/7] utils: Add Ivf file writer tests --- src/utils.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index 351bda63..cbb1c1f8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -367,3 +367,80 @@ impl Drop for UserPtrFrame { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ivf_file_header() { + let mut hdr = IvfFileHeader { + version: 0, + codec: IvfFileHeader::CODEC_VP9, + width: 256, + height: 256, + framerate: 30_000, + timescale: 1_000, + frame_count: 1, + + ..Default::default() + }; + + let mut buf = Vec::new(); + hdr.writo_into(&mut buf).unwrap(); + + const EXPECTED: [u8; 32] = [ + 0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x00, 0x01, + 0x00, 0x01, 0x30, 0x75, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(&buf, &EXPECTED); + + hdr.width = 1920; + hdr.height = 800; + hdr.framerate = 24; + hdr.timescale = 1; + hdr.frame_count = 100; + + buf.clear(); + hdr.writo_into(&mut buf).unwrap(); + + const EXPECTED2: [u8; 32] = [ + 0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x80, 0x07, + 0x20, 0x03, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(&buf, &EXPECTED2); + } + + #[test] + fn test_ivf_frame_header() { + let mut hdr = IvfFrameHeader { + frame_size: 199249, + timestamp: 0, + }; + + let mut buf = Vec::new(); + hdr.writo_into(&mut buf).unwrap(); + + const EXPECTED: [u8; 12] = [ + 0x51, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(&buf, &EXPECTED); + + hdr.timestamp = 1; + hdr.frame_size = 52; + + buf.clear(); + hdr.writo_into(&mut buf).unwrap(); + + const EXPECTED2: [u8; 12] = [ + 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(&buf, &EXPECTED2); + } +} From 7260bbdb6e7a87dbd4a6ea6cbc2b15816c873324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Grzesik?= Date: Mon, 4 Mar 2024 13:26:19 +0100 Subject: [PATCH 7/7] encoder: Merge StatelessCodec and StatelessCodecSpecific traits --- src/encoder/stateless.rs | 25 +++++++++++-------------- src/encoder/stateless/h264.rs | 5 +---- src/encoder/stateless/vp9.rs | 5 +---- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index 33ba0cef..4677b966 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -179,9 +179,9 @@ pub(super) trait Predictor { } /// Generic trait for stateless encoder backends -pub trait StatelessVideoEncoderBackend +pub trait StatelessVideoEncoderBackend: Sized where - Codec: StatelessCodec, + Codec: StatelessCodec, { /// Backend's specific representation of the input frame, transformed with [`import_picture`]. /// Might be a wrapper of the input handle with additional backend specific data or a copy of @@ -211,7 +211,7 @@ pub trait StatelessEncoderBackendImport { } /// Trait helping contain all codec specific and backend specific types -pub trait StatelessCodecSpecific: StatelessCodec +pub trait StatelessCodec: Sized where Backend: StatelessVideoEncoderBackend, { @@ -231,8 +231,6 @@ where type ReferencePromise: BackendPromise; } -pub trait StatelessCodec: Sized {} - /// Stateless video encoder interface. pub trait StatelessVideoEncoder { /// Enqueues the frame for encoding. The implementation will drop the handle after it is no @@ -290,21 +288,20 @@ where /// Helper aliases for codec and backend specific types type Picture = >::Picture; -type Reference = >::Reference; +type Reference = >::Reference; -type Request = >::Request; +type Request = >::Request; -type CodedPromise = >::CodedPromise; +type CodedPromise = >::CodedPromise; -type ReferencePromise = - >::ReferencePromise; +type ReferencePromise = >::ReferencePromise; type BoxPredictor = Box, Reference, Request>>; pub struct StatelessEncoder where Backend: StatelessVideoEncoderBackend, - Codec: StatelessCodec + StatelessCodecSpecific, + Codec: StatelessCodec, { /// Pending frame output promise queue output_queue: OutputQueue>, @@ -335,14 +332,14 @@ where pub trait StatelessEncoderExecute where Backend: StatelessVideoEncoderBackend, - Codec: StatelessCodec + StatelessCodecSpecific, + Codec: StatelessCodec, { fn execute(&mut self, request: Request) -> EncodeResult<()>; } impl StatelessEncoder where - Codec: StatelessCodec + StatelessCodecSpecific, + Codec: StatelessCodec, Backend: StatelessVideoEncoderBackend, Self: StatelessEncoderExecute, { @@ -387,7 +384,7 @@ where impl StatelessVideoEncoder for StatelessEncoder where - Codec: StatelessCodec + StatelessCodecSpecific, + Codec: StatelessCodec, Backend: StatelessVideoEncoderBackend, Backend: StatelessEncoderBackendImport, Self: StatelessEncoderExecute, diff --git a/src/encoder/stateless/h264.rs b/src/encoder/stateless/h264.rs index 2e6cbef6..c9332fb9 100644 --- a/src/encoder/stateless/h264.rs +++ b/src/encoder/stateless/h264.rs @@ -18,7 +18,6 @@ use crate::encoder::stateless::FrameMetadata; 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::StatelessEncoderBackendImport; use crate::encoder::stateless::StatelessEncoderExecute; @@ -158,7 +157,7 @@ where pub struct H264; -impl StatelessCodecSpecific for H264 +impl StatelessCodec for H264 where Backend: StatelessVideoEncoderBackend, { @@ -171,8 +170,6 @@ where type ReferencePromise = ReferencePromise; } -impl StatelessCodec for H264 {} - /// Trait for stateless encoder backend for H.264 pub trait StatelessH264EncoderBackend: StatelessVideoEncoderBackend { /// Submit a [`BackendRequest`] to the backend. This operation returns both a diff --git a/src/encoder/stateless/vp9.rs b/src/encoder/stateless/vp9.rs index 5a47aff9..8d09b7f9 100644 --- a/src/encoder/stateless/vp9.rs +++ b/src/encoder/stateless/vp9.rs @@ -12,7 +12,6 @@ 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; @@ -83,7 +82,7 @@ pub struct BackendRequest { pub struct VP9; -impl StatelessCodecSpecific for VP9 +impl StatelessCodec for VP9 where Backend: StatelessVideoEncoderBackend, { @@ -96,8 +95,6 @@ where type ReferencePromise = Backend::ReconPromise; } -impl StatelessCodec for VP9 {} - pub trait StatelessVP9EncoderBackend: StatelessVideoEncoderBackend { fn encode_frame( &mut self,