xref: /aosp_15_r20/external/crosvm/devices/src/virtio/video/mod.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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::AsBytes;
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 mod decoder;
42 pub mod device;
43 #[cfg(feature = "video-encoder")]
44 mod encoder;
45 mod error;
46 mod event;
47 mod format;
48 mod params;
49 mod protocol;
50 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 {
new( base_features: u64, device_type: VideoDeviceType, backend: VideoBackendType, resource_bridge: Option<Tube>, ) -> VideoDevice140     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 {
drop(&mut self)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 
build_config(backend: VideoBackendType) -> virtio_video_config165 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 {
keep_rds(&self) -> Vec<RawDescriptor>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 
device_type(&self) -> DeviceType194     fn device_type(&self) -> DeviceType {
195         match &self.device_type {
196             VideoDeviceType::Decoder => DeviceType::VideoDecoder,
197             VideoDeviceType::Encoder => DeviceType::VideoEncoder,
198         }
199     }
200 
queue_max_sizes(&self) -> &[u16]201     fn queue_max_sizes(&self) -> &[u16] {
202         QUEUE_SIZES
203     }
204 
features(&self) -> u64205     fn features(&self) -> u64 {
206         self.base_features | backend_supported_virtio_features(self.backend)
207     }
208 
read_config(&self, offset: u64, data: &mut [u8])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_bytes_mut(), offset);
212     }
213 
activate( &mut self, mem: GuestMemory, _interrupt: Interrupt, mut queues: BTreeMap<usize, Queue>, ) -> anyhow::Result<()>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")]
create_decoder_device( backend: VideoBackendType, resource_bridge: Tube, mem: GuestMemory, ) -> Result<Box<dyn Device>>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: {}",
341                         e
342                     ))
343                 })?
344                 .into_trait_object()
345         }
346         #[cfg(feature = "libvda")]
347         VideoBackendType::LibvdaVd => {
348             decoder::backend::vda::LibvdaDecoder::new(libvda::decode::VdaImplType::Gavd)
349                 .map_err(|e| {
350                     Error::DeviceCreationFailed(format!(
351                         "Failed to initialize VD for decoder: {}",
352                         e
353                     ))
354                 })?
355                 .into_trait_object()
356         }
357         #[cfg(feature = "ffmpeg")]
358         VideoBackendType::Ffmpeg => {
359             decoder::backend::ffmpeg::FfmpegDecoder::new().into_trait_object()
360         }
361         #[cfg(feature = "vaapi")]
362         VideoBackendType::Vaapi => decoder::backend::vaapi::VaapiDecoder::new()
363             .map_err(|e| {
364                 Error::DeviceCreationFailed(format!(
365                     "Failed to initialize VA-API driver for decoder: {}",
366                     e
367                 ))
368             })?
369             .into_trait_object(),
370     };
371 
372     Ok(Box::new(decoder::Decoder::new(
373         backend,
374         resource_bridge,
375         mem,
376     )))
377 }
378 
379 /// Manages the zero-length, EOS-marked buffer signaling the end of a stream.
380 ///
381 /// Both the decoder and encoder need to signal end-of-stream events using a zero-sized buffer
382 /// marked with the `VIRTIO_VIDEO_BUFFER_FLAG_EOS` flag. This struct allows to keep a buffer aside
383 /// for that purpose.
384 ///
385 /// TODO(b/149725148): Remove this when libvda supports buffer flags.
386 #[cfg(feature = "video-encoder")]
387 struct EosBufferManager {
388     stream_id: u32,
389     eos_buffer: Option<u32>,
390     client_awaits_eos: bool,
391     responses: Vec<device::VideoEvtResponseType>,
392 }
393 
394 #[cfg(feature = "video-encoder")]
395 impl EosBufferManager {
396     /// Create a new EOS manager for stream `stream_id`.
new(stream_id: u32) -> Self397     fn new(stream_id: u32) -> Self {
398         Self {
399             stream_id,
400             eos_buffer: None,
401             client_awaits_eos: false,
402             responses: Default::default(),
403         }
404     }
405 
406     /// Attempt to reserve buffer `buffer_id` for use as EOS buffer.
407     ///
408     /// This method should be called by the output buffer queueing code of the device. It returns
409     /// `true` if the buffer has been kept aside for EOS, `false` otherwise (which means another
410     /// buffer is already kept aside for EOS). If `true` is returned, the client must not use the
411     /// buffer for any other purpose.
try_reserve_eos_buffer(&mut self, buffer_id: u32) -> bool412     fn try_reserve_eos_buffer(&mut self, buffer_id: u32) -> bool {
413         let is_none = self.eos_buffer.is_none();
414 
415         if is_none {
416             info!(
417                 "stream {}: keeping buffer {} aside to signal EOS.",
418                 self.stream_id, buffer_id
419             );
420             self.eos_buffer = Some(buffer_id);
421         }
422 
423         is_none
424     }
425 
426     /// Attempt to complete an EOS event using the previously reserved buffer, if available.
427     ///
428     /// `responses` is a vector of responses to be sent to the driver along with the EOS buffer. If
429     /// an EOS buffer has been made available using the `try_reserve_eos_buffer` method, then this
430     /// method returns the `responses` vector with the EOS buffer dequeue appended in first
431     /// position.
432     ///
433     /// If no EOS buffer is available, then the contents of `responses` is put aside, and will be
434     /// returned the next time this method is called with an EOS buffer available. When this
435     /// happens, `client_awaits_eos` will be set to true, and the client can check this member and
436     /// call this method again right after queuing the next buffer to obtain the EOS response as
437     /// soon as is possible.
try_complete_eos( &mut self, responses: Vec<device::VideoEvtResponseType>, ) -> Option<Vec<device::VideoEvtResponseType>>438     fn try_complete_eos(
439         &mut self,
440         responses: Vec<device::VideoEvtResponseType>,
441     ) -> Option<Vec<device::VideoEvtResponseType>> {
442         let eos_buffer_id = self.eos_buffer.take().or_else(|| {
443             info!("stream {}: no EOS resource available on successful flush response, waiting for next buffer to be queued.", self.stream_id);
444             self.client_awaits_eos = true;
445             if !self.responses.is_empty() {
446                 error!("stream {}: EOS requested while one is already in progress. This is a bug!", self.stream_id);
447             }
448             self.responses = responses;
449             None
450         })?;
451 
452         let eos_tag = device::AsyncCmdTag::Queue {
453             stream_id: self.stream_id,
454             queue_type: command::QueueType::Output,
455             resource_id: eos_buffer_id,
456         };
457 
458         let eos_response = response::CmdResponse::ResourceQueue {
459             timestamp: 0,
460             flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
461             size: 0,
462         };
463 
464         self.client_awaits_eos = false;
465 
466         info!(
467             "stream {}: signaling EOS using buffer {}.",
468             self.stream_id, eos_buffer_id
469         );
470 
471         let mut responses = std::mem::take(&mut self.responses);
472         responses.insert(
473             0,
474             device::VideoEvtResponseType::AsyncCmd(device::AsyncCmdResponse::from_response(
475                 eos_tag,
476                 eos_response,
477             )),
478         );
479 
480         Some(responses)
481     }
482 
483     /// Reset the state of the manager, for use during e.g. stream resets.
reset(&mut self)484     fn reset(&mut self) {
485         self.eos_buffer = None;
486         self.client_awaits_eos = false;
487         self.responses.clear();
488     }
489 }
490