Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/video/decoder/backend/vda.rs
5394 views
1
// Copyright 2020 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::collections::btree_map::Entry;
6
use std::collections::BTreeMap;
7
use std::convert::TryFrom;
8
9
use anyhow::anyhow;
10
use base::error;
11
use base::warn;
12
use base::AsRawDescriptor;
13
use base::IntoRawDescriptor;
14
use libvda::decode::Event as LibvdaEvent;
15
16
use crate::virtio::video::decoder::backend::*;
17
use crate::virtio::video::decoder::Capability;
18
use crate::virtio::video::error::VideoError;
19
use crate::virtio::video::error::VideoResult;
20
use crate::virtio::video::format::*;
21
22
/// Since libvda only accepts 32-bit timestamps, we are going to truncate the frame 64-bit timestamp
23
/// (of nanosecond granularity) to only keep seconds granularity. This would result in information
24
/// being lost on a regular client, but the Android C2 decoder only sends timestamps with second
25
/// granularity, so this approach is going to work there. However, this means that this backend is
26
/// very unlikely to work with any other guest software. We accept this fact because it is
27
/// impossible to use outside of ChromeOS anyway.
28
const TIMESTAMP_TRUNCATE_FACTOR: u64 = 1_000_000_000;
29
30
impl TryFrom<Format> for libvda::Profile {
31
type Error = VideoError;
32
33
fn try_from(format: Format) -> Result<Self, Self::Error> {
34
Ok(match format {
35
Format::VP8 => libvda::Profile::VP8,
36
Format::VP9 => libvda::Profile::VP9Profile0,
37
Format::H264 => libvda::Profile::H264ProfileBaseline,
38
Format::Hevc => libvda::Profile::HevcProfileMain,
39
_ => {
40
error!("specified format {} is not supported by VDA", format);
41
return Err(VideoError::InvalidParameter);
42
}
43
})
44
}
45
}
46
47
impl TryFrom<Format> for libvda::PixelFormat {
48
type Error = VideoError;
49
50
fn try_from(format: Format) -> Result<Self, Self::Error> {
51
Ok(match format {
52
Format::NV12 => libvda::PixelFormat::NV12,
53
_ => {
54
error!("specified format {} is not supported by VDA", format);
55
return Err(VideoError::InvalidParameter);
56
}
57
})
58
}
59
}
60
61
impl From<&FramePlane> for libvda::FramePlane {
62
fn from(plane: &FramePlane) -> Self {
63
libvda::FramePlane {
64
offset: plane.offset as i32,
65
stride: plane.stride as i32,
66
}
67
}
68
}
69
70
impl From<libvda::decode::Event> for DecoderEvent {
71
fn from(event: libvda::decode::Event) -> Self {
72
// We cannot use the From trait here since neither libvda::decode::Response
73
// no std::result::Result are defined in the current crate.
74
fn vda_response_to_result(resp: libvda::decode::Response) -> VideoResult<()> {
75
match resp {
76
libvda::decode::Response::Success => Ok(()),
77
resp => Err(VideoError::BackendFailure(anyhow!("VDA failure: {}", resp))),
78
}
79
}
80
81
match event {
82
LibvdaEvent::ProvidePictureBuffers {
83
min_num_buffers,
84
width,
85
height,
86
visible_rect_left,
87
visible_rect_top,
88
visible_rect_right,
89
visible_rect_bottom,
90
} => DecoderEvent::ProvidePictureBuffers {
91
min_num_buffers,
92
width,
93
height,
94
visible_rect: Rect {
95
left: visible_rect_left,
96
top: visible_rect_top,
97
right: visible_rect_right,
98
bottom: visible_rect_bottom,
99
},
100
},
101
LibvdaEvent::PictureReady {
102
buffer_id,
103
bitstream_id,
104
..
105
} => DecoderEvent::PictureReady {
106
picture_buffer_id: buffer_id,
107
// Restore the truncated timestamp to its original value (hopefully).
108
timestamp: TIMESTAMP_TRUNCATE_FACTOR.wrapping_mul(bitstream_id as u64),
109
},
110
LibvdaEvent::NotifyEndOfBitstreamBuffer { bitstream_id } => {
111
// We will patch the timestamp to the actual bitstream ID in `read_event`.
112
DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id as u32)
113
}
114
LibvdaEvent::NotifyError(resp) => DecoderEvent::NotifyError(
115
VideoError::BackendFailure(anyhow!("VDA failure: {}", resp)),
116
),
117
LibvdaEvent::ResetResponse(resp) => {
118
DecoderEvent::ResetCompleted(vda_response_to_result(resp))
119
}
120
LibvdaEvent::FlushResponse(resp) => {
121
DecoderEvent::FlushCompleted(vda_response_to_result(resp))
122
}
123
}
124
}
125
}
126
127
// Used by DecoderSession::get_capabilities().
128
fn from_pixel_format(
129
fmt: &libvda::PixelFormat,
130
mask: u64,
131
width_range: FormatRange,
132
height_range: FormatRange,
133
) -> FormatDesc {
134
let format = match fmt {
135
libvda::PixelFormat::NV12 => Format::NV12,
136
libvda::PixelFormat::YV12 => Format::YUV420,
137
};
138
139
let frame_formats = vec![FrameFormat {
140
width: width_range,
141
height: height_range,
142
bitrates: Vec::new(),
143
}];
144
145
FormatDesc {
146
mask,
147
format,
148
frame_formats,
149
plane_align: 1,
150
}
151
}
152
153
pub struct VdaDecoderSession {
154
vda_session: libvda::decode::Session,
155
format: Option<libvda::PixelFormat>,
156
/// libvda can only handle 32-bit timestamps, so we will give it the buffer ID as a timestamp
157
/// and map it back to the actual timestamp using this table when a decoded frame is produced.
158
timestamp_to_resource_id: BTreeMap<u32, u32>,
159
}
160
161
impl DecoderSession for VdaDecoderSession {
162
fn set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()> {
163
self.format = Some(libvda::PixelFormat::try_from(format)?);
164
Ok(self.vda_session.set_output_buffer_count(buffer_count)?)
165
}
166
167
fn decode(
168
&mut self,
169
resource_id: u32,
170
timestamp: u64,
171
resource: GuestResourceHandle,
172
offset: u32,
173
bytes_used: u32,
174
) -> VideoResult<()> {
175
let handle = match resource {
176
GuestResourceHandle::VirtioObject(handle) => handle,
177
_ => {
178
return Err(VideoError::BackendFailure(anyhow!(
179
"VDA backend only supports virtio object resources"
180
)))
181
}
182
};
183
184
// While the virtio-video driver handles timestamps as nanoseconds, Chrome assumes
185
// per-second timestamps coming. So, we need a conversion from nsec to sec. Note that this
186
// value should not be an unix time stamp but a frame number that the Android V4L2 C2
187
// decoder passes to the driver as a 32-bit integer in our implementation. So, overflow must
188
// not happen in this conversion.
189
let truncated_timestamp = (timestamp / TIMESTAMP_TRUNCATE_FACTOR) as u32;
190
self.timestamp_to_resource_id
191
.insert(truncated_timestamp, resource_id);
192
193
if truncated_timestamp as u64 * TIMESTAMP_TRUNCATE_FACTOR != timestamp {
194
warn!("truncation of timestamp {} resulted in precision loss. Only send timestamps with second granularity to this backend.", timestamp);
195
}
196
197
Ok(self.vda_session.decode(
198
truncated_timestamp as i32, // bitstream_id
199
// Steal the descriptor of the resource, as libvda will close it.
200
handle.desc.into_raw_descriptor(),
201
offset,
202
bytes_used,
203
)?)
204
}
205
206
fn flush(&mut self) -> VideoResult<()> {
207
Ok(self.vda_session.flush()?)
208
}
209
210
fn reset(&mut self) -> VideoResult<()> {
211
Ok(self.vda_session.reset()?)
212
}
213
214
fn clear_output_buffers(&mut self) -> VideoResult<()> {
215
Ok(())
216
}
217
218
fn event_pipe(&self) -> &dyn AsRawDescriptor {
219
self.vda_session.pipe()
220
}
221
222
fn use_output_buffer(
223
&mut self,
224
picture_buffer_id: i32,
225
resource: GuestResource,
226
) -> VideoResult<()> {
227
let handle = match resource.handle {
228
GuestResourceHandle::VirtioObject(handle) => handle,
229
_ => {
230
return Err(VideoError::BackendFailure(anyhow!(
231
"VDA backend only supports virtio object resources"
232
)))
233
}
234
};
235
let vda_planes: Vec<libvda::FramePlane> = resource.planes.iter().map(Into::into).collect();
236
237
Ok(self.vda_session.use_output_buffer(
238
picture_buffer_id,
239
self.format.ok_or(VideoError::BackendFailure(anyhow!(
240
"set_output_parameters() must be called before use_output_buffer()"
241
)))?,
242
// Steal the descriptor of the resource, as libvda will close it.
243
handle.desc.into_raw_descriptor(),
244
&vda_planes,
245
handle.modifier,
246
)?)
247
}
248
249
fn reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()> {
250
Ok(self.vda_session.reuse_output_buffer(picture_buffer_id)?)
251
}
252
253
fn read_event(&mut self) -> VideoResult<DecoderEvent> {
254
self.vda_session
255
.read_event()
256
.map(Into::into)
257
// Libvda returned the truncated timestamp that we gave it as the timestamp of this
258
// buffer. Replace it with the bitstream ID that was passed to `decode` for this
259
// resource.
260
.map(|mut e| {
261
if let DecoderEvent::NotifyEndOfBitstreamBuffer(timestamp) = &mut e {
262
let bitstream_id = self
263
.timestamp_to_resource_id
264
.remove(timestamp)
265
.unwrap_or_else(|| {
266
error!("timestamp {} not registered!", *timestamp);
267
0
268
});
269
*timestamp = bitstream_id;
270
}
271
e
272
})
273
.map_err(Into::into)
274
}
275
}
276
277
/// A VDA decoder backend that can be passed to `Decoder::new` in order to create a working decoder.
278
pub struct LibvdaDecoder(libvda::decode::VdaInstance);
279
280
/// SAFETY: safe because the Rcs in `VdaInstance` are always used from the same thread.
281
unsafe impl Send for LibvdaDecoder {}
282
283
impl LibvdaDecoder {
284
/// Create a decoder backend instance that can be used to instantiate an decoder.
285
pub fn new(backend_type: libvda::decode::VdaImplType) -> VideoResult<Self> {
286
Ok(Self(libvda::decode::VdaInstance::new(backend_type)?))
287
}
288
}
289
290
impl DecoderBackend for LibvdaDecoder {
291
type Session = VdaDecoderSession;
292
293
fn new_session(&mut self, format: Format) -> VideoResult<Self::Session> {
294
let profile = libvda::Profile::try_from(format)?;
295
296
Ok(VdaDecoderSession {
297
vda_session: self.0.open_session(profile).map_err(|e| {
298
error!("failed to open a session for {:?}: {}", format, e);
299
VideoError::InvalidOperation
300
})?,
301
format: None,
302
timestamp_to_resource_id: Default::default(),
303
})
304
}
305
306
fn get_capabilities(&self) -> Capability {
307
let caps = libvda::decode::VdaInstance::get_capabilities(&self.0);
308
309
// Raise the first |# of supported raw formats|-th bits because we can assume that any
310
// combination of (a coded format, a raw format) is valid in Chrome.
311
let mask = !(u64::MAX << caps.output_formats.len());
312
313
let mut in_fmts = vec![];
314
let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
315
for fmt in caps.input_formats.iter() {
316
match Profile::from_libvda_profile(fmt.profile) {
317
Some(profile) => {
318
let format = profile.to_format();
319
in_fmts.push(FormatDesc {
320
mask,
321
format,
322
frame_formats: vec![FrameFormat {
323
width: FormatRange {
324
min: fmt.min_width,
325
max: fmt.max_width,
326
step: 1,
327
},
328
height: FormatRange {
329
min: fmt.min_height,
330
max: fmt.max_height,
331
step: 1,
332
},
333
bitrates: Vec::new(),
334
}],
335
plane_align: 1,
336
});
337
match profiles.entry(format) {
338
Entry::Occupied(mut e) => e.get_mut().push(profile),
339
Entry::Vacant(e) => {
340
e.insert(vec![profile]);
341
}
342
}
343
}
344
None => {
345
warn!(
346
"No virtio-video equivalent for libvda profile, skipping: {:?}",
347
fmt.profile
348
);
349
}
350
}
351
}
352
353
let levels: BTreeMap<Format, Vec<Level>> = if profiles.contains_key(&Format::H264) {
354
// We only support Level 1.0 for H.264.
355
vec![(Format::H264, vec![Level::H264_1_0])]
356
.into_iter()
357
.collect()
358
} else {
359
Default::default()
360
};
361
362
// Prepare {min, max} of {width, height}.
363
// While these values are associated with each input format in libvda,
364
// they are associated with each output format in virtio-video protocol.
365
// Thus, we compute max of min values and min of max values here.
366
let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
367
let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
368
let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
369
let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
370
let width_range = FormatRange {
371
min: min_width.unwrap_or(0),
372
max: max_width.unwrap_or(0),
373
step: 1,
374
};
375
let height_range = FormatRange {
376
min: min_height.unwrap_or(0),
377
max: max_height.unwrap_or(0),
378
step: 1,
379
};
380
381
// Raise the first |# of supported coded formats|-th bits because we can assume that any
382
// combination of (a coded format, a raw format) is valid in Chrome.
383
let mask = !(u64::MAX << caps.input_formats.len());
384
let out_fmts = caps
385
.output_formats
386
.iter()
387
.map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
388
.collect();
389
390
Capability::new(in_fmts, out_fmts, profiles, levels)
391
}
392
}
393
394