Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/video/mod.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
//! This module implements the virtio video encoder and decoder devices.
6
//! The current implementation uses [v3 RFC] of the virtio-video protocol.
7
//!
8
//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg
9
10
use std::collections::BTreeMap;
11
use std::thread;
12
13
use anyhow::anyhow;
14
use anyhow::Context;
15
use base::error;
16
#[cfg(feature = "video-encoder")]
17
use base::info;
18
use base::AsRawDescriptor;
19
use base::Error as SysError;
20
use base::Event;
21
use base::RawDescriptor;
22
use base::Tube;
23
use data_model::Le32;
24
use remain::sorted;
25
use thiserror::Error;
26
use vm_memory::GuestMemory;
27
use zerocopy::IntoBytes;
28
29
use crate::virtio::copy_config;
30
use crate::virtio::virtio_device::VirtioDevice;
31
use crate::virtio::DeviceType;
32
use crate::virtio::Interrupt;
33
use crate::virtio::Queue;
34
35
#[macro_use]
36
mod macros;
37
mod async_cmd_desc_map;
38
mod command;
39
mod control;
40
#[cfg(feature = "video-decoder")]
41
pub mod decoder;
42
pub mod device;
43
#[cfg(feature = "video-encoder")]
44
mod encoder;
45
mod error;
46
mod event;
47
pub mod format;
48
mod params;
49
mod protocol;
50
pub mod resource;
51
mod response;
52
mod utils;
53
pub mod worker;
54
55
#[cfg(all(
56
feature = "video-decoder",
57
not(any(feature = "libvda", feature = "ffmpeg", feature = "vaapi"))
58
))]
59
compile_error!("The \"video-decoder\" feature requires at least one of \"ffmpeg\", \"libvda\" or \"vaapi\" to also be enabled.");
60
61
#[cfg(all(
62
feature = "video-encoder",
63
not(any(feature = "libvda", feature = "ffmpeg"))
64
))]
65
compile_error!("The \"video-encoder\" feature requires at least one of \"ffmpeg\" or \"libvda\" to also be enabled.");
66
67
#[cfg(feature = "ffmpeg")]
68
mod ffmpeg;
69
#[cfg(feature = "libvda")]
70
mod vda;
71
72
use command::ReadCmdError;
73
use device::Device;
74
use worker::Worker;
75
76
use super::device_constants::video::backend_supported_virtio_features;
77
use super::device_constants::video::virtio_video_config;
78
use super::device_constants::video::VideoBackendType;
79
use super::device_constants::video::VideoDeviceType;
80
81
// CMD_QUEUE_SIZE = max number of command descriptors for input and output queues
82
// Experimentally, it appears a stream allocates 16 input and 26 output buffers = 42 total
83
// For 8 simultaneous streams, 2 descs per buffer * 42 buffers * 8 streams = 672 descs
84
// Allocate 1024 to give some headroom in case of extra streams/buffers
85
//
86
// TODO(b/204055006): Make cmd queue size dependent of
87
// (max buf cnt for input + max buf cnt for output) * max descs per buffer * max nb of streams
88
const CMD_QUEUE_SIZE: u16 = 1024;
89
90
// EVENT_QUEUE_SIZE = max number of event descriptors for stream events like resolution changes
91
const EVENT_QUEUE_SIZE: u16 = 256;
92
93
const QUEUE_SIZES: &[u16] = &[CMD_QUEUE_SIZE, EVENT_QUEUE_SIZE];
94
95
/// An error indicating something went wrong in virtio-video's worker.
96
#[sorted]
97
#[derive(Error, Debug)]
98
pub enum Error {
99
/// Cloning a descriptor failed
100
#[error("failed to clone a descriptor: {0}")]
101
CloneDescriptorFailed(SysError),
102
/// No available descriptor in which an event is written to.
103
#[error("no available descriptor in which an event is written to")]
104
DescriptorNotAvailable,
105
/// Creating a video device failed.
106
#[cfg(feature = "video-decoder")]
107
#[error("failed to create a video device: {0}")]
108
DeviceCreationFailed(String),
109
/// Making an EventAsync failed.
110
#[error("failed to create an EventAsync: {0}")]
111
EventAsyncCreationFailed(cros_async::AsyncError),
112
/// Failed to read a virtio-video command.
113
#[error("failed to read a command from the guest: {0}")]
114
ReadFailure(ReadCmdError),
115
/// Creating WaitContext failed.
116
#[error("failed to create WaitContext: {0}")]
117
WaitContextCreationFailed(SysError),
118
/// Error while polling for events.
119
#[error("failed to wait for events: {0}")]
120
WaitError(SysError),
121
/// Failed to write an event into the event queue.
122
#[error("failed to write an event {event:?} into event queue: {error}")]
123
WriteEventFailure {
124
event: event::VideoEvt,
125
error: std::io::Error,
126
},
127
}
128
129
pub type Result<T> = std::result::Result<T, Error>;
130
131
pub struct VideoDevice {
132
device_type: VideoDeviceType,
133
backend: VideoBackendType,
134
kill_evt: Option<Event>,
135
resource_bridge: Option<Tube>,
136
base_features: u64,
137
}
138
139
impl VideoDevice {
140
pub fn new(
141
base_features: u64,
142
device_type: VideoDeviceType,
143
backend: VideoBackendType,
144
resource_bridge: Option<Tube>,
145
) -> VideoDevice {
146
VideoDevice {
147
device_type,
148
backend,
149
kill_evt: None,
150
resource_bridge,
151
base_features,
152
}
153
}
154
}
155
156
impl Drop for VideoDevice {
157
fn drop(&mut self) {
158
if let Some(kill_evt) = self.kill_evt.take() {
159
// Ignore the result because there is nothing we can do about it.
160
let _ = kill_evt.signal();
161
}
162
}
163
}
164
165
pub fn build_config(backend: VideoBackendType) -> virtio_video_config {
166
let mut device_name = [0u8; 32];
167
match backend {
168
#[cfg(feature = "libvda")]
169
VideoBackendType::Libvda => device_name[0..6].copy_from_slice("libvda".as_bytes()),
170
#[cfg(feature = "libvda")]
171
VideoBackendType::LibvdaVd => device_name[0..8].copy_from_slice("libvdavd".as_bytes()),
172
#[cfg(feature = "ffmpeg")]
173
VideoBackendType::Ffmpeg => device_name[0..6].copy_from_slice("ffmpeg".as_bytes()),
174
#[cfg(feature = "vaapi")]
175
VideoBackendType::Vaapi => device_name[0..5].copy_from_slice("vaapi".as_bytes()),
176
};
177
virtio_video_config {
178
version: Le32::from(0),
179
max_caps_length: Le32::from(1024), // Set a big number
180
max_resp_length: Le32::from(1024), // Set a big number
181
device_name,
182
}
183
}
184
185
impl VirtioDevice for VideoDevice {
186
fn keep_rds(&self) -> Vec<RawDescriptor> {
187
let mut keep_rds = Vec::new();
188
if let Some(resource_bridge) = &self.resource_bridge {
189
keep_rds.push(resource_bridge.as_raw_descriptor());
190
}
191
keep_rds
192
}
193
194
fn device_type(&self) -> DeviceType {
195
match &self.device_type {
196
VideoDeviceType::Decoder => DeviceType::VideoDecoder,
197
VideoDeviceType::Encoder => DeviceType::VideoEncoder,
198
}
199
}
200
201
fn queue_max_sizes(&self) -> &[u16] {
202
QUEUE_SIZES
203
}
204
205
fn features(&self) -> u64 {
206
self.base_features | backend_supported_virtio_features(self.backend)
207
}
208
209
fn read_config(&self, offset: u64, data: &mut [u8]) {
210
let mut cfg = build_config(self.backend);
211
copy_config(data, 0, cfg.as_mut_bytes(), offset);
212
}
213
214
fn activate(
215
&mut self,
216
mem: GuestMemory,
217
_interrupt: Interrupt,
218
mut queues: BTreeMap<usize, Queue>,
219
) -> anyhow::Result<()> {
220
if queues.len() != QUEUE_SIZES.len() {
221
return Err(anyhow!(
222
"wrong number of queues are passed: expected {}, actual {}",
223
queues.len(),
224
QUEUE_SIZES.len()
225
));
226
}
227
228
let (self_kill_evt, kill_evt) = Event::new()
229
.and_then(|e| Ok((e.try_clone()?, e)))
230
.context("failed to create kill Event pair")?;
231
self.kill_evt = Some(self_kill_evt);
232
233
let cmd_queue = queues.pop_first().unwrap().1;
234
let event_queue = queues.pop_first().unwrap().1;
235
let backend = self.backend;
236
let resource_bridge = self
237
.resource_bridge
238
.take()
239
.context("no resource bridge is passed")?;
240
let mut worker = Worker::new(cmd_queue, event_queue);
241
242
let worker_result = match &self.device_type {
243
#[cfg(feature = "video-decoder")]
244
VideoDeviceType::Decoder => thread::Builder::new()
245
.name("v_video_decoder".to_owned())
246
.spawn(move || {
247
let device: Box<dyn Device> =
248
match create_decoder_device(backend, resource_bridge, mem) {
249
Ok(value) => value,
250
Err(e) => {
251
error!("{}", e);
252
return;
253
}
254
};
255
256
if let Err(e) = worker.run(device, &kill_evt) {
257
error!("Failed to start decoder worker: {}", e);
258
};
259
// Don't return any information since the return value is never checked.
260
}),
261
#[cfg(feature = "video-encoder")]
262
VideoDeviceType::Encoder => thread::Builder::new()
263
.name("v_video_encoder".to_owned())
264
.spawn(move || {
265
let device: Box<dyn Device> = match backend {
266
#[cfg(feature = "libvda")]
267
VideoBackendType::Libvda => {
268
let vda = match encoder::backend::vda::LibvdaEncoder::new() {
269
Ok(vda) => vda,
270
Err(e) => {
271
error!("Failed to initialize VDA for encoder: {}", e);
272
return;
273
}
274
};
275
276
match encoder::EncoderDevice::new(vda, resource_bridge, mem) {
277
Ok(encoder) => Box::new(encoder),
278
Err(e) => {
279
error!("Failed to create encoder device: {}", e);
280
return;
281
}
282
}
283
}
284
#[cfg(feature = "libvda")]
285
VideoBackendType::LibvdaVd => {
286
error!("Invalid backend for encoder");
287
return;
288
}
289
#[cfg(feature = "ffmpeg")]
290
VideoBackendType::Ffmpeg => {
291
let ffmpeg = encoder::backend::ffmpeg::FfmpegEncoder::new();
292
293
match encoder::EncoderDevice::new(ffmpeg, resource_bridge, mem) {
294
Ok(encoder) => Box::new(encoder),
295
Err(e) => {
296
error!("Failed to create encoder device: {}", e);
297
return;
298
}
299
}
300
}
301
#[cfg(feature = "vaapi")]
302
VideoBackendType::Vaapi => {
303
error!("The VA-API encoder is not supported yet");
304
return;
305
}
306
};
307
308
if let Err(e) = worker.run(device, &kill_evt) {
309
error!("Failed to start encoder worker: {}", e);
310
}
311
}),
312
#[allow(unreachable_patterns)]
313
// A device will never be created for a device type not enabled
314
device_type => unreachable!("Not compiled with {:?} enabled", device_type),
315
};
316
worker_result.with_context(|| {
317
format!(
318
"failed to spawn virtio_video worker for {:?}",
319
&self.device_type
320
)
321
})?;
322
Ok(())
323
}
324
}
325
326
#[cfg(feature = "video-decoder")]
327
pub fn create_decoder_device(
328
backend: VideoBackendType,
329
resource_bridge: Tube,
330
mem: GuestMemory,
331
) -> Result<Box<dyn Device>> {
332
use decoder::backend::DecoderBackend;
333
334
let backend = match backend {
335
#[cfg(feature = "libvda")]
336
VideoBackendType::Libvda => {
337
decoder::backend::vda::LibvdaDecoder::new(libvda::decode::VdaImplType::Gavda)
338
.map_err(|e| {
339
Error::DeviceCreationFailed(format!(
340
"Failed to initialize VDA for decoder: {e}"
341
))
342
})?
343
.into_trait_object()
344
}
345
#[cfg(feature = "libvda")]
346
VideoBackendType::LibvdaVd => {
347
decoder::backend::vda::LibvdaDecoder::new(libvda::decode::VdaImplType::Gavd)
348
.map_err(|e| {
349
Error::DeviceCreationFailed(format!("Failed to initialize VD for decoder: {e}"))
350
})?
351
.into_trait_object()
352
}
353
#[cfg(feature = "ffmpeg")]
354
VideoBackendType::Ffmpeg => {
355
decoder::backend::ffmpeg::FfmpegDecoder::new().into_trait_object()
356
}
357
#[cfg(feature = "vaapi")]
358
VideoBackendType::Vaapi => decoder::backend::vaapi::VaapiDecoder::new()
359
.map_err(|e| {
360
Error::DeviceCreationFailed(format!(
361
"Failed to initialize VA-API driver for decoder: {e}"
362
))
363
})?
364
.into_trait_object(),
365
};
366
367
Ok(Box::new(decoder::Decoder::new(
368
backend,
369
resource_bridge,
370
mem,
371
)))
372
}
373
374
/// Manages the zero-length, EOS-marked buffer signaling the end of a stream.
375
///
376
/// Both the decoder and encoder need to signal end-of-stream events using a zero-sized buffer
377
/// marked with the `VIRTIO_VIDEO_BUFFER_FLAG_EOS` flag. This struct allows to keep a buffer aside
378
/// for that purpose.
379
///
380
/// TODO(b/149725148): Remove this when libvda supports buffer flags.
381
#[cfg(feature = "video-encoder")]
382
struct EosBufferManager {
383
stream_id: u32,
384
eos_buffer: Option<u32>,
385
client_awaits_eos: bool,
386
responses: Vec<device::VideoEvtResponseType>,
387
}
388
389
#[cfg(feature = "video-encoder")]
390
impl EosBufferManager {
391
/// Create a new EOS manager for stream `stream_id`.
392
fn new(stream_id: u32) -> Self {
393
Self {
394
stream_id,
395
eos_buffer: None,
396
client_awaits_eos: false,
397
responses: Default::default(),
398
}
399
}
400
401
/// Attempt to reserve buffer `buffer_id` for use as EOS buffer.
402
///
403
/// This method should be called by the output buffer queueing code of the device. It returns
404
/// `true` if the buffer has been kept aside for EOS, `false` otherwise (which means another
405
/// buffer is already kept aside for EOS). If `true` is returned, the client must not use the
406
/// buffer for any other purpose.
407
fn try_reserve_eos_buffer(&mut self, buffer_id: u32) -> bool {
408
let is_none = self.eos_buffer.is_none();
409
410
if is_none {
411
info!(
412
"stream {}: keeping buffer {} aside to signal EOS.",
413
self.stream_id, buffer_id
414
);
415
self.eos_buffer = Some(buffer_id);
416
}
417
418
is_none
419
}
420
421
/// Attempt to complete an EOS event using the previously reserved buffer, if available.
422
///
423
/// `responses` is a vector of responses to be sent to the driver along with the EOS buffer. If
424
/// an EOS buffer has been made available using the `try_reserve_eos_buffer` method, then this
425
/// method returns the `responses` vector with the EOS buffer dequeue appended in first
426
/// position.
427
///
428
/// If no EOS buffer is available, then the contents of `responses` is put aside, and will be
429
/// returned the next time this method is called with an EOS buffer available. When this
430
/// happens, `client_awaits_eos` will be set to true, and the client can check this member and
431
/// call this method again right after queuing the next buffer to obtain the EOS response as
432
/// soon as is possible.
433
fn try_complete_eos(
434
&mut self,
435
responses: Vec<device::VideoEvtResponseType>,
436
) -> Option<Vec<device::VideoEvtResponseType>> {
437
let eos_buffer_id = self.eos_buffer.take().or_else(|| {
438
info!("stream {}: no EOS resource available on successful flush response, waiting for next buffer to be queued.", self.stream_id);
439
self.client_awaits_eos = true;
440
if !self.responses.is_empty() {
441
error!("stream {}: EOS requested while one is already in progress. This is a bug!", self.stream_id);
442
}
443
self.responses = responses;
444
None
445
})?;
446
447
let eos_tag = device::AsyncCmdTag::Queue {
448
stream_id: self.stream_id,
449
queue_type: command::QueueType::Output,
450
resource_id: eos_buffer_id,
451
};
452
453
let eos_response = response::CmdResponse::ResourceQueue {
454
timestamp: 0,
455
flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
456
size: 0,
457
};
458
459
self.client_awaits_eos = false;
460
461
info!(
462
"stream {}: signaling EOS using buffer {}.",
463
self.stream_id, eos_buffer_id
464
);
465
466
let mut responses = std::mem::take(&mut self.responses);
467
responses.insert(
468
0,
469
device::VideoEvtResponseType::AsyncCmd(device::AsyncCmdResponse::from_response(
470
eos_tag,
471
eos_response,
472
)),
473
);
474
475
Some(responses)
476
}
477
478
/// Reset the state of the manager, for use during e.g. stream resets.
479
fn reset(&mut self) {
480
self.eos_buffer = None;
481
self.client_awaits_eos = false;
482
self.responses.clear();
483
}
484
}
485
486