Skip to content

Commit

Permalink
decoder: enable fully dynamic StatelessVideoDecoder trait objects
Browse files Browse the repository at this point in the history
Add the necessary implementations and wrapper types to allow a
StatelessVideoDecoder to be turned into a fully dynamic trait object
with no other dependency that its Descriptor.

This now allows user code to control any instance of a decoder,
regardless of its codec or backend, through the same code path.
  • Loading branch information
Gnurou committed Jun 18, 2024
1 parent e82c142 commit 4fa4bd6
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 25 deletions.
39 changes: 20 additions & 19 deletions examples/ccdec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//! input and writing the raw decoded frames to a file.

use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Cursor;
Expand All @@ -16,11 +15,9 @@ use std::os::fd::AsFd;
use std::os::fd::BorrowedFd;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr;

use argh::FromArgs;
use cros_codecs::backend::vaapi::decoder::VaapiDecodedHandle;
use cros_codecs::codec::h264::parser::Nalu as H264Nalu;
use cros_codecs::codec::h265::parser::Nalu as H265Nalu;
use cros_codecs::decoder::stateless::av1::Av1;
Expand All @@ -32,6 +29,7 @@ use cros_codecs::decoder::stateless::StatelessDecoder;
use cros_codecs::decoder::stateless::StatelessVideoDecoder;
use cros_codecs::decoder::BlockingMode;
use cros_codecs::decoder::DecodedHandle;
use cros_codecs::decoder::DynDecodedHandle;
use cros_codecs::decoder::StreamInfo;
use cros_codecs::multiple_desc_type;
use cros_codecs::utils::simple_playback_loop;
Expand Down Expand Up @@ -342,51 +340,54 @@ fn main() {
};

let display = libva::Display::open().expect("failed to open libva display");

