blob: 370b7963ed9658feb597fbcc9b0823b1d1f2c75f [file] [log] [blame]
// Copyright 2022 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::cell::RefMut;
use thiserror::Error;
use crate::DecodedFormat;
use crate::Resolution;
pub mod h264;
pub mod h265;
pub mod vp8;
pub mod vp9;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
StatelessBackendError(#[from] StatelessBackendError),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
#[derive(Error, Debug)]
pub enum StatelessBackendError {
#[error("not enough resources to proceed with the operation now")]
OutOfResources,
#[error("this resource is not ready")]
ResourceNotReady,
#[error("this format is not supported")]
UnsupportedFormat,
#[error("negotiation failed")]
NegotiationFailed(anyhow::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
pub type StatelessBackendResult<T> = std::result::Result<T, StatelessBackendError>;
pub(crate) trait VideoDecoderBackend {
/// The type that the backend returns as a result of a decode operation.
/// This will usually be some backend-specific type with a resource and a
/// resource pool so that said buffer can be reused for another decode
/// operation when it goes out of scope.
type Handle: DecodedHandle + Clone;
/// Returns the current coded resolution of the bitstream being processed.
/// This may be None if we have not read the stream parameters yet.
fn coded_resolution(&self) -> Option<Resolution>;
/// Returns the current display resolution of the bitstream being processed.
/// This may be None if we have not read the stream parameters yet.
fn display_resolution(&self) -> Option<Resolution>;
/// Gets the number of output resources allocated by the backend.
fn num_resources_total(&self) -> usize;
/// Gets the number of output resources left in the backend.
fn num_resources_left(&self) -> usize;
/// Gets the chosen format. This is set to a default after the decoder reads
/// enough stream metadata from the bitstream. Some buffers need to be
/// processed first before the default format can be set.
fn format(&self) -> Option<DecodedFormat>;
/// Try altering the decoded format.
fn try_format(&mut self, format: DecodedFormat) -> Result<()>;
}
pub trait VideoDecoder {
/// Decode the `bitstream` represented by `timestamp`. Returns zero or more
/// decoded handles representing the decoded data.
fn decode(&mut self, timestamp: u64, bitstream: &[u8]) -> Result<Vec<Box<dyn DecodedHandle>>>;
/// Flush the decoder i.e. finish processing all queued decode requests and
/// emit frames for them.
fn flush(&mut self) -> Result<Vec<Box<dyn DecodedHandle>>>;
/// Whether negotiation of the decoded format is possible. In particular, a
/// decoder will indicate that negotiation is possible after enough metadata
/// is collected from parsing the bitstream through calls to the `decode()`
/// method.
///
/// The negotiation process will start as soon as `negotiation_possible()`
/// returns true. At this moment, the client and the backend can settle on a
/// format by using the `supported_formats_for_stream()`, `format()` and
/// `try_format()` methods.
///
/// When `negotiation_possible()` returns true, the client may also query
/// the backend for new values for the coded resolution, display resolution
/// and/or to the number of resources allocated.
///
/// The negotiation process ends as soon as another call to `decode()` is
/// made, at which point any queued data will be processed first in order to
/// generate any frames that might have been pending while the negotiation
/// process was under way and `negotiation_possible()` will from then on
/// return false.
///
/// If no action is undertaken by the client in the window of time where
/// `negotiation_possible()` returns true, it is assumed that the default
/// format chosen by the backend is acceptable.
///
/// The negotiation process can happen more than once if new stream metadata
/// indicate a change of the stream parameters such that the current decoded
/// format becomes incompatible with the stream. In this case,
/// `negotiation_possible()` will once again return true and the same
/// process described above will take place.
fn negotiation_possible(&self) -> bool;
/// Gets the number of output resources left in the backend after accounting
/// for any buffers that might be queued in the decoder.
fn num_resources_left(&self) -> Option<usize>;
/// Gets the number of output resources allocated by the backend.
fn num_resources_total(&self) -> usize;
///
/// Returns the current coded resolution of the bitstream being processed.
/// This may be None if we have not read the stream parameters yet.
fn coded_resolution(&self) -> Option<Resolution>;
}
pub trait DynHandle {
/// Gets an exclusive reference to the backend handle of this picture.
/// Assumes that this picture is backed by a handle and panics if not the case.
fn dyn_mappable_handle_mut<'a>(&'a mut self) -> Box<dyn MappableHandle + 'a>;
}
/// A trait for types that can be mapped into the client's address space.
pub trait MappableHandle {
/// Read the contents of `self` into `buffer`.
///
/// The size of `buffer` must be equal to `image_size()`, or an error will be returned.
fn read(&mut self, buffer: &mut [u8]) -> Result<()>;
/// Returns the size of the `buffer` argument required to call `read` on this handle.
fn image_size(&mut self) -> usize;
}
/// Instructs the decoder on whether it should block on the decode operations.
/// Nonblocking mode is conditional on backend support.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockingMode {
Blocking,
NonBlocking,
}
impl Default for BlockingMode {
fn default() -> Self {
Self::Blocking
}
}
/// The handle type used by the stateless decoder backend. The only requirement
/// from implementors is that they give access to the underlying handle and
/// that they can be (cheaply) cloned.
pub trait DecodedHandle {
fn dyn_picture_mut(&self) -> RefMut<dyn DynHandle>;
/// Returns the display order for the picture backed by this handle, if set by the decoder.
fn display_order(&self) -> Option<u64>;
/// Sets the display order for the picture backend by this handle
fn set_display_order(&mut self, display_order: u64);
/// Returns the timestamp of the picture.
fn timestamp(&self) -> u64;
/// Returns the display resolution at the time this handle was decoded.
fn display_resolution(&self) -> Resolution;
/// Returns `true` if this handle has been completely decoded.
fn is_ready(&self) -> bool;
/// Wait until this handle has been completely rendered.
fn sync(&self) -> StatelessBackendResult<()>;
}
#[cfg(test)]
pub(crate) mod tests {
use crate::decoders::DecodedHandle;
use crate::decoders::VideoDecoder;
/// Stream that can be used in tests, along with the CRC32 of all of its frames.
pub struct TestStream {
/// Bytestream to decode.
pub stream: &'static [u8],
/// Expected CRC for each frame, one per line.
pub crcs: &'static str,
}
/// Run the codec-specific `decoding_loop` on a `decoder` with a given `test`, linearly
/// decoding the stream until its end.
///
/// If `check_crcs` is `true`, then the expected CRCs of the decoded images are compared
/// against the existing result. We may want to set this to false when using a decoder backend
/// that does not produce actual frames.
///
/// `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, L>(
decoding_loop: L,
mut decoder: D,
test: &TestStream,
check_crcs: bool,
dump_yuv: bool,
) where
D: VideoDecoder,
L: Fn(&mut D, &[u8], &mut dyn FnMut(Box<dyn DecodedHandle>)),
{
let mut crcs = test.crcs.lines().enumerate();
decoding_loop(&mut decoder, test.stream, &mut |handle| {
let (frame_num, expected_crc) = crcs.next().expect("decoded more frames than expected");
if check_crcs || dump_yuv {
let mut picture = handle.dyn_picture_mut();
let mut backend_handle = picture.dyn_mappable_handle_mut();
let buffer_size = backend_handle.image_size();
let mut nv12 = vec![0; buffer_size];
backend_handle.read(&mut nv12).unwrap();
if dump_yuv {
std::fs::write(format!("/tmp/frame{:03}.yuv", frame_num), &nv12).unwrap();
}
if check_crcs {
let frame_crc = format!("{:08x}", crc32fast::hash(&nv12));
assert_eq!(frame_crc, expected_crc, "at frame {}", frame_num);
}
}
});
assert_eq!(crcs.next(), None, "decoded less frames than expected");
}
}