use std::collections::VecDeque;
use std::mem::MaybeUninit;
use std::time::Duration;
use bytes::{BufMut, Bytes, BytesMut};
use moq_net::Timestamp;
use ndk::media::media_codec::{
self, BufferInfo, DequeuedInputBufferResult, DequeuedOutputBufferInfoResult, MediaCodecDirection, OutputBuffer,
};
use ndk::media::media_format::MediaFormat;
use ndk::media_error::MediaError;
use super::super::encoder::{Codec, Config};
use super::{Backend, Encoded};
use crate::{Color, Error, Frame, I420};
pub(crate) const NAME: &str = "mediacodec";
const MIME_H264: &str = "video/avc";
const MIME_H265: &str = "video/hevc";
const KEY_MIME: &str = "mime";
const KEY_WIDTH: &str = "width";
const KEY_HEIGHT: &str = "height";
const KEY_BIT_RATE: &str = "bitrate";
const KEY_BITRATE_MODE: &str = "bitrate-mode";
const KEY_FRAME_RATE: &str = "frame-rate";
const KEY_COLOR_FORMAT: &str = "color-format";
const KEY_COLOR_STANDARD: &str = "color-standard";
const KEY_COLOR_TRANSFER: &str = "color-transfer";
const KEY_COLOR_RANGE: &str = "color-range";
const KEY_I_FRAME_INTERVAL: &str = "i-frame-interval";
const KEY_LATENCY: &str = "latency";
const KEY_LOW_LATENCY: &str = "low-latency";
const KEY_MAX_B_FRAMES: &str = "max-bframes";
const KEY_PRIORITY: &str = "priority";
const KEY_REQUEST_SYNC_FRAME: &str = "request-sync";
const KEY_VIDEO_BITRATE: &str = "video-bitrate";
const COLOR_FORMAT_NV12: i32 = 21;
const BITRATE_MODE_CBR: i32 = 2;
const PRIORITY_REALTIME: i32 = 0;
const COLOR_STANDARD_BT709: i32 = 1;
const COLOR_STANDARD_BT601_NTSC: i32 = 4;
const COLOR_TRANSFER_SDR_VIDEO: i32 = 3;
const COLOR_RANGE_FULL: i32 = 1;
const COLOR_RANGE_LIMITED: i32 = 2;
const FLAG_KEY_FRAME: u32 = 1;
const FLAG_CODEC_CONFIG: u32 = 2;
const FLAG_END_OF_STREAM: u32 = 4;
const INPUT_TIMEOUT: Duration = Duration::from_millis(10);
const OUTPUT_TIMEOUT: Duration = Duration::ZERO;
const DRAIN_TIMEOUT: Duration = Duration::from_millis(50);
const DRAIN_ROUNDS: u32 = 20;
pub(crate) struct MediaCodec {
codec: media_codec::MediaCodec,
kind: Codec,
width: usize,
height: usize,
framerate: u32,
parameter_sets: Option<Bytes>,
pending: VecDeque<(i64, Timestamp)>,
last_timestamp: Option<Timestamp>,
frame_index: i64,
keyframe_pending: bool,
ended: bool,
}
unsafe impl Send for MediaCodec {}
impl MediaCodec {
pub(crate) fn open(config: &Config) -> Result<Box<dyn Backend>, Error> {
let mime = match config.codec {
Codec::H264 => MIME_H264,
Codec::H265 => MIME_H265,
};
let codec = media_codec::MediaCodec::from_encoder_type(mime)
.ok_or_else(|| Error::Codec(anyhow::anyhow!("no MediaCodec encoder for {mime}")))?;
let format = encoder_format(config, mime);
codec
.configure(&format, None, MediaCodecDirection::Encoder)
.map_err(|e| codec_err("configure", e))?;
codec.start().map_err(|e| codec_err("start", e))?;
tracing::info!(
encoder = NAME,
codec = ?config.codec,
width = config.width,
height = config.height,
"opened video encoder"
);
Ok(Box::new(Self {
codec,
kind: config.codec,
width: config.width as usize,
height: config.height as usize,
framerate: config.framerate.max(1),
parameter_sets: None,
pending: VecDeque::new(),
last_timestamp: None,
frame_index: 0,
keyframe_pending: false,
ended: false,
}))
}
fn sample_time(&self) -> i64 {
self.frame_index * 1_000_000 / self.framerate as i64
}
fn request_keyframe(&self) -> Result<(), Error> {
let mut params = MediaFormat::new();
params.set_i32(KEY_REQUEST_SYNC_FRAME, 0);
self.codec
.set_parameters(params)
.map_err(|e| codec_err("request a sync frame", e))
}
fn submit(&mut self, frame: &Frame, keyframe: bool) -> Result<(), Error> {
if keyframe {
self.keyframe_pending = true;
self.request_keyframe()?;
}
let i420 = frame.surface.to_i420()?;
let size = I420::len(self.width as u32, self.height as u32);
let sample_time = self.sample_time();
let submitted = match self
.codec
.dequeue_input_buffer(INPUT_TIMEOUT)
.map_err(|e| codec_err("dequeue an input buffer", e))?
{
DequeuedInputBufferResult::Buffer(mut buffer) => {
fill_nv12(buffer.buffer_mut(), &i420, self.width, self.height)?;
self.codec
.queue_input_buffer(buffer, 0, size, sample_time as u64, 0)
.map_err(|e| codec_err("queue an input buffer", e))?;
true
}
DequeuedInputBufferResult::TryAgainLater => false,
};
if !submitted {
tracing::debug!(encoder = NAME, "no input buffer available, dropping a frame");
return Ok(());
}
self.pending.push_back((sample_time, frame.timestamp));
self.frame_index += 1;
self.keyframe_pending = false;
Ok(())
}
fn drain(&mut self, timeout: Duration, out: &mut Vec<Encoded>) -> Result<bool, Error> {
loop {
let taken = match self
.codec
.dequeue_output_buffer(timeout)
.map_err(|e| codec_err("dequeue an output buffer", e))?
{
DequeuedOutputBufferInfoResult::Buffer(buffer) => {
let info = *buffer.info();
let unit = access_unit(&buffer, &info);
self.codec
.release_output_buffer(buffer, false)
.map_err(|e| codec_err("release an output buffer", e))?;
Some((info, unit))
}
DequeuedOutputBufferInfoResult::TryAgainLater => return Ok(false),
DequeuedOutputBufferInfoResult::OutputFormatChanged
| DequeuedOutputBufferInfoResult::OutputBuffersChanged => None,
};
let Some((info, unit)) = taken else { continue };
let flags = info.flags();
if flags & FLAG_CODEC_CONFIG != 0 {
self.parameter_sets = Some(unit);
} else if !unit.is_empty() {
let timestamp =
take_timestamp(&mut self.pending, &mut self.last_timestamp, info.presentation_time_us());
let payload = if flags & FLAG_KEY_FRAME != 0 {
with_parameter_sets(self.parameter_sets.as_ref(), self.kind, unit)
} else {
unit
};
out.push(Encoded::new(payload, timestamp));
}
if flags & FLAG_END_OF_STREAM != 0 {
return Ok(true);
}
}
}
fn drain_tail(&mut self) -> Result<Vec<Encoded>, Error> {
let mut out = Vec::new();
if self.ended || self.pending.is_empty() {
return Ok(out);
}
self.signal_end_of_input(&mut out)?;
self.ended = true;
for _ in 0..DRAIN_ROUNDS {
if self.drain(DRAIN_TIMEOUT, &mut out)? {
self.pending.clear();
return Ok(out);
}
}
Err(Error::Codec(anyhow::anyhow!(
"MediaCodec did not reach end of stream within {:?}",
DRAIN_TIMEOUT * DRAIN_ROUNDS
)))
}
fn signal_end_of_input(&mut self, out: &mut Vec<Encoded>) -> Result<(), Error> {
let sample_time = self.sample_time();
for _ in 0..DRAIN_ROUNDS {
let queued = match self
.codec
.dequeue_input_buffer(INPUT_TIMEOUT)
.map_err(|e| codec_err("dequeue an input buffer", e))?
{
DequeuedInputBufferResult::Buffer(buffer) => {
self.codec
.queue_input_buffer(buffer, 0, 0, sample_time as u64, FLAG_END_OF_STREAM)
.map_err(|e| codec_err("queue end of stream", e))?;
true
}
DequeuedInputBufferResult::TryAgainLater => false,
};
if queued {
return Ok(());
}
self.drain(OUTPUT_TIMEOUT, out)?;
}
Err(Error::Codec(anyhow::anyhow!(
"MediaCodec never freed an input buffer for the end of stream"
)))
}
}
impl Backend for MediaCodec {
fn encode(&mut self, frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error> {
let mut out = Vec::new();
self.drain(OUTPUT_TIMEOUT, &mut out)?;
self.submit(frame, keyframe || self.keyframe_pending)?;
self.drain(OUTPUT_TIMEOUT, &mut out)?;
Ok(out)
}
fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
let out = self.drain_tail()?;
if self.ended {
self.codec.flush().map_err(|e| codec_err("flush", e))?;
self.ended = false;
self.keyframe_pending = true;
}
Ok(out)
}
fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
self.drain_tail()
}
fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
let mut params = MediaFormat::new();
params.set_i32(KEY_VIDEO_BITRATE, clamp_i32(bitrate));
self.codec
.set_parameters(params)
.map_err(|e| codec_err("set the bitrate", e))
}
fn name(&self) -> &str {
NAME
}
}
fn encoder_format(config: &Config, mime: &str) -> MediaFormat {
let mut format = MediaFormat::new();
format.set_str(KEY_MIME, mime);
format.set_i32(KEY_WIDTH, config.width as i32);
format.set_i32(KEY_HEIGHT, config.height as i32);
format.set_i32(KEY_COLOR_FORMAT, COLOR_FORMAT_NV12);
format.set_i32(KEY_BIT_RATE, clamp_i32(config.resolved_bitrate()));
format.set_i32(KEY_BITRATE_MODE, BITRATE_MODE_CBR);
format.set_i32(KEY_FRAME_RATE, config.framerate as i32);
format.set_i32(KEY_PRIORITY, PRIORITY_REALTIME);
format.set_f32(KEY_I_FRAME_INTERVAL, config.gop as f32 / config.framerate.max(1) as f32);
format.set_i32(KEY_LATENCY, 1);
format.set_i32(KEY_LOW_LATENCY, 1);
format.set_i32(KEY_MAX_B_FRAMES, 0);
let color = config.resolved_color();
let standard = match color {
Color::Bt601Limited | Color::Bt601Full => COLOR_STANDARD_BT601_NTSC,
Color::Bt709Limited | Color::Bt709Full => COLOR_STANDARD_BT709,
};
format.set_i32(KEY_COLOR_STANDARD, standard);
format.set_i32(KEY_COLOR_TRANSFER, COLOR_TRANSFER_SDR_VIDEO);
format.set_i32(
KEY_COLOR_RANGE,
if color.limited() {
COLOR_RANGE_LIMITED
} else {
COLOR_RANGE_FULL
},
);
format
}
fn fill_nv12(buffer: &mut [MaybeUninit<u8>], i420: &I420, width: usize, height: usize) -> Result<(), Error> {
let luma_len = width * height;
let needed = luma_len + luma_len / 2;
if buffer.len() < needed {
return Err(Error::Codec(anyhow::anyhow!(
"MediaCodec input buffer is {} bytes, needs {needed} for {width}x{height} NV12",
buffer.len()
)));
}
let (luma, chroma) = buffer[..needed].split_at_mut(luma_len);
unsafe {
std::ptr::copy_nonoverlapping(i420.y().as_ptr(), luma.as_mut_ptr().cast::<u8>(), luma_len);
}
for ((pair, u), v) in chroma.chunks_exact_mut(2).zip(i420.u()).zip(i420.v()) {
pair[0].write(*u);
pair[1].write(*v);
}
Ok(())
}
fn access_unit(buffer: &OutputBuffer<'_>, info: &BufferInfo) -> Bytes {
if info.size() <= 0 {
return Bytes::new();
}
let bytes = buffer.buffer();
let start = (info.offset().max(0) as usize).min(bytes.len());
let end = start.saturating_add(info.size().max(0) as usize).min(bytes.len());
Bytes::copy_from_slice(&bytes[start..end])
}
fn take_timestamp(
pending: &mut VecDeque<(i64, Timestamp)>,
last: &mut Option<Timestamp>,
sample_time: i64,
) -> Timestamp {
let matched = match pending.iter().position(|(fed, _)| *fed == sample_time) {
Some(index) => pending.remove(index),
None => {
let oldest = pending.pop_front();
if oldest.is_some() {
tracing::debug!(sample_time, "encoder output did not match a fed sample time");
}
oldest
}
};
match matched {
Some((_, timestamp)) => {
*last = Some(timestamp);
timestamp
}
None => {
tracing::warn!("encoder produced output with no frame outstanding");
last.unwrap_or(Timestamp::ZERO)
}
}
}
fn with_parameter_sets(sets: Option<&Bytes>, codec: Codec, unit: Bytes) -> Bytes {
let Some(sets) = sets else { return unit };
if opens_with_parameter_set(&unit, codec) {
return unit;
}
let mut out = BytesMut::with_capacity(sets.len() + unit.len());
out.put_slice(sets);
out.put_slice(&unit);
out.freeze()
}
fn opens_with_parameter_set(unit: &[u8], codec: Codec) -> bool {
let header = match unit {
[0, 0, 0, 1, header, ..] | [0, 0, 1, header, ..] => *header,
_ => return false,
};
match codec {
Codec::H265 => matches!((header >> 1) & 0x3f, 32 | 33),
_ => header & 0x1f == 7,
}
}
fn codec_err(what: &str, error: MediaError) -> Error {
Error::Codec(anyhow::anyhow!("failed to {what} on the MediaCodec encoder: {error}"))
}
fn clamp_i32(value: u64) -> i32 {
value.min(i32::MAX as u64) as i32
}
#[cfg(test)]
mod tests {
use super::*;
const SPS: &[u8] = &[0, 0, 0, 1, 0x67, 0x42, 0xc0, 0x1e];
const IDR: &[u8] = &[0, 0, 0, 1, 0x65, 0x88, 0x84];
fn timestamp(micros: u64) -> Timestamp {
Timestamp::from_micros(micros).unwrap()
}
#[test]
fn a_keyframe_without_parameter_sets_gets_them() {
let sets = Bytes::from_static(SPS);
let unit = Bytes::from_static(IDR);
let out = with_parameter_sets(Some(&sets), Codec::H264, unit);
assert_eq!(&out[..SPS.len()], SPS);
assert_eq!(&out[SPS.len()..], IDR);
}
#[test]
fn a_keyframe_that_already_carries_them_is_left_alone() {
let sets = Bytes::from_static(SPS);
let mut unit = Vec::from(SPS);
unit.extend_from_slice(IDR);
let unit = Bytes::from(unit);
let out = with_parameter_sets(Some(&sets), Codec::H264, unit.clone());
assert_eq!(out, unit);
}
#[test]
fn h265_parameter_sets_are_recognized_by_their_own_header() {
let vps = [0, 0, 0, 1, 0x40, 0x01];
let idr = [0, 0, 0, 1, 0x26, 0x01];
assert!(opens_with_parameter_set(&vps, Codec::H265));
assert!(!opens_with_parameter_set(&idr, Codec::H265));
}
#[test]
fn output_is_paired_with_the_frame_it_was_encoded_from() {
let mut pending = VecDeque::from(vec![(0, timestamp(1_000)), (33_333, timestamp(34_333))]);
let mut last = None;
assert_eq!(take_timestamp(&mut pending, &mut last, 0), timestamp(1_000));
assert_eq!(take_timestamp(&mut pending, &mut last, 33_333), timestamp(34_333));
assert!(pending.is_empty());
}
#[test]
fn an_unmatched_sample_time_falls_back_to_the_oldest_frame() {
let mut pending = VecDeque::from(vec![(0, timestamp(1_000))]);
let mut last = None;
assert_eq!(take_timestamp(&mut pending, &mut last, 12_345), timestamp(1_000));
assert_eq!(take_timestamp(&mut pending, &mut last, 12_345), timestamp(1_000));
}
#[test]
#[ignore = "needs an Android device with a MediaCodec H.264 encoder"]
fn encodes_a_keyframe_with_parameter_sets_inline() {
let config = Config::new(320, 240, 30);
let mut backend = MediaCodec::open(&config).expect("a MediaCodec encoder");
let size = config.size();
let i420 = I420::new(size.width, size.height, vec![0x80; I420::len(size.width, size.height)]).unwrap();
let frame = Frame::new(crate::Surface::I420(i420), timestamp(0));
let mut encoded = backend.encode(&frame, true).unwrap();
if encoded.is_empty() {
encoded = backend.flush().unwrap();
}
let annexb: Vec<u8> = encoded.iter().flat_map(|frame| frame.payload.iter().copied()).collect();
moq_mux::codec::h264::config(&annexb).expect("a keyframe carrying its own parameter sets");
}
}