blob: f40a504c7b6485320152c956f8f39b7b81947a34 [file] [log] [blame]
// Copyright 2023 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::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use anyhow::anyhow;
use anyhow::Context as AnyhowContext;
use libva::Config;
use libva::Context;
use libva::Display;
use libva::Image;
use libva::Picture;
use libva::PictureEnd;
use libva::PictureNew;
use libva::PictureSync;
use libva::SurfaceMemoryDescriptor;
use libva::VaError;
use crate::backend::vaapi::p01x_to_i01x;
use crate::backend::vaapi::surface_pool::PooledSurface;
use crate::backend::vaapi::surface_pool::SurfacePool;
use crate::backend::vaapi::va_rt_format_to_string;
use crate::backend::vaapi::y21x_to_i21x;
use crate::backend::vaapi::FormatMap;
use crate::backend::vaapi::FORMAT_MAP;
use crate::decoder::stateless::PoolLayer;
use crate::decoder::stateless::StatelessBackendResult;
use crate::decoder::stateless::StatelessCodec;
use crate::decoder::stateless::StatelessDecoderBackend;
use crate::decoder::stateless::StatelessDecoderBackendPicture;
use crate::decoder::DecodedHandle as DecodedHandleTrait;
use crate::decoder::DynHandle;
use crate::decoder::FramePool;
use crate::decoder::MappableHandle;
use crate::decoder::StreamInfo;
use crate::i4xx_copy;
use crate::nv12_copy;
use crate::y410_to_i410;
use crate::DecodedFormat;
use crate::Fourcc;
use crate::Resolution;
use super::supported_formats_for_rt_format;
use super::y412_to_i412;
/// A decoded frame handle.
pub(crate) type DecodedHandle<M> = Rc<RefCell<VaapiDecodedHandle<M>>>;
impl<M: SurfaceMemoryDescriptor> DecodedHandleTrait for DecodedHandle<M> {
type Descriptor = M;
fn coded_resolution(&self) -> Resolution {
self.borrow().coded_resolution
}
fn display_resolution(&self) -> Resolution {
self.borrow().display_resolution
}
fn timestamp(&self) -> u64 {
self.borrow().timestamp()
}
fn dyn_picture<'a>(&'a self) -> Box<dyn DynHandle + 'a> {
Box::new(self.borrow())
}
fn is_ready(&self) -> bool {
self.borrow().is_va_ready().unwrap_or(true)
}
fn sync(&self) -> anyhow::Result<()> {
self.borrow_mut().sync().context("while syncing picture")?;
Ok(())
}
fn resource(&self) -> std::cell::Ref<M> {
std::cell::Ref::map(self.borrow(), |r| match &r.state {
PictureState::Ready(p) => p.surface().as_ref(),
PictureState::Pending(p) => p.surface().as_ref(),
PictureState::Invalid => unreachable!(),
})
}
}
/// A trait for providing the basic information needed to setup libva for decoding.
pub(crate) trait VaStreamInfo {
/// Returns the VA profile of the stream.
fn va_profile(&self) -> anyhow::Result<i32>;
/// Returns the RT format of the stream.
fn rt_format(&self) -> anyhow::Result<u32>;
/// Returns the minimum number of surfaces required to decode the stream.
fn min_num_surfaces(&self) -> usize;
/// Returns the coded size of the surfaces required to decode the stream.
fn coded_size(&self) -> (u32, u32);
/// Returns the visible rectangle within the coded size for the stream.
fn visible_rect(&self) -> ((u32, u32), (u32, u32));
}
pub(crate) struct ParsedStreamMetadata {
/// A VAContext from which we can decode from.
pub(crate) context: Rc<Context>,
/// The VAConfig that created the context. It must kept here so that
/// it does not get dropped while it is in use.
#[allow(dead_code)]
config: Config,
/// Information about the current stream, directly extracted from it.
stream_info: StreamInfo,
/// The image format we will use to map the surfaces. This is usually the
/// same as the surface's internal format, but occasionally we can try
/// mapping in a different format if requested and if the VA-API driver can
/// do it.
map_format: Rc<libva::VAImageFormat>,
/// The rt_format parsed from the stream.
rt_format: u32,
/// The profile parsed from the stream.
profile: i32,
}
/// Controls how the decoder should create its surface pool.
#[derive(Clone, Debug)]
pub(crate) enum PoolCreationMode {
/// Create a single pool and assume a single spatial layer. Used for non-SVC
/// content.
Highest,
/// Create a pool for each spatial layer. Used for SVC content.
Layers(Vec<Resolution>),
}
/// State of the input stream, which can be either unparsed (we don't know the stream properties
/// yet) or parsed (we know the stream properties and are ready to decode).
pub(crate) enum StreamMetadataState {
/// The metadata for the current stream has not yet been parsed.
Unparsed,
/// The metadata for the current stream has been parsed and a suitable
/// VAContext has been created to accomodate it.
Parsed(ParsedStreamMetadata),
}
type StreamStateWithPool<M> = (StreamMetadataState, Vec<Rc<RefCell<SurfacePool<M>>>>);
impl StreamMetadataState {
/// Returns a reference to the parsed metadata state or an error if we haven't reached that
/// state yet.
pub(crate) fn get_parsed(&self) -> anyhow::Result<&ParsedStreamMetadata> {
match self {
StreamMetadataState::Unparsed { .. } => Err(anyhow!("Stream metadata not parsed yet")),
StreamMetadataState::Parsed(parsed_metadata) => Ok(parsed_metadata),
}
}
/// Initializes or reinitializes the codec state.
fn open<S: VaStreamInfo, M: SurfaceMemoryDescriptor>(
display: &Rc<Display>,
hdr: S,
format_map: Option<&FormatMap>,
old_metadata_state: StreamMetadataState,
old_surface_pools: Vec<Rc<RefCell<SurfacePool<M>>>>,
supports_context_reuse: bool,
pool_creation_mode: PoolCreationMode,
) -> anyhow::Result<StreamStateWithPool<M>> {
let va_profile = hdr.va_profile()?;
let rt_format = hdr.rt_format()?;
let coded_resolution =
Resolution::from(hdr.coded_size()).round(crate::ResolutionRoundMode::Even);
let format_map = if let Some(format_map) = format_map {
format_map
} else {
// Pick the first one that fits
FORMAT_MAP
.iter()
.find(|&map| map.rt_format == rt_format)
.ok_or(anyhow!(
"format {} is not supported by your hardware or by the implementation for the current codec",
va_rt_format_to_string(rt_format)
))?
};
let map_format = display
.query_image_formats()?
.iter()
.find(|f| f.fourcc == format_map.va_fourcc)
.cloned()
.ok_or_else(|| {
anyhow!(
"fourcc {} is not supported by your hardware or by the implementation for the current codec",
Fourcc::from(format_map.va_fourcc)
)
})?;
let min_num_surfaces = hdr.min_num_surfaces();
let visible_rect = hdr.visible_rect();
let display_resolution = Resolution {
width: visible_rect.1 .0 - visible_rect.0 .0,
height: visible_rect.1 .1 - visible_rect.0 .1,
};
let layers = match pool_creation_mode {
PoolCreationMode::Highest => vec![coded_resolution],
PoolCreationMode::Layers(layers) => layers,
};
let (config, context, surface_pools) = match old_metadata_state {
// Nothing has changed for VAAPI, reuse current context.
//
// This can happen as the decoder cannot possibly know whether a
// given backend will really need to renegotiate on a given change
// of stream parameters.
StreamMetadataState::Parsed(old_state)
if old_state.stream_info.coded_resolution == coded_resolution
&& old_state.rt_format == rt_format
&& old_state.profile == va_profile =>
{
(old_state.config, old_state.context, old_surface_pools)
}
// The resolution has changed, but we support context reuse. Reuse
// current context.
StreamMetadataState::Parsed(old_state)
if supports_context_reuse
&& old_state.rt_format == rt_format
&& old_state.profile == va_profile =>
{
(old_state.config, old_state.context, old_surface_pools)
}
// Create new context.
_ => {
let config = display.create_config(
vec![libva::VAConfigAttrib {
type_: libva::VAConfigAttribType::VAConfigAttribRTFormat,
value: rt_format,
}],
va_profile,
libva::VAEntrypoint::VAEntrypointVLD,
)?;
let context = display.create_context::<M>(
&config,
coded_resolution.width,
coded_resolution.height,
None,
true,
)?;
let surface_pools = layers
.iter()
.map(|layer| {
SurfacePool::new(
Rc::clone(display),
rt_format,
Some(libva::UsageHint::USAGE_HINT_DECODER),
*layer,
)
})
.collect();
(config, context, surface_pools)
}
};
/* sanity check */
assert!(surface_pools.len() == layers.len());
for (pool, layer) in surface_pools.iter().zip(layers.iter()) {
let mut pool = pool.borrow_mut();
if !pool.coded_resolution().can_contain(*layer) {
/* this will purge the old surfaces by not reclaiming them */
pool.set_coded_resolution(*layer);
}
}
Ok((
StreamMetadataState::Parsed(ParsedStreamMetadata {
context,
config,
stream_info: StreamInfo {
format: match rt_format {
libva::constants::VA_RT_FORMAT_YUV420 => DecodedFormat::I420,
libva::constants::VA_RT_FORMAT_YUV422 => DecodedFormat::I422,
libva::constants::VA_RT_FORMAT_YUV444 => DecodedFormat::I444,
libva::constants::VA_RT_FORMAT_YUV420_10 => DecodedFormat::I010,
libva::constants::VA_RT_FORMAT_YUV420_12 => DecodedFormat::I012,
libva::constants::VA_RT_FORMAT_YUV422_10 => DecodedFormat::I210,
libva::constants::VA_RT_FORMAT_YUV422_12 => DecodedFormat::I212,
libva::constants::VA_RT_FORMAT_YUV444_10 => DecodedFormat::I410,
libva::constants::VA_RT_FORMAT_YUV444_12 => DecodedFormat::I412,
_ => panic!("unrecognized RT format {}", rt_format),
},
coded_resolution,
display_resolution,
min_num_frames: min_num_surfaces,
},
map_format: Rc::new(map_format),
rt_format,
profile: va_profile,
}),
surface_pools,
))
}
}
/// VA-API backend handle.
///
/// This includes the VA picture which can be pending rendering or complete, as well as useful
/// meta-information.
pub struct VaapiDecodedHandle<M: SurfaceMemoryDescriptor> {
state: PictureState<M>,
/// The decoder resolution when this frame was processed. Not all codecs
/// send resolution data in every frame header.
coded_resolution: Resolution,
/// Actual resolution of the visible rectangle in the decoded buffer.
display_resolution: Resolution,
/// Image format for this surface, taken from the pool it originates from.
map_format: Rc<libva::VAImageFormat>,
}
impl<M: SurfaceMemoryDescriptor> VaapiDecodedHandle<M> {
/// Creates a new pending handle on `surface_id`.
fn new(
picture: Picture<PictureNew, PooledSurface<M>>,
metadata: &ParsedStreamMetadata,
) -> anyhow::Result<Self> {
let picture = picture.begin()?.render()?.end()?;
Ok(Self {
state: PictureState::Pending(picture),
coded_resolution: metadata.stream_info.coded_resolution,
display_resolution: metadata.stream_info.display_resolution,
map_format: Rc::clone(&metadata.map_format),
})
}
fn sync(&mut self) -> Result<(), VaError> {
let res;
(self.state, res) = match std::mem::replace(&mut self.state, PictureState::Invalid) {
state @ PictureState::Ready(_) => (state, Ok(())),
PictureState::Pending(picture) => match picture.sync() {
Ok(picture) => (PictureState::Ready(picture), Ok(())),
Err((e, picture)) => (PictureState::Pending(picture), Err(e)),
},
PictureState::Invalid => unreachable!(),
};
res
}
/// Returns a mapped VAImage. this maps the VASurface onto our address space.
/// This can be used in place of "DynMappableHandle::map()" if the client
/// wants to access the backend mapping directly for any reason.
///
/// Note that DynMappableHandle is downcastable.
fn image(&self) -> anyhow::Result<Image> {
match &self.state {
PictureState::Ready(picture) => {
// Map the VASurface onto our address space.
let image = picture.create_image(
*self.map_format,
self.coded_resolution.into(),
self.display_resolution.into(),
)?;
Ok(image)
}
// Either we are in `Ready` state or we didn't call `sync()`.
PictureState::Pending(_) | PictureState::Invalid => {
Err(anyhow::anyhow!("picture is not in Ready state"))
}
}
}
/// Returns the picture of this handle.
pub(crate) fn picture(&self) -> Option<&Picture<PictureSync, PooledSurface<M>>> {
match &self.state {
PictureState::Ready(picture) => Some(picture),
PictureState::Pending(_) => None,
PictureState::Invalid => unreachable!(),
}
}
/// Returns the timestamp of this handle.
fn timestamp(&self) -> u64 {
match &self.state {
PictureState::Ready(picture) => picture.timestamp(),
PictureState::Pending(picture) => picture.timestamp(),
PictureState::Invalid => unreachable!(),
}
}
/// Returns the id of the VA surface backing this handle.
pub(crate) fn surface_id(&self) -> libva::VASurfaceID {
match &self.state {
PictureState::Ready(picture) => picture.surface().id(),
PictureState::Pending(picture) => picture.surface().id(),
PictureState::Invalid => unreachable!(),
}
}
fn is_va_ready(&self) -> Result<bool, VaError> {
match &self.state {
PictureState::Ready(_) => Ok(true),
PictureState::Pending(picture) => picture
.surface()
.query_status()
.map(|s| s == libva::VASurfaceStatus::VASurfaceReady),
PictureState::Invalid => unreachable!(),
}
}
}
impl<'a, M: SurfaceMemoryDescriptor> DynHandle for std::cell::Ref<'a, VaapiDecodedHandle<M>> {
fn dyn_mappable_handle<'b>(&'b self) -> anyhow::Result<Box<dyn MappableHandle + 'b>> {
self.image().map(|i| Box::new(i) as Box<dyn MappableHandle>)
}
}
/// Rendering state of a VA picture.
enum PictureState<M: SurfaceMemoryDescriptor> {
Ready(Picture<PictureSync, PooledSurface<M>>),
Pending(Picture<PictureEnd, PooledSurface<M>>),
// Only set in the destructor when we take ownership of the VA picture.
Invalid,
}
impl<'a> MappableHandle for Image<'a> {
fn read(&mut self, buffer: &mut [u8]) -> anyhow::Result<()> {
let image_size = self.image_size();
let image_inner = self.image();
let display_resolution = self.display_resolution();
let width = display_resolution.0 as usize;
let height = display_resolution.1 as usize;
if buffer.len() != image_size {
return Err(anyhow!(
"buffer size is {} while image size is {}",
buffer.len(),
image_size
));
}
let pitches = image_inner.pitches.map(|x| x as usize);
let offsets = image_inner.offsets.map(|x| x as usize);
match image_inner.format.fourcc {
libva::constants::VA_FOURCC_NV12 => {
nv12_copy(self.as_ref(), buffer, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_I420 => {
i4xx_copy(
self.as_ref(),
buffer,
width,
height,
pitches,
offsets,
(true, true),
);
}
libva::constants::VA_FOURCC_422H => {
i4xx_copy(
self.as_ref(),
buffer,
width,
height,
pitches,
offsets,
(true, false),
);
}
libva::constants::VA_FOURCC_444P => {
i4xx_copy(
self.as_ref(),
buffer,
width,
height,
pitches,
offsets,
(false, false),
);
}
libva::constants::VA_FOURCC_P010 => {
p01x_to_i01x(self.as_ref(), buffer, 10, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_P012 => {
p01x_to_i01x(self.as_ref(), buffer, 12, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_Y210 => {
y21x_to_i21x(self.as_ref(), buffer, 10, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_Y212 => {
y21x_to_i21x(self.as_ref(), buffer, 12, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_Y410 => {
y410_to_i410(self.as_ref(), buffer, width, height, pitches, offsets);
}
libva::constants::VA_FOURCC_Y412 => {
y412_to_i412(self.as_ref(), buffer, width, height, pitches, offsets);
}
_ => {
return Err(anyhow!(
"unsupported format 0x{:x}",
image_inner.format.fourcc
))
}
}
Ok(())
}
fn image_size(&mut self) -> usize {
let image = self.image();
let display_resolution = self.display_resolution();
crate::decoded_frame_size(
(&image.format).try_into().unwrap(),
display_resolution.0 as usize,
display_resolution.1 as usize,
)
}
}
pub struct VaapiBackend<M>
where
M: SurfaceMemoryDescriptor,
{
/// VA display in use for this stream.
display: Rc<Display>,
/// Pools of surfaces. We reuse surfaces as they are expensive to allocate.
/// We allow for multiple pools so as to support one spatial layer per pool
/// when needed.
pub(crate) surface_pools: Vec<Rc<RefCell<SurfacePool<M>>>>,
/// The metadata state. Updated whenever the decoder reads new data from the stream.
pub(crate) metadata_state: StreamMetadataState,
/// Whether the codec supports context reuse on DRC. This is only supported
/// by VP9 and AV1.
supports_context_reuse: bool,
/// Controls the creation of surface pools.
pool_creation_mode: PoolCreationMode,
}
impl<M> VaapiBackend<M>
where
M: SurfaceMemoryDescriptor + 'static,
{
pub(crate) fn new(display: Rc<libva::Display>, supports_context_reuse: bool) -> Self {
// Create a pool with reasonable defaults, as we don't know the format of the stream yet.
let surface_pools = vec![SurfacePool::new(
Rc::clone(&display),
libva::constants::VA_RT_FORMAT_YUV420,
Some(libva::UsageHint::USAGE_HINT_DECODER),
Resolution::from((16, 16)),
)];
Self {
display,
surface_pools,
metadata_state: StreamMetadataState::Unparsed,
supports_context_reuse,
pool_creation_mode: PoolCreationMode::Highest,
}
}
pub(crate) fn new_sequence<StreamData>(
&mut self,
stream_params: &StreamData,
pool_creation_mode: PoolCreationMode,
) -> StatelessBackendResult<()>
where
for<'a> &'a StreamData: VaStreamInfo,
{
let old_metadata_state =
std::mem::replace(&mut self.metadata_state, StreamMetadataState::Unparsed);
let old_surface_pools = self.surface_pools.drain(..).collect();
(self.metadata_state, self.surface_pools) = StreamMetadataState::open(
&self.display,
stream_params,
None,
old_metadata_state,
old_surface_pools,
self.supports_context_reuse,
pool_creation_mode.clone(),
)?;
self.pool_creation_mode = pool_creation_mode;
Ok(())
}
pub(crate) fn process_picture<Codec: StatelessCodec>(
&mut self,
picture: Picture<PictureNew, PooledSurface<M>>,
) -> StatelessBackendResult<<Self as StatelessDecoderBackend<Codec>>::Handle>
where
Self: StatelessDecoderBackendPicture<Codec>,
for<'a> &'a Codec::FormatInfo: VaStreamInfo,
{
let metadata = self.metadata_state.get_parsed()?;
Ok(Rc::new(RefCell::new(VaapiDecodedHandle::new(
picture, metadata,
)?)))
}
/// Gets a set of supported formats for the particular stream being
/// processed. This requires that some buffers be processed before this call
/// is made. Only formats that are compatible with the current color space,
/// bit depth, and chroma format are returned such that no conversion is
/// needed.
fn supported_formats_for_stream(&self) -> anyhow::Result<HashSet<DecodedFormat>> {
let metadata = self.metadata_state.get_parsed()?;
let image_formats = self.display.query_image_formats()?;
let formats = supported_formats_for_rt_format(
&self.display,
metadata.rt_format,
metadata.profile,
libva::VAEntrypoint::VAEntrypointVLD,
&image_formats,
)?;
Ok(formats.into_iter().map(|f| f.decoded_format).collect())
}
pub(crate) fn highest_pool(&mut self) -> &Rc<RefCell<SurfacePool<M>>> {
/* we guarantee that there is at least one pool, at minimum */
self.surface_pools
.iter()
.max_by_key(|p| p.borrow().coded_resolution().height)
.unwrap()
}
pub(crate) fn pool(&mut self, layer: Resolution) -> Option<&Rc<RefCell<SurfacePool<M>>>> {
self.surface_pools
.iter()
.find(|p| p.borrow().coded_resolution() == layer)
}
}
/// Shortcut for pictures used for the VAAPI backend.
pub type VaapiPicture<M> = Picture<PictureNew, PooledSurface<M>>;
impl<Codec: StatelessCodec, M> StatelessDecoderBackend<Codec> for VaapiBackend<M>
where
VaapiBackend<M>: StatelessDecoderBackendPicture<Codec>,
for<'a> &'a Codec::FormatInfo: VaStreamInfo,
M: SurfaceMemoryDescriptor + 'static,
{
type Handle = DecodedHandle<M>;
fn try_format(
&mut self,
format_info: &Codec::FormatInfo,
format: crate::DecodedFormat,
) -> anyhow::Result<()> {
let supported_formats_for_stream = self.supported_formats_for_stream()?;
if supported_formats_for_stream.contains(&format) {
let map_format = FORMAT_MAP
.iter()
.find(|&map| map.decoded_format == format)
.ok_or_else(|| {
anyhow!(
"cannot find corresponding VA format for decoded format {:?}",
format
)
})?;
let old_metadata_state =
std::mem::replace(&mut self.metadata_state, StreamMetadataState::Unparsed);
// TODO: since we have established that it's best to let the VA
// driver choose the surface's internal (tiled) format, and map to
// the fourcc we want on-the-fly, this call to open() becomes
// redundant.
//
// Let's fix it at a later commit, because it involves other,
// non-related, cleanups.
//
// This does not apply to other (future) backends, like V4L2, which
// need to reallocate on format change.
let old_surface_pools = self.surface_pools.drain(..).collect();
(self.metadata_state, self.surface_pools) = StreamMetadataState::open(
&self.display,
format_info,
Some(map_format),
old_metadata_state,
old_surface_pools,
self.supports_context_reuse,
self.pool_creation_mode.clone(),
)?;
Ok(())
} else {
Err(anyhow!("Format {:?} is unsupported.", format))
}
}
fn frame_pool(&mut self, layer: PoolLayer) -> Vec<&mut dyn FramePool<M>> {
if let PoolLayer::Highest = layer {
return vec![self
.surface_pools
.iter_mut()
.max_by_key(|other| other.coded_resolution().height)
.unwrap()];
}
self.surface_pools
.iter_mut()
.filter(|pool| {
match layer {
PoolLayer::Highest => unreachable!(),
PoolLayer::Layer(resolution) => pool.coded_resolution() == resolution,
PoolLayer::All => {
/* let all through */
true
}
}
})
.map(|x| x as &mut dyn FramePool<M>)
.collect()
}
fn stream_info(&self) -> Option<&StreamInfo> {
self.metadata_state
.get_parsed()
.ok()
.map(|m| &m.stream_info)
}
}