blob: 4389001c2e967d7f23fa8dd20bfecd8a7261ac1f [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::error::Error;
use std::fmt::{Display, Formatter};
use thiserror::Error as ThisError;
use base::MappedRegion;
use base::MemoryMappingArena;
use base::MmapError;
use ffmpeg::avcodec::AvBuffer;
use ffmpeg::avcodec::AvBufferSource;
use ffmpeg::avcodec::AvError;
use ffmpeg::avcodec::AvFrame;
use ffmpeg::avcodec::AvFrameError;
use ffmpeg::avcodec::AvPixelFormat;
use ffmpeg::avcodec::Dimensions;
use ffmpeg::avcodec::PlaneDescriptor;
use crate::virtio::video::format::Format;
use crate::virtio::video::resource::BufferHandle;
use crate::virtio::video::resource::GuestResource;
/// A simple wrapper that turns a [`MemoryMappingArena`] into an [`AvBufferSource`].
///
/// **Note:** Only use this if you can reasonably assume the mapped memory won't be written to from
/// another thread or the VM! The reason [`AvBufferSource`] is not directly implemented on
/// [`MemoryMappingArena`] is exactly due to this unsafety: If the guest or someone else writes to
/// the buffer FFmpeg is currently reading from or writing to, undefined behavior might occur. This
/// struct acts as an opt-in mechanism for this potential unsafety.
pub struct MemoryMappingAvBufferSource(MemoryMappingArena);
impl From<MemoryMappingArena> for MemoryMappingAvBufferSource {
fn from(inner: MemoryMappingArena) -> Self {
Self(inner)
}
}
impl AvBufferSource for MemoryMappingAvBufferSource {
fn as_ptr(&self) -> *const u8 {
self.0.as_ptr() as _
}
fn len(&self) -> usize {
self.0.size()
}
}
pub trait TryAsAvFrameExt {
type BufferSource;
type Error: Error;
/// Try to create an [`AvFrame`] from `self`.
///
/// `wrapper` is a constructor for `T` that wraps `Self::BufferSource` and implement the
/// `AvBufferSource` functionality. This can be used to provide a custom destructor
/// implementation: for example, sending a virtio message signaling the buffer source is no
/// longer used and it can be recycled by the guest.
///
/// Note that `Self::BufferSource` might not always implement `AvBufferSource`. For instance,
/// it's not guaranteed that a `MemoryMappingArena` created from `GuestResource` will be always
/// immutable, and the backend need to explicit acknowledge the potential unsoundness by
/// wrapping it in [`MemoryMappingAvBufferSource`].
fn try_as_av_frame<T: AvBufferSource + 'static>(
&self,
wrapper: impl FnOnce(Self::BufferSource) -> T,
) -> Result<AvFrame, Self::Error>;
}
#[derive(Debug, ThisError)]
pub enum GuestResourceToAvFrameError {
#[error("error while creating the AvFrame: {0}")]
AvFrameError(#[from] AvFrameError),
#[error("cannot mmap guest resource: {0}")]
MappingResource(#[from] MmapError),
#[error("virtio resource format is not convertible to AvPixelFormat")]
InvalidFormat,
#[error("out of memory while allocating AVBuffer")]
CannotAllocateAvBuffer,
}
impl From<AvError> for GuestResourceToAvFrameError {
fn from(e: AvError) -> Self {
GuestResourceToAvFrameError::AvFrameError(AvFrameError::AvError(e))
}
}
impl TryAsAvFrameExt for GuestResource {
type BufferSource = MemoryMappingArena;
type Error = GuestResourceToAvFrameError;
fn try_as_av_frame<T: AvBufferSource + 'static>(
&self,
wrapper: impl FnOnce(MemoryMappingArena) -> T,
) -> Result<AvFrame, Self::Error> {
let mut builder = AvFrame::builder()?;
builder.set_dimensions(Dimensions {
width: self.width,
height: self.height,
})?;
let format = self
.format
.try_into()
.map_err(|_| GuestResourceToAvFrameError::InvalidFormat)?;
builder.set_format(format)?;
let planes = &self.planes;
// We have at least one plane, so we can unwrap below.
let len = format.plane_sizes(planes.iter().map(|p| p.stride as _), self.height)?;
let start = planes.iter().map(|p| p.offset).min().unwrap();
let end = planes
.iter()
.enumerate()
.map(|(i, p)| p.offset + len[i])
.max()
.unwrap();
let mapping = self.handle.get_mapping(start, end - start)?;
Ok(builder.build_owned(
[AvBuffer::new(wrapper(mapping))
.ok_or(GuestResourceToAvFrameError::CannotAllocateAvBuffer)?],
planes.iter().map(|p| PlaneDescriptor {
buffer_index: 0,
offset: p.offset - start,
stride: p.stride,
}),
)?)
}
}
/// The error returned when converting between `AvPixelFormat` and `Format` and there's no
/// applicable format.
// The empty field prevents constructing this and allows extending it in the future.
#[derive(Debug)]
pub struct TryFromFormatError(());
impl Display for TryFromFormatError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"No matching format to convert between AvPixelFormat and Format"
)
}
}
impl Error for TryFromFormatError {}
impl TryFrom<Format> for AvPixelFormat {
type Error = TryFromFormatError;
fn try_from(value: Format) -> Result<Self, Self::Error> {
use ffmpeg::*;
AvPixelFormat::try_from(match value {
Format::NV12 => AVPixelFormat_AV_PIX_FMT_NV12,
Format::YUV420 => AVPixelFormat_AV_PIX_FMT_YUV420P,
_ => return Err(TryFromFormatError(())),
})
.map_err(|_|
// The error case should never happen as long as we use valid constant values, but
// don't panic in case something goes wrong.
TryFromFormatError(()))
}
}
impl TryFrom<AvPixelFormat> for Format {
type Error = TryFromFormatError;
fn try_from(fmt: AvPixelFormat) -> Result<Self, Self::Error> {
// https://github.com/rust-lang/rust/issues/39371 Lint wrongly warns the consumer
#![allow(non_upper_case_globals)]
use ffmpeg::*;
Ok(match fmt.pix_fmt() {
AVPixelFormat_AV_PIX_FMT_NV12 => Format::NV12,
AVPixelFormat_AV_PIX_FMT_YUV420P => Format::YUV420,
_ => return Err(TryFromFormatError(())),
})
}
}