Path: blob/main/devices/src/virtio/video/encoder/backend/ffmpeg.rs
5394 views
// Copyright 2022 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::BTreeMap;5use std::collections::VecDeque;6use std::os::raw::c_int;7use std::ptr;8use std::sync::Arc;9use std::sync::Weak;1011use anyhow::anyhow;12use anyhow::Context;13use base::error;14use base::AsRawDescriptor;15use base::MappedRegion;16use base::MemoryMappingArena;17use ffmpeg::avcodec::AvBufferSource;18use ffmpeg::avcodec::AvCodec;19use ffmpeg::avcodec::AvCodecContext;20use ffmpeg::avcodec::AvCodecIterator;21use ffmpeg::avcodec::AvFrame;22use ffmpeg::avcodec::AvPacket;23use ffmpeg::avcodec::Dimensions;24use ffmpeg::avcodec::TryReceiveResult;25use ffmpeg::max_buffer_alignment;26use ffmpeg::AVPictureType_AV_PICTURE_TYPE_I;27use ffmpeg::AVRational;28use ffmpeg::AV_PKT_FLAG_KEY;2930use crate::virtio::video::encoder::backend::Encoder;31use crate::virtio::video::encoder::backend::EncoderSession;32use crate::virtio::video::encoder::EncoderCapabilities;33use crate::virtio::video::encoder::EncoderEvent;34use crate::virtio::video::encoder::InputBufferId;35use crate::virtio::video::encoder::OutputBufferId;36use crate::virtio::video::encoder::SessionConfig;37use crate::virtio::video::error::VideoError;38use crate::virtio::video::error::VideoResult;39use crate::virtio::video::ffmpeg::TryAsAvFrameExt;40use crate::virtio::video::format::Bitrate;41use crate::virtio::video::format::Format;42use crate::virtio::video::format::FormatDesc;43use crate::virtio::video::format::FormatRange;44use crate::virtio::video::format::FrameFormat;45use crate::virtio::video::format::Profile;46use crate::virtio::video::resource::BufferHandle;47use crate::virtio::video::resource::GuestResource;48use crate::virtio::video::resource::GuestResourceHandle;49use crate::virtio::video::utils::EventQueue;50use crate::virtio::video::utils::SyncEventQueue;5152/// Structure wrapping a backing memory mapping for an input frame that can be used as a libavcodec53/// buffer source. It also sends a `ProcessedInputBuffer` event when dropped.54struct InputBuffer {55/// Memory mapping to the input frame.56mapping: MemoryMappingArena,57/// Bistream ID that will be sent as part of the `ProcessedInputBuffer` event.58buffer_id: InputBufferId,59/// Pointer to the event queue to send the `ProcessedInputBuffer` event to. The event will60/// not be sent if the pointer becomes invalid.61event_queue: Weak<SyncEventQueue<EncoderEvent>>,62}6364impl Drop for InputBuffer {65fn drop(&mut self) {66match self.event_queue.upgrade() {67None => (),68// If the event queue is still valid, send the event signaling we can be reused.69Some(event_queue) => event_queue70.queue_event(EncoderEvent::ProcessedInputBuffer { id: self.buffer_id })71.unwrap_or_else(|e| {72error!("cannot send end of input buffer notification: {:#}", e)73}),74}75}76}7778impl AvBufferSource for InputBuffer {79fn as_ptr(&self) -> *const u8 {80self.mapping.as_ptr()81}8283fn len(&self) -> usize {84self.mapping.size()85}8687fn is_empty(&self) -> bool {88self.len() == 089}90}9192enum CodecJob {93Frame(AvFrame),94Flush,95}9697pub struct FfmpegEncoderSession {98/// Queue of events waiting to be read by the client.99event_queue: Arc<SyncEventQueue<EncoderEvent>>,100101/// FIFO of jobs submitted by the client and waiting to be performed.102codec_jobs: VecDeque<CodecJob>,103/// Queue of (unfilled) output buffers to fill with upcoming encoder output.104output_queue: VecDeque<(OutputBufferId, MemoryMappingArena)>,105/// `true` if a flush is pending. While a pending flush exist, input buffers are temporarily106/// held on and not sent to the encoder. An actual flush call will be issued when we run out of107/// output buffers (to defend against FFmpeg bugs), and we'll try to receive outputs again108/// until we receive another code indicating the flush has completed, at which point this109/// flag will be reset.110is_flushing: bool,111112/// The libav context for this session.113context: AvCodecContext,114115next_input_buffer_id: InputBufferId,116next_output_buffer_id: OutputBufferId,117}118119impl FfmpegEncoderSession {120/// Try to send one input frame to the codec for encode.121///122/// Returns `Ok(true)` if the frame was successfully queued, `Ok(false)` if the frame was not123/// queued due to the queue being full or an in-progress flushing, and `Err` in case of errors.124fn try_send_input_job(&mut self) -> VideoResult<bool> {125// When a flush is queued, drain buffers.126if self.is_flushing {127return Ok(false);128}129130match self.codec_jobs.front() {131Some(CodecJob::Frame(b)) => {132let result = self133.context134.try_send_frame(b)135.context("while sending frame")136.map_err(VideoError::BackendFailure);137// This look awkward but we have to do it like this since VideoResult doesn't138// implement PartialEq.139if let Ok(false) = result {140} else {141self.codec_jobs.pop_front().unwrap();142}143result144}145Some(CodecJob::Flush) => {146self.codec_jobs.pop_front().unwrap();147148// Queue a flush. The actual flush will be performed when receive returns EAGAIN.149self.is_flushing = true;150Ok(true)151}152None => Ok(false),153}154}155156/// Try to retrieve one encoded packet from the codec, and if success, deliver it to the guest.157///158/// Returns `Ok(true)` if the packet was successfully retrieved and the guest was signaled,159/// `Ok(false)` if there's no full packet available right now, and `Err` in case of error.160fn try_receive_packet(&mut self) -> VideoResult<bool> {161let (buffer_id, out_buf) = match self.output_queue.front_mut() {162Some(p) => p,163None => return Ok(false),164};165166let mut packet = AvPacket::empty();167168match self169.context170.try_receive_packet(&mut packet)171.context("while receiving packet")172{173Ok(TryReceiveResult::TryAgain) => {174if !self.is_flushing {175return Ok(false);176}177178// Flush the encoder, then move on to draining.179if let Err(err) = self.context.flush_encoder() {180self.is_flushing = false;181self.event_queue182.queue_event(EncoderEvent::FlushResponse { flush_done: false })183.context("while flushing")184.map_err(VideoError::BackendFailure)?;185return Err(err)186.context("while flushing")187.map_err(VideoError::BackendFailure);188}189self.try_receive_packet()190}191Ok(TryReceiveResult::FlushCompleted) => {192self.is_flushing = false;193self.event_queue194.queue_event(EncoderEvent::FlushResponse { flush_done: true })195.map_err(Into::into)196.map_err(VideoError::BackendFailure)?;197self.context.reset();198Ok(false)199}200Ok(TryReceiveResult::Received) => {201let packet_size = packet.as_ref().size as usize;202if packet_size > out_buf.size() {203return Err(VideoError::BackendFailure(anyhow!(204"encoded packet does not fit in output buffer"205)));206}207// SAFETY:208// Safe because packet.as_ref().data and out_buf.as_ptr() are valid references and209// we did bound check above.210unsafe {211ptr::copy_nonoverlapping(packet.as_ref().data, out_buf.as_ptr(), packet_size);212}213self.event_queue214.queue_event(EncoderEvent::ProcessedOutputBuffer {215id: *buffer_id,216bytesused: packet.as_ref().size as _,217keyframe: (packet.as_ref().flags as u32 & AV_PKT_FLAG_KEY) != 0,218timestamp: packet.as_ref().dts as _,219})220.map_err(Into::into)221.map_err(VideoError::BackendFailure)?;222self.output_queue.pop_front();223Ok(true)224}225Err(e) => Err(VideoError::BackendFailure(e)),226}227}228229/// Try to progress through the encoding pipeline, either by sending input frames or by230/// retrieving output packets and delivering them to the guest.231fn try_encode(&mut self) -> VideoResult<()> {232// Go through the pipeline stages as long as it makes some kind of progress.233loop {234let mut progress = false;235// Use |= instead of || to avoid short-circuiting, which is harmless but makes the236// execution order weird.237progress |= self.try_send_input_job()?;238progress |= self.try_receive_packet()?;239if !progress {240break;241}242}243Ok(())244}245}246247impl EncoderSession for FfmpegEncoderSession {248fn encode(249&mut self,250resource: GuestResource,251timestamp: u64,252force_keyframe: bool,253) -> VideoResult<InputBufferId> {254let buffer_id = self.next_input_buffer_id;255self.next_input_buffer_id = buffer_id.wrapping_add(1);256257let mut frame: AvFrame = resource258.try_as_av_frame(|mapping| InputBuffer {259mapping,260buffer_id,261event_queue: Arc::downgrade(&self.event_queue),262})263.context("while creating input AvFrame")264.map_err(VideoError::BackendFailure)?;265266if force_keyframe {267frame.set_pict_type(AVPictureType_AV_PICTURE_TYPE_I);268}269frame.set_pts(timestamp as i64);270self.codec_jobs.push_back(CodecJob::Frame(frame));271self.try_encode()?;272273Ok(buffer_id)274}275276fn use_output_buffer(277&mut self,278resource: GuestResourceHandle,279offset: u32,280size: u32,281) -> VideoResult<OutputBufferId> {282let buffer_id = self.next_output_buffer_id;283self.next_output_buffer_id = buffer_id.wrapping_add(1);284285let mapping = resource286.get_mapping(offset as usize, size as usize)287.context("while mapping output buffer")288.map_err(VideoError::BackendFailure)?;289290self.output_queue.push_back((buffer_id, mapping));291self.try_encode()?;292Ok(buffer_id)293}294295fn flush(&mut self) -> VideoResult<()> {296if self.is_flushing {297return Err(VideoError::BackendFailure(anyhow!(298"flush is already in progress"299)));300}301self.codec_jobs.push_back(CodecJob::Flush);302self.try_encode()?;303Ok(())304}305306fn request_encoding_params_change(307&mut self,308bitrate: Bitrate,309framerate: u32,310) -> VideoResult<()> {311match bitrate {312Bitrate::Cbr { target } => {313self.context.set_bit_rate(target as u64);314}315Bitrate::Vbr { target, peak } => {316self.context.set_bit_rate(target as u64);317self.context.set_max_bit_rate(peak as u64);318}319}320// TODO(b/241492607): support fractional frame rates.321self.context.set_time_base(AVRational {322num: 1,323den: framerate as c_int,324});325Ok(())326}327328fn event_pipe(&self) -> &dyn AsRawDescriptor {329self.event_queue.as_ref()330}331332fn read_event(&mut self) -> VideoResult<EncoderEvent> {333self.event_queue334.dequeue_event()335.context("while reading encoder event")336.map_err(VideoError::BackendFailure)337}338}339340pub struct FfmpegEncoder {341codecs: BTreeMap<Format, AvCodec>,342}343344impl FfmpegEncoder {345/// Create a new ffmpeg encoder backend instance.346pub fn new() -> Self {347// Find all the encoders supported by libav and store them.348let codecs = AvCodecIterator::new()349.filter_map(|codec| {350if !codec.is_encoder() {351return None;352}353354let codec_name = codec.name();355356// Only retain software encoders we know with their corresponding format. Other357// encoder might depend on hardware (e.g. *_qsv) which we can't use.358let format = match codec_name {359"libx264" => Format::H264,360"libvpx" => Format::VP8,361"libvpx-vp9" => Format::VP9,362"libx265" => Format::Hevc,363_ => return None,364};365366Some((format, codec))367})368.collect();369370Self { codecs }371}372}373374impl Encoder for FfmpegEncoder {375type Session = FfmpegEncoderSession;376377fn query_capabilities(&self) -> VideoResult<EncoderCapabilities> {378let codecs = &self.codecs;379let mut format_idx = BTreeMap::new();380let mut input_format_descs = vec![];381let output_format_descs = codecs382.iter()383.enumerate()384.map(|(i, (&format, codec))| {385let mut in_formats = 0;386for in_format in codec.pixel_format_iter() {387if let Ok(in_format) = Format::try_from(in_format) {388let idx = format_idx.entry(in_format).or_insert_with(|| {389let idx = input_format_descs.len();390input_format_descs.push(FormatDesc {391mask: 0,392format: in_format,393frame_formats: vec![FrameFormat {394// These frame sizes are arbitrary, but avcodec does not seem to395// have any specific restriction in that regard (or any way to396// query the supported resolutions).397width: FormatRange {398min: 64,399max: 16384,400step: 1,401},402height: FormatRange {403min: 64,404max: 16384,405step: 1,406},407bitrates: Default::default(),408}],409plane_align: max_buffer_alignment() as u32,410});411idx412});413input_format_descs[*idx].mask |= 1 << i;414in_formats |= 1 << *idx;415}416}417FormatDesc {418mask: in_formats,419format,420frame_formats: vec![FrameFormat {421// These frame sizes are arbitrary, but avcodec does not seem to have any422// specific restriction in that regard (or any way to query the supported423// resolutions).424width: FormatRange {425min: 64,426max: 16384,427step: 1,428},429height: FormatRange {430min: 64,431max: 16384,432step: 1,433},434bitrates: Default::default(),435}],436plane_align: max_buffer_alignment() as u32,437}438})439.collect();440// TODO(ishitatsuyuki): right now we haven't plumbed the profile handling yet and will use441// a hard coded set of profiles. Make this support more profiles when442// we implement the conversion between virtio and ffmpeg profiles.443let coded_format_profiles = codecs444.iter()445.map(|(&format, _codec)| {446(447format,448match format {449Format::H264 => vec![Profile::H264Baseline],450Format::Hevc => vec![Profile::HevcMain],451Format::VP8 => vec![Profile::VP8Profile0],452Format::VP9 => vec![Profile::VP9Profile0],453_ => vec![],454},455)456})457.collect();458let caps = EncoderCapabilities {459input_format_descs,460output_format_descs,461coded_format_profiles,462};463464Ok(caps)465}466467fn start_session(&mut self, config: SessionConfig) -> VideoResult<Self::Session> {468let dst_format = config469.dst_params470.format471.ok_or(VideoError::InvalidOperation)?;472let codec = self473.codecs474.get(&dst_format)475.ok_or(VideoError::InvalidFormat)?;476let pix_fmt = config477.src_params478.format479.ok_or(VideoError::InvalidOperation)?480.try_into()481.map_err(|_| VideoError::InvalidFormat)?;482let context = codec483.build_encoder()484.and_then(|mut b| {485b.set_pix_fmt(pix_fmt);486b.set_dimensions(Dimensions {487width: config.src_params.frame_width,488height: config.src_params.frame_height,489});490b.set_time_base(AVRational {491num: 1,492den: config.frame_rate as _,493});494b.build()495})496.context("while creating new session")497.map_err(VideoError::BackendFailure)?;498let session = FfmpegEncoderSession {499event_queue: Arc::new(500EventQueue::new()501.context("while creating encoder session")502.map_err(VideoError::BackendFailure)?503.into(),504),505codec_jobs: Default::default(),506output_queue: Default::default(),507is_flushing: false,508context,509next_input_buffer_id: 0,510next_output_buffer_id: 0,511};512session513.event_queue514.queue_event(EncoderEvent::RequireInputBuffers {515input_count: 4,516input_frame_height: config.src_params.frame_height,517input_frame_width: config.src_params.frame_width,518output_buffer_size: 16 * 1024 * 1024,519})520.context("while sending buffer request")521.map_err(VideoError::BackendFailure)?;522Ok(session)523}524525fn stop_session(&mut self, _session: Self::Session) -> VideoResult<()> {526// Just Drop.527Ok(())528}529}530531532