devices/virtio/video/
mod.rs

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
10use std::collections::BTreeMap;
11use std::thread;
12
13use anyhow::anyhow;
14use anyhow::Context;
15use base::error;
16#[cfg(feature = "video-encoder")]
17use base::info;
18use base::AsRawDescriptor;
19use base::Error as SysError;
20use base::Event;
21use base::RawDescriptor;
22use base::Tube;
23use data_model::Le32;
24use remain::sorted;
25use thiserror::Error;
26use vm_memory::GuestMemory;
27use zerocopy::IntoBytes;
28
29use crate::virtio::copy_config;
30use crate::virtio::virtio_device::VirtioDevice;
31use crate::virtio::DeviceType;
32use crate::virtio::Interrupt;
33use crate::virtio::Queue;
34
35#[macro_use]
36mod macros;
37mod async_cmd_desc_map;
38mod command;
39mod control;
40#[cfg(feature = "video-decoder")]
41pub mod decoder;
42pub mod device;
43#[cfg(feature = "video-encoder")]
44mod encoder;
45mod error;
46mod event;
47pub mod format;
48mod params;
49mod protocol;
50pub mod resource;
51mod response;
52mod utils;
53pub mod worker;
54
55#[cfg(all(
56    feature = "video-decoder",
57    not(any(feature = "libvda", feature = "ffmpeg", feature = "vaapi"))
58))]
59compile_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))]
65compile_error!("The \"video-encoder\" feature requires at least one of \"ffmpeg\" or \"libvda\" to also be enabled.");
66
67#[cfg(feature = "ffmpeg")]
68mod ffmpeg;
69#[cfg(feature = "libvda")]
70mod vda;
71
72use command::ReadCmdError;
73use device::Device;
74use worker::Worker;
75
76use super::device_constants::video::backend_supported_virtio_features;
77use super::device_constants::video::virtio_video_config;
78use super::device_constants::video::VideoBackendType;
79use 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
88const CMD_QUEUE_SIZE: u16 = 1024;
89
90// EVENT_QUEUE_SIZE = max number of event descriptors for stream events like resolution changes
91const EVENT_QUEUE_SIZE: u16 = 256;
92
93const 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)]
98pub 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
129pub type Result<T> = std::result::Result<T, Error>;
130
131pub 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
139impl 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
156impl 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
165pub 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
185impl 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")]
327pub 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")]
382struct 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")]
390impl 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}