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