1use 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
81const CMD_QUEUE_SIZE: u16 = 1024;
89
90const EVENT_QUEUE_SIZE: u16 = 256;
92
93const QUEUE_SIZES: &[u16] = &[CMD_QUEUE_SIZE, EVENT_QUEUE_SIZE];
94
95#[sorted]
97#[derive(Error, Debug)]
98pub enum Error {
99 #[error("failed to clone a descriptor: {0}")]
101 CloneDescriptorFailed(SysError),
102 #[error("no available descriptor in which an event is written to")]
104 DescriptorNotAvailable,
105 #[cfg(feature = "video-decoder")]
107 #[error("failed to create a video device: {0}")]
108 DeviceCreationFailed(String),
109 #[error("failed to create an EventAsync: {0}")]
111 EventAsyncCreationFailed(cros_async::AsyncError),
112 #[error("failed to read a command from the guest: {0}")]
114 ReadFailure(ReadCmdError),
115 #[error("failed to create WaitContext: {0}")]
117 WaitContextCreationFailed(SysError),
118 #[error("failed to wait for events: {0}")]
120 WaitError(SysError),
121 #[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 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), max_resp_length: Le32::from(1024), 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 }),
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 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#[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 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 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 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 fn reset(&mut self) {
480 self.eos_buffer = None;
481 self.client_awaits_eos = false;
482 self.responses.clear();
483 }
484}