#![forbid(unsafe_code)]
use mediaway_common::{
Bytes, CodecKind, Packet, PixelFormat, Rational, StreamInfo, VideoFrame, VideoFrameStorage,
VideoGeometry,
};
use rav1e::prelude::{ChromaSampling, Config, Context, EncoderConfig, EncoderStatus, FrameType};
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Av1Error {
#[error("unsupported AV1 encode input (pixel format or storage kind)")]
Unsupported,
#[error("invalid AV1 encoder configuration: {0}")]
InvalidConfig(String),
#[error("invalid AV1 encode input (dimensions or buffer size mismatch)")]
InvalidInput,
#[error("rav1e backend failure")]
Backend,
#[error("AV1 encoder session closed")]
Closed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Av1EncoderConfig {
pub width: u32,
pub height: u32,
pub time_base: Rational,
pub bitrate_bps: u32,
pub speed: u8,
pub low_latency: bool,
}
impl Av1EncoderConfig {
#[must_use]
pub const fn new(width: u32, height: u32, time_base: Rational) -> Self {
Self {
width,
height,
time_base,
bitrate_bps: 0,
speed: 6,
low_latency: false,
}
}
}
pub struct Av1Encoder {
ctx: Context<u8>,
stream_info: StreamInfo,
width: usize,
height: usize,
chroma_width: usize,
chroma_height: usize,
}
impl Av1Encoder {
pub fn open(config: &Av1EncoderConfig) -> Result<Self, Av1Error> {
let bitrate = i32::try_from(config.bitrate_bps).map_err(|_| Av1Error::InvalidInput)?;
let mut enc = EncoderConfig::with_speed_preset(config.speed);
enc.width = config.width as usize;
enc.height = config.height as usize;
enc.time_base =
rav1e::prelude::Rational::new(config.time_base.num, u64::from(config.time_base.den));
enc.chroma_sampling = ChromaSampling::Cs420;
enc.low_latency = config.low_latency;
if bitrate > 0 {
enc.bitrate = bitrate;
enc.quantizer = 255;
}
let ctx: Context<u8> = Config::new()
.with_encoder_config(enc)
.new_context()
.map_err(|e| Av1Error::InvalidConfig(e.to_string()))?;
let extra_data = Bytes::from(ctx.container_sequence_header());
let (chroma_width, chroma_height) = ChromaSampling::Cs420
.get_chroma_dimensions(config.width as usize, config.height as usize);
Ok(Self {
ctx,
stream_info: StreamInfo::Video {
id: 0,
codec: CodecKind::Av1,
time_base: config.time_base,
geometry: VideoGeometry {
width: config.width,
height: config.height,
},
extra_data,
},
width: config.width as usize,
height: config.height as usize,
chroma_width,
chroma_height,
})
}
#[must_use]
pub const fn stream_info(&self) -> &StreamInfo {
&self.stream_info
}
pub fn push_frame(&mut self, frame: &VideoFrame) -> Result<(), Av1Error> {
if frame.format != PixelFormat::I420 {
return Err(Av1Error::Unsupported);
}
if frame.width as usize != self.width || frame.height as usize != self.height {
return Err(Av1Error::InvalidInput);
}
let VideoFrameStorage::Cpu { data } = &frame.storage else {
return Err(Av1Error::Unsupported);
};
let y_len = self.width * self.height;
let chroma_len = self.chroma_width * self.chroma_height;
let expected_len = y_len + 2 * chroma_len;
if data.len() != expected_len {
return Err(Av1Error::InvalidInput);
}
let mut rav1e_frame = self.ctx.new_frame();
rav1e_frame.planes[0].copy_from_raw_u8(&data[..y_len], self.width, 1);
rav1e_frame.planes[1].copy_from_raw_u8(
&data[y_len..y_len + chroma_len],
self.chroma_width,
1,
);
rav1e_frame.planes[2].copy_from_raw_u8(
&data[y_len + chroma_len..expected_len],
self.chroma_width,
1,
);
match self.ctx.send_frame(rav1e_frame) {
Ok(()) => Ok(()),
Err(EncoderStatus::EnoughData) => Err(Av1Error::Closed),
Err(
EncoderStatus::Failure
| EncoderStatus::NeedMoreData
| EncoderStatus::LimitReached
| EncoderStatus::Encoded
| EncoderStatus::NotReady,
) => Err(Av1Error::Backend),
}
}
pub fn poll_packet(&mut self) -> Result<Option<Packet>, Av1Error> {
let stream_id = self.stream_info.id();
loop {
match self.ctx.receive_packet() {
Ok(packet) => return Ok(Some(convert_packet(packet, stream_id))),
Err(EncoderStatus::Encoded) => {}
Err(
EncoderStatus::NeedMoreData
| EncoderStatus::LimitReached
| EncoderStatus::NotReady
| EncoderStatus::EnoughData,
) => return Ok(None),
Err(EncoderStatus::Failure) => return Err(Av1Error::Backend),
}
}
}
pub fn flush(&mut self) -> Result<(), Av1Error> {
self.ctx.flush();
Ok(())
}
}
fn pts_from_input_frameno(input_frameno: u64) -> i64 {
i64::try_from(input_frameno).unwrap_or(i64::MAX)
}
fn convert_packet(packet: rav1e::Packet<u8>, stream_id: u32) -> Packet {
Packet {
stream_id,
pts: pts_from_input_frameno(packet.input_frameno),
dts: pts_from_input_frameno(packet.input_frameno),
duration: 1,
is_keyframe: packet.frame_type == FrameType::KEY,
is_discard: false,
payload: Bytes::from(packet.data),
}
}
#[cfg(test)]
#[path = "av1_tests.rs"]
mod tests;