use std::collections::BTreeMap;
use std::thread;
use anyhow::anyhow;
use anyhow::Context;
use base::error;
#[cfg(feature = "video-encoder")]
use base::info;
use base::AsRawDescriptor;
use base::Error as SysError;
use base::Event;
use base::RawDescriptor;
use base::Tube;
use data_model::Le32;
use remain::sorted;
use thiserror::Error;
use vm_memory::GuestMemory;
use zerocopy::AsBytes;
use crate::virtio::copy_config;
use crate::virtio::virtio_device::VirtioDevice;
use crate::virtio::DeviceType;
use crate::virtio::Interrupt;
use crate::virtio::Queue;
#[macro_use]
mod macros;
mod async_cmd_desc_map;
mod command;
mod control;
#[cfg(feature = "video-decoder")]
mod decoder;
pub mod device;
#[cfg(feature = "video-encoder")]
mod encoder;
mod error;
mod event;
mod format;
mod params;
mod protocol;
mod resource;
mod response;
mod utils;
pub mod worker;
#[cfg(all(
feature = "video-decoder",
not(any(feature = "libvda", feature = "ffmpeg", feature = "vaapi"))
))]
compile_error!("The \"video-decoder\" feature requires at least one of \"ffmpeg\", \"libvda\" or \"vaapi\" to also be enabled.");
#[cfg(all(
feature = "video-encoder",
not(any(feature = "libvda", feature = "ffmpeg"))
))]
compile_error!("The \"video-encoder\" feature requires at least one of \"ffmpeg\" or \"libvda\" to also be enabled.");
#[cfg(feature = "ffmpeg")]
mod ffmpeg;
#[cfg(feature = "libvda")]
mod vda;
use command::ReadCmdError;
use device::Device;
use worker::Worker;
use super::device_constants::video::backend_supported_virtio_features;
use super::device_constants::video::virtio_video_config;
use super::device_constants::video::VideoBackendType;
use super::device_constants::video::VideoDeviceType;
const CMD_QUEUE_SIZE: u16 = 1024;
const EVENT_QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[CMD_QUEUE_SIZE, EVENT_QUEUE_SIZE];
#[sorted]
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to clone a descriptor: {0}")]
CloneDescriptorFailed(SysError),
#[error("no available descriptor in which an event is written to")]
DescriptorNotAvailable,
#[cfg(feature = "video-decoder")]
#[error("failed to create a video device: {0}")]
DeviceCreationFailed(String),
#[error("failed to create an EventAsync: {0}")]
EventAsyncCreationFailed(cros_async::AsyncError),
#[error("failed to read a command from the guest: {0}")]
ReadFailure(ReadCmdError),
#[error("failed to create WaitContext: {0}")]
WaitContextCreationFailed(SysError),
#[error("failed to wait for events: {0}")]
WaitError(SysError),
#[error("failed to write an event {event:?} into event queue: {error}")]
WriteEventFailure {
event: event::VideoEvt,
error: std::io::Error,
},
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct VideoDevice {
device_type: VideoDeviceType,
backend: VideoBackendType,
kill_evt: Option<Event>,
resource_bridge: Option<Tube>,
base_features: u64,
}
impl VideoDevice {
pub fn new(
base_features: u64,
device_type: VideoDeviceType,
backend: VideoBackendType,
resource_bridge: Option<Tube>,
) -> VideoDevice {
VideoDevice {
device_type,
backend,
kill_evt: None,
resource_bridge,
base_features,
}
}
}
impl Drop for VideoDevice {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
let _ = kill_evt.signal();
}
}
}
pub fn build_config(backend: VideoBackendType) -> virtio_video_config {
let mut device_name = [0u8; 32];
match backend {
#[cfg(feature = "libvda")]
VideoBackendType::Libvda => device_name[0..6].copy_from_slice("libvda".as_bytes()),
#[cfg(feature = "libvda")]
VideoBackendType::LibvdaVd => device_name[0..8].copy_from_slice("libvdavd".as_bytes()),
#[cfg(feature = "ffmpeg")]
VideoBackendType::Ffmpeg => device_name[0..6].copy_from_slice("ffmpeg".as_bytes()),
#[cfg(feature = "vaapi")]
VideoBackendType::Vaapi => device_name[0..5].copy_from_slice("vaapi".as_bytes()),
};
virtio_video_config {
version: Le32::from(0),
max_caps_length: Le32::from(1024), max_resp_length: Le32::from(1024), device_name,
}
}
impl VirtioDevice for VideoDevice {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut keep_rds = Vec::new();
if let Some(resource_bridge) = &self.resource_bridge {
keep_rds.push(resource_bridge.as_raw_descriptor());
}
keep_rds
}
fn device_type(&self) -> DeviceType {
match &self.device_type {
VideoDeviceType::Decoder => DeviceType::VideoDecoder,
VideoDeviceType::Encoder => DeviceType::VideoEncoder,
}
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.base_features | backend_supported_virtio_features(self.backend)
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let mut cfg = build_config(self.backend);
copy_config(data, 0, cfg.as_bytes_mut(), offset);
}
fn activate(
&mut self,
mem: GuestMemory,
_interrupt: Interrupt,
mut queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
if queues.len() != QUEUE_SIZES.len() {
return Err(anyhow!(
"wrong number of queues are passed: expected {}, actual {}",
queues.len(),
QUEUE_SIZES.len()
));
}
let (self_kill_evt, kill_evt) = Event::new()
.and_then(|e| Ok((e.try_clone()?, e)))
.context("failed to create kill Event pair")?;
self.kill_evt = Some(self_kill_evt);
let cmd_queue = queues.pop_first().unwrap().1;
let event_queue = queues.pop_first().unwrap().1;
let backend = self.backend;
let resource_bridge = self
.resource_bridge
.take()
.context("no resource bridge is passed")?;
let mut worker = Worker::new(cmd_queue, event_queue);
let worker_result = match &self.device_type {
#[cfg(feature = "video-decoder")]
VideoDeviceType::Decoder => thread::Builder::new()
.name("v_video_decoder".to_owned())
.spawn(move || {
let device: Box<dyn Device> =
match create_decoder_device(backend, resource_bridge, mem) {
Ok(value) => value,
Err(e) => {
error!("{}", e);
return;
}
};
if let Err(e) = worker.run(device, &kill_evt) {
error!("Failed to start decoder worker: {}", e);
};
}),
#[cfg(feature = "video-encoder")]
VideoDeviceType::Encoder => thread::Builder::new()
.name("v_video_encoder".to_owned())
.spawn(move || {
let device: Box<dyn Device> = match backend {
#[cfg(feature = "libvda")]
VideoBackendType::Libvda => {
let vda = match encoder::backend::vda::LibvdaEncoder::new() {
Ok(vda) => vda,
Err(e) => {
error!("Failed to initialize VDA for encoder: {}", e);
return;
}
};
match encoder::EncoderDevice::new(vda, resource_bridge, mem) {
Ok(encoder) => Box::new(encoder),
Err(e) => {
error!("Failed to create encoder device: {}", e);
return;
}
}
}
#[cfg(feature = "libvda")]
VideoBackendType::LibvdaVd => {
error!("Invalid backend for encoder");
return;
}
#[cfg(feature = "ffmpeg")]
VideoBackendType::Ffmpeg => {
let ffmpeg = encoder::backend::ffmpeg::FfmpegEncoder::new();
match encoder::EncoderDevice::new(ffmpeg, resource_bridge, mem) {
Ok(encoder) => Box::new(encoder),
Err(e) => {
error!("Failed to create encoder device: {}", e);
return;
}
}
}
#[cfg(feature = "vaapi")]
VideoBackendType::Vaapi => {
error!("The VA-API encoder is not supported yet");
return;
}
};
if let Err(e) = worker.run(device, &kill_evt) {
error!("Failed to start encoder worker: {}", e);
}
}),
#[allow(unreachable_patterns)]
device_type => unreachable!("Not compiled with {:?} enabled", device_type),
};
worker_result.with_context(|| {
format!(
"failed to spawn virtio_video worker for {:?}",
&self.device_type
)
})?;
Ok(())
}
}
#[cfg(feature = "video-decoder")]
pub fn create_decoder_device(
backend: VideoBackendType,
resource_bridge: Tube,
mem: GuestMemory,
) -> Result<Box<dyn Device>> {
use decoder::backend::DecoderBackend;
let backend = match backend {
#[cfg(feature = "libvda")]
VideoBackendType::Libvda => {
decoder::backend::vda::LibvdaDecoder::new(libvda::decode::VdaImplType::Gavda)
.map_err(|e| {
Error::DeviceCreationFailed(format!(
"Failed to initialize VDA for decoder: {}",
e
))
})?
.into_trait_object()
}
#[cfg(feature = "libvda")]
VideoBackendType::LibvdaVd => {
decoder::backend::vda::LibvdaDecoder::new(libvda::decode::VdaImplType::Gavd)
.map_err(|e| {
Error::DeviceCreationFailed(format!(
"Failed to initialize VD for decoder: {}",
e
))
})?
.into_trait_object()
}
#[cfg(feature = "ffmpeg")]
VideoBackendType::Ffmpeg => {
decoder::backend::ffmpeg::FfmpegDecoder::new().into_trait_object()
}
#[cfg(feature = "vaapi")]
VideoBackendType::Vaapi => decoder::backend::vaapi::VaapiDecoder::new()
.map_err(|e| {
Error::DeviceCreationFailed(format!(
"Failed to initialize VA-API driver for decoder: {}",
e
))
})?
.into_trait_object(),
};
Ok(Box::new(decoder::Decoder::new(
backend,
resource_bridge,
mem,
)))
}
#[cfg(feature = "video-encoder")]
struct EosBufferManager {
stream_id: u32,
eos_buffer: Option<u32>,
client_awaits_eos: bool,
responses: Vec<device::VideoEvtResponseType>,
}
#[cfg(feature = "video-encoder")]
impl EosBufferManager {
fn new(stream_id: u32) -> Self {
Self {
stream_id,
eos_buffer: None,
client_awaits_eos: false,
responses: Default::default(),
}
}
fn try_reserve_eos_buffer(&mut self, buffer_id: u32) -> bool {
let is_none = self.eos_buffer.is_none();
if is_none {
info!(
"stream {}: keeping buffer {} aside to signal EOS.",
self.stream_id, buffer_id
);
self.eos_buffer = Some(buffer_id);
}
is_none
}
fn try_complete_eos(
&mut self,
responses: Vec<device::VideoEvtResponseType>,
) -> Option<Vec<device::VideoEvtResponseType>> {
let eos_buffer_id = self.eos_buffer.take().or_else(|| {
info!("stream {}: no EOS resource available on successful flush response, waiting for next buffer to be queued.", self.stream_id);
self.client_awaits_eos = true;
if !self.responses.is_empty() {
error!("stream {}: EOS requested while one is already in progress. This is a bug!", self.stream_id);
}
self.responses = responses;
None
})?;
let eos_tag = device::AsyncCmdTag::Queue {
stream_id: self.stream_id,
queue_type: command::QueueType::Output,
resource_id: eos_buffer_id,
};
let eos_response = response::CmdResponse::ResourceQueue {
timestamp: 0,
flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
size: 0,
};
self.client_awaits_eos = false;
info!(
"stream {}: signaling EOS using buffer {}.",
self.stream_id, eos_buffer_id
);
let mut responses = std::mem::take(&mut self.responses);
responses.insert(
0,
device::VideoEvtResponseType::AsyncCmd(device::AsyncCmdResponse::from_response(
eos_tag,
eos_response,
)),
);
Some(responses)
}
fn reset(&mut self) {
self.eos_buffer = None;
self.client_awaits_eos = false;
self.responses.clear();
}
}