Path: blob/main/devices/src/virtio/video/encoder/backend/vda.rs
5394 views
// Copyright 2021 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::collections::btree_map::Entry;5use std::collections::BTreeMap;67use anyhow::anyhow;8use anyhow::Context;9use base::error;10use base::warn;11use base::AsRawDescriptor;12use base::IntoRawDescriptor;13use libvda::encode::EncodeCapabilities;14use libvda::encode::VeaImplType;15use libvda::encode::VeaInstance;1617use super::*;18use crate::virtio::video::encoder::*;19use crate::virtio::video::error::VideoError;20use crate::virtio::video::error::VideoResult;21use crate::virtio::video::format::Bitrate;22use crate::virtio::video::format::Format;23use crate::virtio::video::format::FormatDesc;24use crate::virtio::video::format::FormatRange;25use crate::virtio::video::format::FrameFormat;26use crate::virtio::video::format::Level;27use crate::virtio::video::format::Profile;28use crate::virtio::video::resource::GuestResource;29use crate::virtio::video::resource::GuestResourceHandle;3031impl From<Bitrate> for libvda::encode::Bitrate {32fn from(bitrate: Bitrate) -> Self {33libvda::encode::Bitrate {34mode: match bitrate {35Bitrate::Vbr { .. } => libvda::encode::BitrateMode::VBR,36Bitrate::Cbr { .. } => libvda::encode::BitrateMode::CBR,37},38target: bitrate.target(),39peak: match &bitrate {40// No need to specify peak if mode is CBR.41Bitrate::Cbr { .. } => 0,42Bitrate::Vbr { peak, .. } => *peak,43},44}45}46}4748/// A VDA encoder backend that can be passed to `EncoderDevice::new` in order to create a working49/// encoder.50pub struct LibvdaEncoder {51instance: VeaInstance,52capabilities: EncoderCapabilities,53}5455impl LibvdaEncoder {56pub fn new() -> VideoResult<Self> {57let instance = VeaInstance::new(VeaImplType::Gavea)?;5859let EncodeCapabilities {60input_formats,61output_formats,62} = instance.get_capabilities();6364if input_formats.is_empty() || output_formats.is_empty() {65error!("No input or output formats.");66return Err(VideoError::InvalidFormat);67}6869let input_format_descs: Vec<FormatDesc> = input_formats70.iter()71.map(|input_format| {72let format = match input_format {73libvda::PixelFormat::NV12 => Format::NV12,74libvda::PixelFormat::YV12 => Format::YUV420,75};7677// VEA's GetSupportedProfiles does not return resolution information.78// The input formats are retrieved by querying minigbm.79// TODO(alexlau): Populate this with real information.8081FormatDesc {82mask: !(u64::MAX << output_formats.len()),83format,84frame_formats: vec![FrameFormat {85width: FormatRange {86min: 2,87max: 4096,88step: 1,89},90height: FormatRange {91min: 2,92max: 4096,93step: 1,94},95bitrates: vec![FormatRange {96min: 0,97max: 8000,98step: 1,99}],100}],101plane_align: 1,102}103})104.collect();105106if !input_format_descs107.iter()108.any(|fd| fd.format == Format::NV12)109{110// NV12 is currently the only supported pixel format for libvda.111error!("libvda encoder does not support NV12.");112return Err(VideoError::InvalidFormat);113}114115struct ParsedFormat {116profiles: Vec<Profile>,117max_width: u32,118max_height: u32,119}120let mut parsed_formats: BTreeMap<Format, ParsedFormat> = BTreeMap::new();121122for output_format in output_formats.iter() {123// TODO(alexlau): Consider using `max_framerate_numerator` and124// `max_framerate_denominator`.125let libvda::encode::OutputProfile {126profile: libvda_profile,127max_width,128max_height,129..130} = output_format;131132let profile = match Profile::from_libvda_profile(*libvda_profile) {133Some(p) => p,134None => {135warn!("Skipping unsupported libvda profile: {:?}", libvda_profile);136continue;137}138};139140match parsed_formats.entry(profile.to_format()) {141Entry::Occupied(mut occupied_entry) => {142let parsed_format = occupied_entry.get_mut();143parsed_format.profiles.push(profile);144// If we get different libvda profiles of the same VIRTIO_VIDEO_FORMAT145// (Format) that have different max resolutions or bitrates, take the146// minimum between all of the different profiles.147parsed_format.max_width = std::cmp::min(*max_width, parsed_format.max_width);148parsed_format.max_height = std::cmp::min(*max_height, parsed_format.max_height);149}150Entry::Vacant(vacant_entry) => {151vacant_entry.insert(ParsedFormat {152profiles: vec![profile],153max_width: *max_width,154max_height: *max_height,155});156}157}158}159160let mut output_format_descs = vec![];161let mut coded_format_profiles = BTreeMap::new();162for (format, parsed_format) in parsed_formats.into_iter() {163let ParsedFormat {164mut profiles,165max_width,166max_height,167} = parsed_format;168169output_format_descs.push(FormatDesc {170mask: !(u64::MAX << output_formats.len()),171format,172frame_formats: vec![FrameFormat {173width: FormatRange {174min: 2,175max: max_width,176step: 1,177},178height: FormatRange {179min: 2,180max: max_height,181step: 1,182},183bitrates: vec![FormatRange {184min: 0,185max: 8000,186step: 1,187}],188}],189plane_align: 1,190});191192profiles.sort_unstable();193coded_format_profiles.insert(format, profiles);194}195196Ok(LibvdaEncoder {197instance,198capabilities: EncoderCapabilities {199input_format_descs,200output_format_descs,201coded_format_profiles,202},203})204}205}206207impl Encoder for LibvdaEncoder {208type Session = LibvdaEncoderSession;209210fn query_capabilities(&self) -> VideoResult<EncoderCapabilities> {211Ok(self.capabilities.clone())212}213214fn start_session(&mut self, config: SessionConfig) -> VideoResult<LibvdaEncoderSession> {215if config.dst_params.format.is_none() {216return Err(VideoError::InvalidArgument);217}218219let input_format = match config220.src_params221.format222.ok_or(VideoError::InvalidArgument)?223{224Format::NV12 => libvda::PixelFormat::NV12,225Format::YUV420 => libvda::PixelFormat::YV12,226unsupported_format => {227error!("Unsupported libvda format: {}", unsupported_format);228return Err(VideoError::InvalidArgument);229}230};231232let output_profile = match config.dst_profile.to_libvda_profile() {233Some(p) => p,234None => {235error!("Unsupported libvda profile");236return Err(VideoError::InvalidArgument);237}238};239240let config = libvda::encode::Config {241input_format,242input_visible_width: config.src_params.frame_width,243input_visible_height: config.src_params.frame_height,244output_profile,245bitrate: config.dst_bitrate.into(),246initial_framerate: if config.frame_rate == 0 {247None248} else {249Some(config.frame_rate)250},251h264_output_level: config.dst_h264_level.map(|level| {252// This value is aligned to the H264 standard definition of SPS.level_idc.253match level {254Level::H264_1_0 => 10,255Level::H264_1_1 => 11,256Level::H264_1_2 => 12,257Level::H264_1_3 => 13,258Level::H264_2_0 => 20,259Level::H264_2_1 => 21,260Level::H264_2_2 => 22,261Level::H264_3_0 => 30,262Level::H264_3_1 => 31,263Level::H264_3_2 => 32,264Level::H264_4_0 => 40,265Level::H264_4_1 => 41,266Level::H264_4_2 => 42,267Level::H264_5_0 => 50,268Level::H264_5_1 => 51,269}270}),271};272273let session = self.instance.open_session(config)?;274275Ok(LibvdaEncoderSession {276session,277next_input_buffer_id: 1,278next_output_buffer_id: 1,279})280}281282fn stop_session(&mut self, _session: LibvdaEncoderSession) -> VideoResult<()> {283// Resources will be freed when `_session` is dropped.284Ok(())285}286}287288pub struct LibvdaEncoderSession {289session: libvda::encode::Session,290next_input_buffer_id: InputBufferId,291next_output_buffer_id: OutputBufferId,292}293294impl EncoderSession for LibvdaEncoderSession {295fn encode(296&mut self,297resource: GuestResource,298timestamp: u64,299force_keyframe: bool,300) -> VideoResult<InputBufferId> {301let input_buffer_id = self.next_input_buffer_id;302let desc = match resource.handle {303GuestResourceHandle::VirtioObject(handle) => handle.desc,304_ => {305return Err(VideoError::BackendFailure(anyhow!(306"VDA backend only supports virtio object resources"307)))308}309};310311let libvda_planes = resource312.planes313.iter()314.map(|plane| libvda::FramePlane {315offset: plane.offset as i32,316stride: plane.stride as i32,317})318.collect::<Vec<_>>();319320self.session.encode(321input_buffer_id as i32,322// Steal the descriptor of the resource, as libvda will close it.323desc.into_raw_descriptor(),324&libvda_planes,325timestamp as i64,326force_keyframe,327)?;328329self.next_input_buffer_id = self.next_input_buffer_id.wrapping_add(1);330331Ok(input_buffer_id)332}333334fn use_output_buffer(335&mut self,336resource: GuestResourceHandle,337offset: u32,338size: u32,339) -> VideoResult<OutputBufferId> {340let output_buffer_id = self.next_output_buffer_id;341let desc = match resource {342GuestResourceHandle::VirtioObject(handle) => handle.desc,343_ => {344return Err(VideoError::BackendFailure(anyhow!(345"VDA backend only supports virtio object resources"346)))347}348};349350self.session.use_output_buffer(351output_buffer_id as i32,352// Steal the descriptor of the resource, as libvda will close it.353desc.into_raw_descriptor(),354offset,355size,356)?;357358self.next_output_buffer_id = self.next_output_buffer_id.wrapping_add(1);359360Ok(output_buffer_id)361}362363fn flush(&mut self) -> VideoResult<()> {364self.session365.flush()366.context("while flushing")367.map_err(VideoError::BackendFailure)368}369370fn request_encoding_params_change(371&mut self,372bitrate: Bitrate,373framerate: u32,374) -> VideoResult<()> {375self.session376.request_encoding_params_change(bitrate.into(), framerate)377.context("while requesting encoder parameter change")378.map_err(VideoError::BackendFailure)379}380381fn event_pipe(&self) -> &dyn AsRawDescriptor {382self.session.pipe()383}384385fn read_event(&mut self) -> VideoResult<EncoderEvent> {386let event = self.session.read_event()?;387388use libvda::encode::Event::*;389let encoder_event = match event {390RequireInputBuffers {391input_count,392input_frame_width,393input_frame_height,394output_buffer_size,395} => EncoderEvent::RequireInputBuffers {396input_count,397input_frame_width,398input_frame_height,399output_buffer_size,400},401ProcessedInputBuffer(id) => EncoderEvent::ProcessedInputBuffer { id: id as u32 },402ProcessedOutputBuffer {403output_buffer_id,404payload_size,405key_frame,406timestamp,407..408} => EncoderEvent::ProcessedOutputBuffer {409id: output_buffer_id as u32,410bytesused: payload_size,411keyframe: key_frame,412timestamp: timestamp as u64,413},414FlushResponse { flush_done } => EncoderEvent::FlushResponse { flush_done },415NotifyError(err) => EncoderEvent::NotifyError {416error: VideoError::BackendFailure(anyhow!(err)),417},418};419420Ok(encoder_event)421}422}423424425