// The created `decoder` is turned into a `DynStatelessVideoDecoder` trait object. This allows
// the same code to control the decoder no matter what codec or backend we are using.
let (mut decoder, frame_iter) = match args.input_format {
EncodedFormat::H264 => {
let frame_iter = Box::new(NalIterator::<H264Nalu>::new(&input))
as Box<dyn Iterator<Item = Cow<[u8]>>>;

let decoder =
Box::new(StatelessDecoder::<H264, _>::new_vaapi(display, blocking_mode).unwrap())
as Box<dyn StatelessVideoDecoder<Handle = _, FramePool = _>>;
let decoder = StatelessDecoder::<H264, _>::new_vaapi(display, blocking_mode)
.unwrap()
.into_trait_object();

(decoder, frame_iter)
}
EncodedFormat::VP8 => {
let frame_iter = create_vpx_frame_iterator(&input);

let decoder =
Box::new(StatelessDecoder::<Vp8, _>::new_vaapi(display, blocking_mode).unwrap())
as Box<dyn StatelessVideoDecoder<Handle = _, FramePool = _>>;
let decoder = StatelessDecoder::<Vp8, _>::new_vaapi(display, blocking_mode)
.unwrap()
.into_trait_object();

(decoder, frame_iter)
}
EncodedFormat::VP9 => {
let frame_iter = create_vpx_frame_iterator(&input);

let decoder =
Box::new(StatelessDecoder::<Vp9, _>::new_vaapi(display, blocking_mode).unwrap())
as Box<dyn StatelessVideoDecoder<Handle = _, FramePool = _>>;
let decoder = StatelessDecoder::<Vp9, _>::new_vaapi(display, blocking_mode)
.unwrap()
.into_trait_object();

(decoder, frame_iter)
}
EncodedFormat::H265 => {
let frame_iter = Box::new(NalIterator::<H265Nalu>::new(&input))
as Box<dyn Iterator<Item = Cow<[u8]>>>;

let decoder =
Box::new(StatelessDecoder::<H265, _>::new_vaapi(display, blocking_mode).unwrap())
as Box<dyn StatelessVideoDecoder<Handle = _, FramePool = _>>;
let decoder = StatelessDecoder::<H265, _>::new_vaapi(display, blocking_mode)
.unwrap()
.into_trait_object();

(decoder, frame_iter)
}
EncodedFormat::AV1 => {
let frame_iter = create_vpx_frame_iterator(&input);

let decoder =
Box::new(StatelessDecoder::<Av1, _>::new_vaapi(display, blocking_mode).unwrap())
as Box<dyn StatelessVideoDecoder<Handle = _, FramePool = _>>;
let decoder = StatelessDecoder::<Av1, _>::new_vaapi(display, blocking_mode)
.unwrap()
.into_trait_object();

(decoder, frame_iter)
}
Expand All @@ -395,7 +396,7 @@ fn main() {
let mut md5_context = md5::Context::new();
let mut output_filename_idx = 0;

let mut on_new_frame = |handle: Rc<RefCell<VaapiDecodedHandle<BufferDescriptor>>>| {
let mut on_new_frame = |handle: DynDecodedHandle<BufferDescriptor>| {
if args.output.is_some() || args.compute_md5.is_some() {
handle.sync().unwrap();
let picture = handle.dyn_picture();
Expand Down
39 changes: 39 additions & 0 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,45 @@ pub trait DecodedHandle {
fn resource(&self) -> std::cell::Ref<Self::Descriptor>;
}

/// Implementation for any boxed [`DecodedHandle`], including trait objects.
impl<H> DecodedHandle for Box<H>
where
H: DecodedHandle + ?Sized,
{
type Descriptor = H::Descriptor;

fn dyn_picture<'a>(&'a self) -> Box<dyn DynHandle + 'a> {
self.as_ref().dyn_picture()
}

fn timestamp(&self) -> u64 {
self.as_ref().timestamp()
}

fn coded_resolution(&self) -> Resolution {
self.as_ref().coded_resolution()
}

fn display_resolution(&self) -> Resolution {
self.as_ref().display_resolution()
}

fn is_ready(&self) -> bool {
self.as_ref().is_ready()
}

fn sync(&self) -> anyhow::Result<()> {
self.as_ref().sync()
}

fn resource(&self) -> std::cell::Ref<Self::Descriptor> {
self.as_ref().resource()
}
}

/// Trait object for [`DecodedHandle`]s using a specific `Descriptor`.
pub type DynDecodedHandle<D> = Box<dyn DecodedHandle<Descriptor = D>>;

/// A queue where decoding jobs wait until they are completed, at which point they can be
/// retrieved.
struct ReadyFramesQueue<T> {
Expand Down
82 changes: 77 additions & 5 deletions src/decoder/stateless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::decoder::BlockingMode;
use crate::decoder::DecodedHandle;
use crate::decoder::DecoderEvent;
use crate::decoder::DecoderFormatNegotiator;
use crate::decoder::DynDecodedHandle;
use crate::decoder::FramePool;
use crate::decoder::ReadyFramesQueue;
use crate::decoder::StreamInfo;
Expand Down Expand Up @@ -285,8 +286,78 @@ pub trait StatelessVideoDecoder {
/// Returns a file descriptor that signals `POLLIN` whenever an event is pending on this
/// decoder.
fn poll_fd(&self) -> BorrowedFd;

/// Transforms the decoder into a [`StatelessVideoDecoder`] trait object.
///
/// All decoders going through this method present the same virtual interface when they return.
/// This is useful in order avoid monomorphization of application code that can control
/// decoders using various codecs or backends.
fn into_trait_object(
self,
) -> DynStatelessVideoDecoder<<Self::Handle as DecodedHandle>::Descriptor>
where
Self: Sized + 'static,
Self::FramePool: Sized + 'static,
Self::Handle: 'static,
{
Box::new(DynStatelessVideoDecoderWrapper(self))
}
}

/// Wrapper type for a `StatelessVideoDecoder` that can be turned into a trait object with a common
/// interface.
struct DynStatelessVideoDecoderWrapper<D: StatelessVideoDecoder>(D);

impl<D> StatelessVideoDecoder for DynStatelessVideoDecoderWrapper<D>
where
D: StatelessVideoDecoder,
<D as StatelessVideoDecoder>::FramePool: Sized + 'static,
<D as StatelessVideoDecoder>::Handle: 'static,
{
type Handle = DynDecodedHandle<<D::Handle as DecodedHandle>::Descriptor>;
type FramePool = dyn FramePool<Descriptor = <D::FramePool as FramePool>::Descriptor>;

fn decode(&mut self, timestamp: u64, bitstream: &[u8]) -> Result<usize, DecodeError> {
self.0.decode(timestamp, bitstream)
}

fn flush(&mut self) -> Result<(), DecodeError> {
self.0.flush()
}

fn frame_pool(&mut self, layer: PoolLayer) -> Vec<&mut Self::FramePool> {
self.0
.frame_pool(layer)
.into_iter()
.map(|p| p as &mut Self::FramePool)
.collect()
}

fn stream_info(&self) -> Option<&StreamInfo> {
self.0.stream_info()
}

fn next_event(&mut self) -> Option<DecoderEvent<Self::Handle>> {
self.0.next_event().map(|e| match e {
DecoderEvent::FrameReady(h) => {
DecoderEvent::FrameReady(Box::new(h) as DynDecodedHandle<_>)
}
DecoderEvent::FormatChanged(_n) => todo!(),
})
}

fn poll_fd(&self) -> BorrowedFd {
self.0.poll_fd()
}
}

pub type DynStatelessVideoDecoder<D> = Box<
dyn StatelessVideoDecoder<
Handle = DynDecodedHandle<D>,
FramePool = dyn FramePool<Descriptor = D>,
>,
>;

pub trait StatelessCodec {
/// Type providing current format information for the codec: resolution, color format, etc.
///
Expand Down Expand Up @@ -453,9 +524,9 @@ where

#[cfg(test)]
pub(crate) mod tests {
use crate::decoder::stateless::StatelessDecoderBackend;
use crate::decoder::stateless::StatelessVideoDecoder;
use crate::decoder::DecodedHandle;
use crate::decoder::FramePool;

/// Stream that can be used in tests, along with the CRC32 of all of its frames.
pub struct TestStream {
Expand All @@ -474,16 +545,17 @@ pub(crate) mod tests {
///
/// `dump_yuv` will dump all the decoded frames into `/tmp/framexxx.yuv`. Set this to true in
/// order to debug the output of the test.
pub fn test_decode_stream<D, B, L>(
pub fn test_decode_stream<D, H, FP, L>(
decoding_loop: L,
mut decoder: D,
test: &TestStream,
check_crcs: bool,
dump_yuv: bool,
) where
B: StatelessDecoderBackend,
D: StatelessVideoDecoder<Handle = B::Handle, FramePool = B::FramePool>,
L: Fn(&mut D, &[u8], &mut dyn FnMut(B::Handle)) -> anyhow::Result<()>,
H: DecodedHandle,
FP: FramePool,
D: StatelessVideoDecoder<Handle = H, FramePool = FP>,
L: Fn(&mut D, &[u8], &mut dyn FnMut(H)) -> anyhow::Result<()>,
{
let mut crcs = test.crcs.lines().enumerate();

Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ pub fn simple_playback_loop<D, R, I, H, FP>(
) -> anyhow::Result<()>
where
H: DecodedHandle,
FP: FramePool<Descriptor = H::Descriptor>,
FP: FramePool<Descriptor = H::Descriptor> + ?Sized,
D: StatelessVideoDecoder<Handle = H, FramePool = FP> + ?Sized,
R: AsRef<[u8]>,
I: Iterator<Item = R>,
Expand Down

0 comments on commit 4fa4bd6

Please sign in to comment.