openipc-video 0.1.33

Low-latency platform video decoding for OpenIPC applications
Documentation
use std::{rc::Rc, sync::Arc};

use cros_codecs::{
    backend::vaapi::decoder::VaapiBackend,
    decoder::{
        stateless::{h264::H264, h265::H265, DecodeError, StatelessDecoder, StatelessVideoDecoder},
        BlockingMode, DecodedHandle, DecoderEvent,
    },
    libva::Display,
    video_frame::gbm_video_frame::GbmDevice,
};

use crate::{FrameDimensions, VideoCodec, VideoError};

use super::frame::{VaFrame, VaFramePool};

type H264Decoder = StatelessDecoder<H264, VaapiBackend<VaFrame>>;
type H265Decoder = StatelessDecoder<H265, VaapiBackend<VaFrame>>;

pub(crate) struct ReadyFrame {
    pub(crate) frame: Arc<VaFrame>,
    pub(crate) token: u64,
    pub(crate) dimensions: FrameDimensions,
}

pub(crate) enum SessionSubmit {
    Accepted(Vec<ReadyFrame>),
    Backpressure(Vec<ReadyFrame>),
}

enum CodecSession {
    H264(Box<H264Decoder>),
    H265(Box<H265Decoder>),
}

pub(crate) struct VaapiSession {
    decoder: CodecSession,
    pool: VaFramePool,
}

impl VaapiSession {
    pub(crate) fn new(
        codec: VideoCodec,
        display: Rc<Display>,
        gbm: Arc<GbmDevice>,
        retained_frame_allowance: usize,
    ) -> Result<Self, VideoError> {
        let decoder = match codec {
            VideoCodec::H264 => CodecSession::H264(Box::new(
                H264Decoder::new_vaapi(display, BlockingMode::NonBlocking)
                    .map_err(|error| backend_error("create H.264 decoder", error))?,
            )),
            VideoCodec::H265 => CodecSession::H265(Box::new(
                H265Decoder::new_vaapi(display, BlockingMode::NonBlocking)
                    .map_err(|error| backend_error("create H.265 decoder", error))?,
            )),
        };
        Ok(Self {
            decoder,
            pool: VaFramePool::new(gbm, retained_frame_allowance),
        })
    }

    pub(crate) fn submit(
        &mut self,
        token: u64,
        bitstream: &[u8],
    ) -> Result<SessionSubmit, VideoError> {
        match &mut self.decoder {
            CodecSession::H264(decoder) => {
                decode_all(decoder.as_mut(), &mut self.pool, token, bitstream)
            }
            CodecSession::H265(decoder) => {
                decode_all(decoder.as_mut(), &mut self.pool, token, bitstream)
            }
        }
    }

    pub(crate) fn flush(&mut self) -> Result<Vec<ReadyFrame>, VideoError> {
        match &mut self.decoder {
            CodecSession::H264(decoder) => flush_decoder(decoder.as_mut(), &mut self.pool),
            CodecSession::H265(decoder) => flush_decoder(decoder.as_mut(), &mut self.pool),
        }
    }
}

fn decode_all<D>(
    decoder: &mut D,
    pool: &mut VaFramePool,
    token: u64,
    bitstream: &[u8],
) -> Result<SessionSubmit, VideoError>
where
    D: StatelessVideoDecoder,
    D::Handle: DecodedHandle<Frame = VaFrame>,
{
    let mut offset = 0;
    let mut frames = Vec::new();
    while offset < bitstream.len() {
        let result = decoder.decode(token, &bitstream[offset..], &mut || pool.allocate());
        match result {
            Ok(0) => {
                return Err(VideoError::Backend {
                    backend: "vaapi",
                    operation: "decode access unit",
                    message: "decoder consumed zero bytes".to_owned(),
                });
            }
            Ok(consumed) => {
                offset = offset.saturating_add(consumed);
                drain_events(decoder, pool, &mut frames)?;
            }
            Err(DecodeError::CheckEvents) => {
                let handled = drain_events(decoder, pool, &mut frames)?;
                if !handled {
                    return Err(VideoError::Backend {
                        backend: "vaapi",
                        operation: "process decoder event",
                        message: "decoder requested events but exposed none".to_owned(),
                    });
                }
            }
            Err(DecodeError::NotEnoughOutputBuffers(_)) => {
                drain_events(decoder, pool, &mut frames)?;
                return Ok(SessionSubmit::Backpressure(frames));
            }
            Err(error) => return Err(backend_error("decode access unit", error)),
        }
    }
    drain_events(decoder, pool, &mut frames)?;
    Ok(SessionSubmit::Accepted(frames))
}

fn flush_decoder<D>(decoder: &mut D, pool: &mut VaFramePool) -> Result<Vec<ReadyFrame>, VideoError>
where
    D: StatelessVideoDecoder,
    D::Handle: DecodedHandle<Frame = VaFrame>,
{
    decoder
        .flush()
        .map_err(|error| backend_error("flush decoder", error))?;
    let mut frames = Vec::new();
    drain_events(decoder, pool, &mut frames)?;
    Ok(frames)
}

fn drain_events<D>(
    decoder: &mut D,
    pool: &mut VaFramePool,
    frames: &mut Vec<ReadyFrame>,
) -> Result<bool, VideoError>
where
    D: StatelessVideoDecoder,
    D::Handle: DecodedHandle<Frame = VaFrame>,
{
    let mut handled = false;
    while let Some(event) = decoder.next_event() {
        handled = true;
        match event {
            DecoderEvent::FormatChanged => {
                let info = decoder.stream_info().ok_or_else(|| VideoError::Backend {
                    backend: "vaapi",
                    operation: "negotiate output format",
                    message: "decoder did not publish stream information".to_owned(),
                })?;
                pool.resize(
                    info.display_resolution,
                    info.coded_resolution,
                    info.min_num_frames,
                )?;
            }
            DecoderEvent::FrameReady(handle) => {
                handle
                    .sync()
                    .map_err(|error| backend_error("synchronize output surface", error))?;
                let display = handle.display_resolution();
                frames.push(ReadyFrame {
                    frame: handle.video_frame(),
                    token: handle.timestamp(),
                    dimensions: FrameDimensions {
                        width: display.width,
                        height: display.height,
                    },
                });
            }
        }
    }
    Ok(handled)
}

fn backend_error(operation: &'static str, error: impl std::fmt::Display) -> VideoError {
    VideoError::Backend {
        backend: "vaapi",
        operation,
        message: error.to_string(),
    }
}