• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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