blob: 22cdc7bcfbd994cfc8dcf8b5deebfacd9cccf0c4 [file] [log] [blame]
// Copyright 2020 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::btree_map::Entry;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use anyhow::anyhow;
use base::error;
use base::warn;
use base::AsRawDescriptor;
use base::IntoRawDescriptor;
use libvda::decode::Event as LibvdaEvent;
use crate::virtio::video::decoder::backend::*;
use crate::virtio::video::decoder::Capability;
use crate::virtio::video::error::VideoError;
use crate::virtio::video::error::VideoResult;
use crate::virtio::video::format::*;
/// Since libvda only accepts 32-bit timestamps, we are going to truncate the frame 64-bit timestamp
/// (of nanosecond granularity) to only keep seconds granularity. This would result in information
/// being lost on a regular client, but the Android C2 decoder only sends timestamps with second
/// granularity, so this approach is going to work there. However, this means that this backend is
/// very unlikely to work with any other guest software. We accept this fact because it is
/// impossible to use outside of ChromeOS anyway.
const TIMESTAMP_TRUNCATE_FACTOR: u64 = 1_000_000_000;
impl TryFrom<Format> for libvda::Profile {
type Error = VideoError;
fn try_from(format: Format) -> Result<Self, Self::Error> {
Ok(match format {
Format::VP8 => libvda::Profile::VP8,
Format::VP9 => libvda::Profile::VP9Profile0,
Format::H264 => libvda::Profile::H264ProfileBaseline,
Format::Hevc => libvda::Profile::HevcProfileMain,
_ => {
error!("specified format {} is not supported by VDA", format);
return Err(VideoError::InvalidParameter);
}
})
}
}
impl TryFrom<Format> for libvda::PixelFormat {
type Error = VideoError;
fn try_from(format: Format) -> Result<Self, Self::Error> {
Ok(match format {
Format::NV12 => libvda::PixelFormat::NV12,
_ => {
error!("specified format {} is not supported by VDA", format);
return Err(VideoError::InvalidParameter);
}
})
}
}
impl From<&FramePlane> for libvda::FramePlane {
fn from(plane: &FramePlane) -> Self {
libvda::FramePlane {
offset: plane.offset as i32,
stride: plane.stride as i32,
}
}
}
impl From<libvda::decode::Event> for DecoderEvent {
fn from(event: libvda::decode::Event) -> Self {
// We cannot use the From trait here since neither libvda::decode::Response
// no std::result::Result are defined in the current crate.
fn vda_response_to_result(resp: libvda::decode::Response) -> VideoResult<()> {
match resp {
libvda::decode::Response::Success => Ok(()),
resp => Err(VideoError::BackendFailure(anyhow!("VDA failure: {}", resp))),
}
}
match event {
LibvdaEvent::ProvidePictureBuffers {
min_num_buffers,
width,
height,
visible_rect_left,
visible_rect_top,
visible_rect_right,
visible_rect_bottom,
} => DecoderEvent::ProvidePictureBuffers {
min_num_buffers,
width,
height,
visible_rect: Rect {
left: visible_rect_left,
top: visible_rect_top,
right: visible_rect_right,
bottom: visible_rect_bottom,
},
},
LibvdaEvent::PictureReady {
buffer_id,
bitstream_id,
left,
top,
right,
bottom,
} => DecoderEvent::PictureReady {
picture_buffer_id: buffer_id,
// Restore the truncated timestamp to its original value (hopefully).
timestamp: TIMESTAMP_TRUNCATE_FACTOR.wrapping_mul(bitstream_id as u64),
visible_rect: Rect {
left,
top,
right,
bottom,
},
},
LibvdaEvent::NotifyEndOfBitstreamBuffer { bitstream_id } => {
// We will patch the timestamp to the actual bitstream ID in `read_event`.
DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id as u32)
}
LibvdaEvent::NotifyError(resp) => DecoderEvent::NotifyError(
VideoError::BackendFailure(anyhow!("VDA failure: {}", resp)),
),
LibvdaEvent::ResetResponse(resp) => {
DecoderEvent::ResetCompleted(vda_response_to_result(resp))
}
LibvdaEvent::FlushResponse(resp) => {
DecoderEvent::FlushCompleted(vda_response_to_result(resp))
}
}
}
}
// Used by DecoderSession::get_capabilities().
fn from_pixel_format(
fmt: &libvda::PixelFormat,
mask: u64,
width_range: FormatRange,
height_range: FormatRange,
) -> FormatDesc {
let format = match fmt {
libvda::PixelFormat::NV12 => Format::NV12,
libvda::PixelFormat::YV12 => Format::YUV420,
};
let frame_formats = vec![FrameFormat {
width: width_range,
height: height_range,
bitrates: Vec::new(),
}];
FormatDesc {
mask,
format,
frame_formats,
plane_align: 1,
}
}
pub struct VdaDecoderSession {
vda_session: libvda::decode::Session,
format: Option<libvda::PixelFormat>,
/// libvda can only handle 32-bit timestamps, so we will give it the buffer ID as a timestamp
/// and map it back to the actual timestamp using this table when a decoded frame is produced.
timestamp_to_resource_id: BTreeMap<u32, u32>,
}
impl DecoderSession for VdaDecoderSession {
fn set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()> {
self.format = Some(libvda::PixelFormat::try_from(format)?);
Ok(self.vda_session.set_output_buffer_count(buffer_count)?)
}
fn decode(
&mut self,
resource_id: u32,
timestamp: u64,
resource: GuestResourceHandle,
offset: u32,
bytes_used: u32,
) -> VideoResult<()> {
let handle = match resource {
GuestResourceHandle::VirtioObject(handle) => handle,
_ => {
return Err(VideoError::BackendFailure(anyhow!(
"VDA backend only supports virtio object resources"
)))
}
};
// While the virtio-video driver handles timestamps as nanoseconds, Chrome assumes
// per-second timestamps coming. So, we need a conversion from nsec to sec. Note that this
// value should not be an unix time stamp but a frame number that the Android V4L2 C2
// decoder passes to the driver as a 32-bit integer in our implementation. So, overflow must
// not happen in this conversion.
let truncated_timestamp = (timestamp / TIMESTAMP_TRUNCATE_FACTOR) as u32;
self.timestamp_to_resource_id
.insert(truncated_timestamp, resource_id);
if truncated_timestamp as u64 * TIMESTAMP_TRUNCATE_FACTOR != timestamp {
warn!("truncation of timestamp {} resulted in precision loss. Only send timestamps with second granularity to this backend.", timestamp);
}
Ok(self.vda_session.decode(
truncated_timestamp as i32, // bitstream_id
// Steal the descriptor of the resource, as libvda will close it.
handle.desc.into_raw_descriptor(),
offset,
bytes_used,
)?)
}
fn flush(&mut self) -> VideoResult<()> {
Ok(self.vda_session.flush()?)
}
fn reset(&mut self) -> VideoResult<()> {
Ok(self.vda_session.reset()?)
}
fn clear_output_buffers(&mut self) -> VideoResult<()> {
Ok(())
}
fn event_pipe(&self) -> &dyn AsRawDescriptor {
self.vda_session.pipe()
}
fn use_output_buffer(
&mut self,
picture_buffer_id: i32,
resource: GuestResource,
) -> VideoResult<()> {
let handle = match resource.handle {
GuestResourceHandle::VirtioObject(handle) => handle,
_ => {
return Err(VideoError::BackendFailure(anyhow!(
"VDA backend only supports virtio object resources"
)))
}
};
let vda_planes: Vec<libvda::FramePlane> = resource.planes.iter().map(Into::into).collect();
Ok(self.vda_session.use_output_buffer(
picture_buffer_id,
self.format.ok_or(VideoError::BackendFailure(anyhow!(
"set_output_parameters() must be called before use_output_buffer()"
)))?,
// Steal the descriptor of the resource, as libvda will close it.
handle.desc.into_raw_descriptor(),
&vda_planes,
handle.modifier,
)?)
}
fn reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()> {
Ok(self.vda_session.reuse_output_buffer(picture_buffer_id)?)
}
fn read_event(&mut self) -> VideoResult<DecoderEvent> {
self.vda_session
.read_event()
.map(Into::into)
// Libvda returned the truncated timestamp that we gave it as the timestamp of this
// buffer. Replace it with the bitstream ID that was passed to `decode` for this
// resource.
.map(|mut e| {
if let DecoderEvent::NotifyEndOfBitstreamBuffer(timestamp) = &mut e {
let bitstream_id = self
.timestamp_to_resource_id
.remove(timestamp)
.unwrap_or_else(|| {
error!("timestamp {} not registered!", *timestamp);
0
});
*timestamp = bitstream_id;
}
e
})
.map_err(Into::into)
}
}
/// A VDA decoder backend that can be passed to `Decoder::new` in order to create a working decoder.
pub struct LibvdaDecoder(libvda::decode::VdaInstance);
impl LibvdaDecoder {
/// Create a decoder backend instance that can be used to instantiate an decoder.
pub fn new(backend_type: libvda::decode::VdaImplType) -> VideoResult<Self> {
Ok(Self(libvda::decode::VdaInstance::new(backend_type)?))
}
}
impl DecoderBackend for LibvdaDecoder {
type Session = VdaDecoderSession;
fn new_session(&mut self, format: Format) -> VideoResult<Self::Session> {
let profile = libvda::Profile::try_from(format)?;
Ok(VdaDecoderSession {
vda_session: self.0.open_session(profile).map_err(|e| {
error!("failed to open a session for {:?}: {}", format, e);
VideoError::InvalidOperation
})?,
format: None,
timestamp_to_resource_id: Default::default(),
})
}
fn get_capabilities(&self) -> Capability {
let caps = libvda::decode::VdaInstance::get_capabilities(&self.0);
// Raise the first |# of supported raw formats|-th bits because we can assume that any
// combination of (a coded format, a raw format) is valid in Chrome.
let mask = !(u64::max_value() << caps.output_formats.len());
let mut in_fmts = vec![];
let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
for fmt in caps.input_formats.iter() {
match Profile::from_libvda_profile(fmt.profile) {
Some(profile) => {
let format = profile.to_format();
in_fmts.push(FormatDesc {
mask,
format,
frame_formats: vec![FrameFormat {
width: FormatRange {
min: fmt.min_width,
max: fmt.max_width,
step: 1,
},
height: FormatRange {
min: fmt.min_height,
max: fmt.max_height,
step: 1,
},
bitrates: Vec::new(),
}],
plane_align: 1,
});
match profiles.entry(format) {
Entry::Occupied(mut e) => e.get_mut().push(profile),
Entry::Vacant(e) => {
e.insert(vec![profile]);
}
}
}
None => {
warn!(
"No virtio-video equivalent for libvda profile, skipping: {:?}",
fmt.profile
);
}
}
}
let levels: BTreeMap<Format, Vec<Level>> = if profiles.contains_key(&Format::H264) {
// We only support Level 1.0 for H.264.
vec![(Format::H264, vec![Level::H264_1_0])]
.into_iter()
.collect()
} else {
Default::default()
};
// Prepare {min, max} of {width, height}.
// While these values are associated with each input format in libvda,
// they are associated with each output format in virtio-video protocol.
// Thus, we compute max of min values and min of max values here.
let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
let width_range = FormatRange {
min: min_width.unwrap_or(0),
max: max_width.unwrap_or(0),
step: 1,
};
let height_range = FormatRange {
min: min_height.unwrap_or(0),
max: max_height.unwrap_or(0),
step: 1,
};
// Raise the first |# of supported coded formats|-th bits because we can assume that any
// combination of (a coded format, a raw format) is valid in Chrome.
let mask = !(u64::max_value() << caps.input_formats.len());
let out_fmts = caps
.output_formats
.iter()
.map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
.collect();
Capability::new(in_fmts, out_fmts, profiles, levels)
}
}