Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/video/ffmpeg.rs
5394 views
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 {
36
fn from(inner: MemoryMappingArena) -> Self {
37
Self(inner)
38
}
39
}
40
impl 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
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`].
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 {
88
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
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 {
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
149
impl Error for TryFromFormatError {}
150
151
impl 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
168
impl 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
}
182
183