use std::collections::VecDeque;
use std::time::Duration;
use crate::{DecodeError, VideoDecoder, VideoDecoderConfig, VideoOutputPreference};
use mediaway_common::{
CodecKind, Packet, PixelFormat, Rational, StreamInfo, VideoFrame, VideoFrameStorage,
VideoGeometry,
};
use ndk::media::media_codec::{
BufferInfo, DequeuedInputBufferResult, DequeuedOutputBufferInfoResult, MediaCodec,
MediaCodecDirection,
};
use ndk::media::media_format::MediaFormat;
use super::codec::{is_supported_video_codec, mime_type};
use super::csd::split_csd;
use super::nv12::{CropRect, strip_and_crop_nv12};
const COLOR_FORMAT_YUV420_SEMI_PLANAR: i32 = 21;
const BUFFER_FLAG_END_OF_STREAM: u32 = 4;
const INPUT_DEQUEUE_TIMEOUT: Duration = Duration::from_millis(20);
const MAX_INPUT_RETRIES: usize = 50;
const OUTPUT_DEQUEUE_TIMEOUT: Duration = Duration::from_millis(5);
const MAX_OPPORTUNISTIC_OUTPUT_DRAIN: usize = 4;
const MAX_FLUSH_DRAIN_RETRIES: usize = 400;
#[derive(Debug, Clone, Copy)]
struct OutputLayout {
stride: u32,
slice_height: u32,
crop: (u32, u32, u32, u32),
}
pub(crate) struct AmediaCodecVideoDecoder {
codec: MediaCodec,
info: StreamInfo,
time_base: Rational,
output_layout: Option<OutputLayout>,
pending: VecDeque<VideoFrame>,
flushed: bool,
}
impl AmediaCodecVideoDecoder {
pub(crate) fn open(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
validate(config)?;
if config.output != VideoOutputPreference::CpuFramesOk {
return Err(DecodeError::Unsupported);
}
let mime = mime_type(config.codec)?;
let codec = MediaCodec::from_decoder_type(mime).ok_or(DecodeError::Backend)?;
let mut format = MediaFormat::new();
format.set_str("mime", mime);
format.set_i32(
"width",
i32::try_from(config.width).map_err(|_| DecodeError::InvalidInput)?,
);
format.set_i32(
"height",
i32::try_from(config.height).map_err(|_| DecodeError::InvalidInput)?,
);
if !config.extra_data.is_empty() {
let (sps, pps) = split_csd(config.extra_data.as_ref());
if let Some(sps) = sps.as_deref() {
format.set_buffer("csd-0", sps);
}
if let Some(pps) = pps.as_deref() {
format.set_buffer("csd-1", pps);
}
}
codec
.configure(&format, None, MediaCodecDirection::Decoder)
.map_err(|_| DecodeError::Backend)?;
codec.start().map_err(|_| DecodeError::Backend)?;
Ok(Self {
codec,
info: stream_info_from(config),
time_base: config.time_base,
output_layout: None,
pending: VecDeque::new(),
flushed: false,
})
}
fn upload_and_queue(&self, data: &[u8], time_us: u64, flags: u32) -> Result<(), DecodeError> {
for _ in 0..MAX_INPUT_RETRIES {
match self
.codec
.dequeue_input_buffer(INPUT_DEQUEUE_TIMEOUT)
.map_err(|_| DecodeError::Backend)?
{
DequeuedInputBufferResult::Buffer(mut input_buffer) => {
let dst = input_buffer.buffer_mut();
let n = data.len().min(dst.len());
for (d, &s) in dst[..n].iter_mut().zip(data[..n].iter()) {
d.write(s);
}
self.codec
.queue_input_buffer(input_buffer, 0, n, time_us, flags)
.map_err(|_| DecodeError::Backend)?;
return Ok(());
}
DequeuedInputBufferResult::TryAgainLater => {}
}
}
Err(DecodeError::Backend)
}
fn drain_output(&mut self, max_attempts: usize) -> Result<(), DecodeError> {
for _ in 0..max_attempts {
match self
.codec
.dequeue_output_buffer(OUTPUT_DEQUEUE_TIMEOUT)
.map_err(|_| DecodeError::Backend)?
{
DequeuedOutputBufferInfoResult::Buffer(output_buffer) => {
let info = *output_buffer.info();
let is_eos = info.flags() & BUFFER_FLAG_END_OF_STREAM != 0;
let full = output_buffer.buffer();
let frame = self
.output_layout
.map(|layout| build_frame(full, layout, &info, self.time_base));
self.codec
.release_output_buffer(output_buffer, false)
.map_err(|_| DecodeError::Backend)?;
if let Some(frame) = frame {
self.pending.push_back(frame);
}
if is_eos {
return Ok(());
}
}
DequeuedOutputBufferInfoResult::OutputFormatChanged => {
self.adopt_output_format()?;
}
DequeuedOutputBufferInfoResult::TryAgainLater => return Ok(()),
DequeuedOutputBufferInfoResult::OutputBuffersChanged => {}
}
}
Ok(())
}
fn adopt_output_format(&mut self) -> Result<(), DecodeError> {
let format = self.codec.output_format();
let color_format = format.i32("color-format").ok_or(DecodeError::Backend)?;
if color_format != COLOR_FORMAT_YUV420_SEMI_PLANAR {
return Err(DecodeError::Unsupported);
}
let coded_width = format.i32("width").ok_or(DecodeError::Backend)?;
let coded_height = format.i32("height").ok_or(DecodeError::Backend)?;
let stride = format
.i32("stride")
.and_then(|v| u32::try_from(v).ok())
.ok_or(DecodeError::Backend)?;
let slice_height = format
.i32("slice-height")
.filter(|&v| v > 0)
.and_then(|v| u32::try_from(v).ok())
.or_else(|| u32::try_from(coded_height).ok())
.ok_or(DecodeError::Backend)?;
let crop = (
crop_component(&format, "crop-left", 0),
crop_component(&format, "crop-top", 0),
crop_component(&format, "crop-right", coded_width),
crop_component(&format, "crop-bottom", coded_height),
);
self.output_layout = Some(OutputLayout {
stride,
slice_height,
crop,
});
if let StreamInfo::Video { geometry, .. } = &mut self.info {
*geometry = VideoGeometry {
width: crop.2.saturating_sub(crop.0),
height: crop.3.saturating_sub(crop.1),
};
}
Ok(())
}
}
fn crop_component(format: &MediaFormat, key: &str, fallback: i32) -> u32 {
let raw = format.i32(key).unwrap_or(fallback);
u32::try_from(raw).unwrap_or(0)
}
fn build_frame(
payload: &[u8],
layout: OutputLayout,
info: &BufferInfo,
time_base: Rational,
) -> VideoFrame {
let start = usize::try_from(info.offset()).unwrap_or(0);
let len = usize::try_from(info.size()).unwrap_or(0);
let end = start.saturating_add(len).min(payload.len());
let region = payload.get(start..end).unwrap_or(&[]);
let crop = CropRect {
left: layout.crop.0,
top: layout.crop.1,
right: layout.crop.2,
bottom: layout.crop.3,
};
let data = strip_and_crop_nv12(region, layout.stride, layout.slice_height, crop);
VideoFrame {
pts: pts_from_time_us(info.presentation_time_us(), time_base),
duration: 0,
width: crop.width(),
height: crop.height(),
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu { data },
}
}
fn time_us_from(pts: i64, time_base: Rational) -> u64 {
if time_base.den == 0 {
return 0;
}
let value = i128::from(pts) * i128::from(time_base.num) * 1_000_000 / i128::from(time_base.den);
u64::try_from(value).unwrap_or(0)
}
fn pts_from_time_us(time_us: i64, time_base: Rational) -> i64 {
if time_base.num == 0 {
return 0;
}
let denom = i128::from(time_base.num) * 1_000_000;
if denom == 0 {
return 0;
}
let value = i128::from(time_us) * i128::from(time_base.den) / denom;
i64::try_from(value).unwrap_or(0)
}
impl VideoDecoder for AmediaCodecVideoDecoder {
fn stream_info(&self) -> &StreamInfo {
&self.info
}
fn push_packet(&mut self, packet: &Packet) -> Result<(), DecodeError> {
if self.flushed {
return Err(DecodeError::Closed);
}
if packet.is_discard {
return Ok(());
}
let time_us = time_us_from(packet.pts, self.time_base);
self.upload_and_queue(packet.payload.as_ref(), time_us, 0)?;
self.drain_output(MAX_OPPORTUNISTIC_OUTPUT_DRAIN)
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
if self.pending.is_empty() {
self.drain_output(1)?;
}
Ok(self.pending.pop_front())
}
fn flush(&mut self) -> Result<(), DecodeError> {
if self.flushed {
return Ok(());
}
self.upload_and_queue(&[], 0, BUFFER_FLAG_END_OF_STREAM)?;
self.drain_output(MAX_FLUSH_DRAIN_RETRIES)?;
self.flushed = true;
Ok(())
}
}
fn validate(config: &VideoDecoderConfig) -> Result<(), DecodeError> {
if !is_supported_video_codec(config.codec) {
return Err(DecodeError::Unsupported);
}
if config.pixel_format != PixelFormat::Nv12 {
return Err(DecodeError::Unsupported);
}
if config.time_base.den == 0 {
return Err(DecodeError::InvalidInput);
}
Ok(())
}
#[allow(clippy::missing_const_for_fn, reason = "StreamInfo holds Bytes")]
fn stream_info_from(config: &VideoDecoderConfig) -> StreamInfo {
StreamInfo::Video {
id: 0,
codec: CodecKind::H264,
time_base: config.time_base,
geometry: VideoGeometry {
width: config.width,
height: config.height,
},
extra_data: config.extra_data.clone(), }
}