xref: /aosp_15_r20/external/crosvm/devices/src/virtio/video/ffmpeg.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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 
5 use std::error::Error;
6 use std::fmt::Display;
7 use std::fmt::Formatter;
8 
9 use base::MappedRegion;
10 use base::MemoryMappingArena;
11 use base::MmapError;
12 use ffmpeg::avcodec::AvBuffer;
13 use ffmpeg::avcodec::AvBufferSource;
14 use ffmpeg::avcodec::AvError;
15 use ffmpeg::avcodec::AvFrame;
16 use ffmpeg::avcodec::AvFrameError;
17 use ffmpeg::avcodec::AvPixelFormat;
18 use ffmpeg::avcodec::Dimensions;
19 use ffmpeg::avcodec::PlaneDescriptor;
20 use thiserror::Error as ThisError;
21 
22 use crate::virtio::video::format::Format;
23 use crate::virtio::video::resource::BufferHandle;
24 use 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.
33 pub struct MemoryMappingAvBufferSource(MemoryMappingArena);
34 
35 impl From<MemoryMappingArena> for MemoryMappingAvBufferSource {
from(inner: MemoryMappingArena) -> Self36     fn from(inner: MemoryMappingArena) -> Self {
37         Self(inner)
38     }
39 }
40 impl AvBufferSource for MemoryMappingAvBufferSource {
as_ptr(&self) -> *const u841     fn as_ptr(&self) -> *const u8 {
42         self.0.as_ptr() as _
43     }
44 
len(&self) -> usize45     fn len(&self) -> usize {
46         self.0.size()
47     }
48 
is_empty(&self) -> bool49     fn is_empty(&self) -> bool {
50         self.len() == 0
51     }
52 }
53 
54 pub 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`].
try_as_av_frame<T: AvBufferSource + 'static>( &self, wrapper: impl FnOnce(Self::BufferSource) -> T, ) -> Result<AvFrame, Self::Error>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)]
76 pub 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 
87 impl From<AvError> for GuestResourceToAvFrameError {
from(e: AvError) -> Self88     fn from(e: AvError) -> Self {
89         GuestResourceToAvFrameError::AvFrameError(AvFrameError::AvError(e))
90     }
91 }
92 
93 impl TryAsAvFrameExt for GuestResource {
94     type BufferSource = MemoryMappingArena;
95     type Error = GuestResourceToAvFrameError;
96 
try_as_av_frame<T: AvBufferSource + 'static>( &self, wrapper: impl FnOnce(MemoryMappingArena) -> T, ) -> Result<AvFrame, Self::Error>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)]
138 pub struct TryFromFormatError(());
139 
140 impl Display for TryFromFormatError {
fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result141     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 
149 impl Error for TryFromFormatError {}
150 
151 impl TryFrom<Format> for AvPixelFormat {
152     type Error = TryFromFormatError;
153 
try_from(value: Format) -> Result<Self, Self::Error>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 
168 impl TryFrom<AvPixelFormat> for Format {
169     type Error = TryFromFormatError;
170 
try_from(fmt: AvPixelFormat) -> Result<Self, Self::Error>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 }
182