devices/virtio/video/
ffmpeg.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::error::Error;
6use std::fmt::Display;
7use std::fmt::Formatter;
8
9use base::MappedRegion;
10use base::MemoryMappingArena;
11use base::MmapError;
12use ffmpeg::avcodec::AvBuffer;
13use ffmpeg::avcodec::AvBufferSource;
14use ffmpeg::avcodec::AvError;
15use ffmpeg::avcodec::AvFrame;
16use ffmpeg::avcodec::AvFrameError;
17use ffmpeg::avcodec::AvPixelFormat;
18use ffmpeg::avcodec::Dimensions;
19use ffmpeg::avcodec::PlaneDescriptor;
20use thiserror::Error as ThisError;
21
22use crate::virtio::video::format::Format;
23use crate::virtio::video::resource::BufferHandle;
24use crate::virtio::video::resource::GuestResource;
25
26/// A simple wrapper that turns a [`MemoryMappingArena`] into an [`AvBufferSource`].
27///
28/// **Note:** Only use this if you can reasonably assume the mapped memory won't be written to from
29/// another thread or the VM! The reason [`AvBufferSource`] is not directly implemented on
30/// [`MemoryMappingArena`] is exactly due to this unsafety: If the guest or someone else writes to
31/// the buffer FFmpeg is currently reading from or writing to, undefined behavior might occur. This
32/// struct acts as an opt-in mechanism for this potential unsafety.
33pub struct MemoryMappingAvBufferSource(MemoryMappingArena);
34
35impl From<MemoryMappingArena> for MemoryMappingAvBufferSource {
36    fn from(inner: MemoryMappingArena) -> Self {
37        Self(inner)
38    }
39}
40impl AvBufferSource for MemoryMappingAvBufferSource {
41    fn as_ptr(&self) -> *const u8 {
42        self.0.as_ptr() as _
43    }
44
45    fn len(&self) -> usize {
46        self.0.size()
47    }
48
49    fn is_empty(&self) -> bool {
50        self.len() == 0
51    }
52}
53
54pub trait TryAsAvFrameExt {
55    type BufferSource;
56    type Error: Error;
57
58    /// Try to create an [`AvFrame`] from `self`.
59    ///
60    /// `wrapper` is a constructor for `T` that wraps `Self::BufferSource` and implement the
61    /// `AvBufferSource` functionality. This can be used to provide a custom destructor
62    /// implementation: for example, sending a virtio message signaling the buffer source is no
63    /// longer used and it can be recycled by the guest.
64    ///
65    /// Note that `Self::BufferSource` might not always implement `AvBufferSource`. For instance,
66    /// it's not guaranteed that a `MemoryMappingArena` created from `GuestResource` will be always
67    /// immutable, and the backend need to explicit acknowledge the potential unsoundness by
68    /// wrapping it in [`MemoryMappingAvBufferSource`].
69    fn try_as_av_frame<T: AvBufferSource + 'static>(
70        &self,
71        wrapper: impl FnOnce(Self::BufferSource) -> T,
72    ) -> Result<AvFrame, Self::Error>;
73}
74
75#[derive(Debug, ThisError)]
76pub enum GuestResourceToAvFrameError {
77    #[error("error while creating the AvFrame: {0}")]
78    AvFrameError(#[from] AvFrameError),
79    #[error("cannot mmap guest resource: {0}")]
80    MappingResource(#[from] MmapError),
81    #[error("virtio resource format is not convertible to AvPixelFormat")]
82    InvalidFormat,
83    #[error("out of memory while allocating AVBuffer")]
84    CannotAllocateAvBuffer,
85}
86
87impl From<AvError> for GuestResourceToAvFrameError {
88    fn from(e: AvError) -> Self {
89        GuestResourceToAvFrameError::AvFrameError(AvFrameError::AvError(e))
90    }
91}
92
93impl TryAsAvFrameExt for GuestResource {
94    type BufferSource = MemoryMappingArena;
95    type Error = GuestResourceToAvFrameError;
96
97    fn try_as_av_frame<T: AvBufferSource + 'static>(
98        &self,
99        wrapper: impl FnOnce(MemoryMappingArena) -> T,
100    ) -> Result<AvFrame, Self::Error> {
101        let mut builder = AvFrame::builder()?;
102        builder.set_dimensions(Dimensions {
103            width: self.width,
104            height: self.height,
105        })?;
106        let format = self
107            .format
108            .try_into()
109            .map_err(|_| GuestResourceToAvFrameError::InvalidFormat)?;
110        builder.set_format(format)?;
111        let planes = &self.planes;
112        // We have at least one plane, so we can unwrap below.
113        let len = format.plane_sizes(planes.iter().map(|p| p.stride as _), self.height)?;
114        let start = planes.iter().map(|p| p.offset).min().unwrap();
115        let end = planes
116            .iter()
117            .enumerate()
118            .map(|(i, p)| p.offset + len[i])
119            .max()
120            .unwrap();
121        let mapping = self.handle.get_mapping(start, end - start)?;
122        Ok(builder.build_owned(
123            [AvBuffer::new(wrapper(mapping))
124                .ok_or(GuestResourceToAvFrameError::CannotAllocateAvBuffer)?],
125            planes.iter().map(|p| PlaneDescriptor {
126                buffer_index: 0,
127                offset: p.offset - start,
128                stride: p.stride,
129            }),
130        )?)
131    }
132}
133
134/// The error returned when converting between `AvPixelFormat` and `Format` and there's no
135/// applicable format.
136// The empty field prevents constructing this and allows extending it in the future.
137#[derive(Debug)]
138pub struct TryFromFormatError(());
139
140impl Display for TryFromFormatError {
141    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
142        write!(
143            f,
144            "No matching format to convert between AvPixelFormat and Format"
145        )
146    }
147}
148
149impl Error for TryFromFormatError {}
150
151impl TryFrom<Format> for AvPixelFormat {
152    type Error = TryFromFormatError;
153
154    fn try_from(value: Format) -> Result<Self, Self::Error> {
155        use ffmpeg::*;
156        AvPixelFormat::try_from(match value {
157            Format::NV12 => AVPixelFormat_AV_PIX_FMT_NV12,
158            Format::YUV420 => AVPixelFormat_AV_PIX_FMT_YUV420P,
159            _ => return Err(TryFromFormatError(())),
160        })
161        .map_err(|_|
162            // The error case should never happen as long as we use valid constant values, but
163            // don't panic in case something goes wrong.
164            TryFromFormatError(()))
165    }
166}
167
168impl TryFrom<AvPixelFormat> for Format {
169    type Error = TryFromFormatError;
170
171    fn try_from(fmt: AvPixelFormat) -> Result<Self, Self::Error> {
172        // https://github.com/rust-lang/rust/issues/39371 Lint wrongly warns the consumer
173        #![allow(non_upper_case_globals)]
174        use ffmpeg::*;
175        Ok(match fmt.pix_fmt() {
176            AVPixelFormat_AV_PIX_FMT_NV12 => Format::NV12,
177            AVPixelFormat_AV_PIX_FMT_YUV420P => Format::YUV420,
178            _ => return Err(TryFromFormatError(())),
179        })
180    }
181}