#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_lossless)]
#![allow(dead_code)]
use crate::error::{CodecError, CodecResult};
use crate::frame::{Plane, VideoFrame};
use crate::traits::{BitrateMode, DecoderConfig, EncoderConfig, VideoDecoder, VideoEncoder};
use crate::av1::{Av1Decoder, Av1Encoder};
use oximedia_core::{CodecId, PixelFormat};
#[derive(Clone, Debug)]
pub struct AvifConfig {
pub crf: f32,
pub bit_depth: u8,
pub threads: usize,
pub icc_profile: Option<Vec<u8>>,
pub speed: u8,
}
impl Default for AvifConfig {
fn default() -> Self {
Self {
crf: 28.0,
bit_depth: 8,
threads: 0,
icc_profile: None,
speed: 6,
}
}
}
#[inline]
fn write_u32_be(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_be_bytes());
}
#[inline]
fn write_u16_be(buf: &mut Vec<u8>, v: u16) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn make_box(fourcc: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = (8 + payload.len()) as u32;
let mut b = Vec::with_capacity(size as usize);
write_u32_be(&mut b, size);
b.extend_from_slice(fourcc);
b.extend_from_slice(payload);
b
}
fn make_full_box(fourcc: &[u8; 4], version: u8, flags: u32, payload: &[u8]) -> Vec<u8> {
let mut pfx = Vec::with_capacity(4 + payload.len());
pfx.push(version);
pfx.extend_from_slice(&(flags & 0x00FF_FFFF).to_be_bytes()[1..]);
pfx.extend_from_slice(payload);
make_box(fourcc, &pfx)
}
fn build_ftyp() -> Vec<u8> {
let mut payload = Vec::with_capacity(16);
payload.extend_from_slice(b"avif"); write_u32_be(&mut payload, 0); payload.extend_from_slice(b"avif"); payload.extend_from_slice(b"mif1"); make_box(b"ftyp", &payload)
}
fn build_hdlr() -> Vec<u8> {
let mut payload = Vec::with_capacity(32);
write_u32_be(&mut payload, 0); payload.extend_from_slice(b"pict"); write_u32_be(&mut payload, 0); write_u32_be(&mut payload, 0);
write_u32_be(&mut payload, 0);
payload.push(0); make_full_box(b"hdlr", 0, 0, &payload)
}
fn build_ispe(width: u32, height: u32) -> Vec<u8> {
let mut payload = Vec::with_capacity(8);
write_u32_be(&mut payload, width);
write_u32_be(&mut payload, height);
make_full_box(b"ispe", 0, 0, &payload)
}
fn build_av1c() -> Vec<u8> {
let payload: [u8; 4] = [0x81, 0x04, 0x0C, 0x00];
make_box(b"av1C", &payload)
}
fn build_ipco(width: u32, height: u32) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&build_ispe(width, height));
payload.extend_from_slice(&build_av1c());
make_box(b"ipco", &payload)
}
fn build_ipma(item_id: u16) -> Vec<u8> {
let mut payload = Vec::with_capacity(16);
write_u32_be(&mut payload, 1); write_u16_be(&mut payload, item_id);
payload.push(2); payload.push(0x01); payload.push(0x82); make_full_box(b"ipma", 0, 0, &payload)
}
fn build_iprp(width: u32, height: u32, item_id: u16) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&build_ipco(width, height));
payload.extend_from_slice(&build_ipma(item_id));
make_box(b"iprp", &payload)
}
fn build_infe(item_id: u16) -> Vec<u8> {
let mut payload = Vec::new();
write_u16_be(&mut payload, item_id);
write_u16_be(&mut payload, 0); payload.extend_from_slice(b"av01"); payload.push(0); make_full_box(b"infe", 2, 0, &payload)
}
fn build_iinf(item_id: u16) -> Vec<u8> {
let mut payload = Vec::new();
write_u16_be(&mut payload, 1); payload.extend_from_slice(&build_infe(item_id));
make_full_box(b"iinf", 0, 0, &payload)
}
fn build_pitm(item_id: u16) -> Vec<u8> {
let mut payload = Vec::new();
write_u16_be(&mut payload, item_id);
make_full_box(b"pitm", 0, 0, &payload)
}
fn build_iloc(item_id: u16, mdat_data_offset: u32, av1_size: u32) -> Vec<u8> {
let mut payload = Vec::with_capacity(32);
payload.push((4 << 4) | 4u8); payload.push(0u8); write_u16_be(&mut payload, 1); write_u16_be(&mut payload, item_id);
write_u16_be(&mut payload, 0); write_u16_be(&mut payload, 1); write_u32_be(&mut payload, mdat_data_offset);
write_u32_be(&mut payload, av1_size);
make_full_box(b"iloc", 0, 0, &payload)
}
fn build_meta(
width: u32,
height: u32,
item_id: u16,
mdat_data_offset: u32,
av1_size: u32,
) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&build_hdlr());
payload.extend_from_slice(&build_pitm(item_id));
payload.extend_from_slice(&build_iloc(item_id, mdat_data_offset, av1_size));
payload.extend_from_slice(&build_iinf(item_id));
payload.extend_from_slice(&build_iprp(width, height, item_id));
make_full_box(b"meta", 0, 0, &payload)
}
#[derive(Debug)]
pub struct AvifEncoder {
config: AvifConfig,
}
impl AvifEncoder {
pub fn new(config: AvifConfig) -> CodecResult<Self> {
if config.bit_depth != 8 && config.bit_depth != 10 && config.bit_depth != 12 {
return Err(CodecError::InvalidParameter(
"bit_depth must be 8, 10, or 12".to_string(),
));
}
Ok(Self { config })
}
pub fn encode(&mut self, frame: &VideoFrame) -> CodecResult<Vec<u8>> {
if frame.width == 0 || frame.height == 0 {
return Err(CodecError::InvalidParameter(
"Frame dimensions must be non-zero".to_string(),
));
}
let av1_data = self.encode_av1_payload(frame)?;
self.assemble_avif(frame.width, frame.height, &av1_data)
}
fn encode_av1_payload(&mut self, frame: &VideoFrame) -> CodecResult<Vec<u8>> {
let enc_config = EncoderConfig {
codec: CodecId::Av1,
width: frame.width,
height: frame.height,
bitrate: BitrateMode::Crf(self.config.crf),
keyint: 1,
threads: self.config.threads,
..EncoderConfig::default()
};
let mut av1_enc = Av1Encoder::new(enc_config)?;
av1_enc.send_frame(frame)?;
av1_enc.flush()?;
let mut av1_bytes = Vec::new();
while let Some(pkt) = av1_enc.receive_packet()? {
av1_bytes.extend_from_slice(&pkt.data);
}
if av1_bytes.is_empty() {
return Err(CodecError::EncodingFailed(
"AV1 encoder produced no output".to_string(),
));
}
Ok(av1_bytes)
}
fn assemble_avif(
&self,
width: u32,
height: u32,
av1_data: &[u8],
) -> CodecResult<Vec<u8>> {
const ITEM_ID: u16 = 1;
let ftyp = build_ftyp();
let av1_size = av1_data.len() as u32;
let meta_placeholder = build_meta(width, height, ITEM_ID, 0, av1_size);
let mdat_data_offset = (ftyp.len() + meta_placeholder.len() + 8) as u32;
let meta = build_meta(width, height, ITEM_ID, mdat_data_offset, av1_size);
let mdat = make_box(b"mdat", av1_data);
let mut file = Vec::with_capacity(ftyp.len() + meta.len() + mdat.len());
file.extend_from_slice(&ftyp);
file.extend_from_slice(&meta);
file.extend_from_slice(&mdat);
Ok(file)
}
}
#[derive(Debug, Default)]
pub struct AvifDecoder {
pub last_width: u32,
pub last_height: u32,
}
impl AvifDecoder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn decode(&mut self, data: &[u8]) -> CodecResult<VideoFrame> {
let av1_payload = self.extract_av1_payload(data)?;
let dec_config = DecoderConfig {
codec: CodecId::Av1,
..DecoderConfig::default()
};
let mut av1_dec = Av1Decoder::new(dec_config)?;
av1_dec.send_packet(&av1_payload, 0)?;
av1_dec.flush()?;
let frame = av1_dec
.receive_frame()?
.ok_or_else(|| CodecError::DecodingFailed("No frame produced".to_string()))?;
self.last_width = frame.width;
self.last_height = frame.height;
Ok(frame)
}
fn extract_av1_payload<'a>(&self, data: &'a [u8]) -> CodecResult<Vec<u8>> {
let mut pos = 0usize;
while pos + 8 <= data.len() {
let box_size = u32::from_be_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
]) as usize;
if box_size < 8 {
return Err(CodecError::InvalidBitstream(
"AVIF: box size < 8".to_string(),
));
}
if pos + box_size > data.len() {
return Err(CodecError::InvalidBitstream(
"AVIF: box extends beyond file".to_string(),
));
}
let fourcc = &data[pos + 4..pos + 8];
if fourcc == b"mdat" {
let payload = data[pos + 8..pos + box_size].to_vec();
return Ok(payload);
}
pos += box_size;
}
Err(CodecError::InvalidBitstream(
"AVIF: no mdat box found".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::Plane;
fn make_test_frame(width: u32, height: u32) -> VideoFrame {
let y_size = (width * height) as usize;
let uv_size = ((width / 2) * (height / 2)) as usize;
let y_plane = Plane::with_dimensions(vec![128u8; y_size], width as usize, width, height);
let u_plane = Plane::with_dimensions(
vec![128u8; uv_size],
(width / 2) as usize,
width / 2,
height / 2,
);
let v_plane = Plane::with_dimensions(
vec![128u8; uv_size],
(width / 2) as usize,
width / 2,
height / 2,
);
let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
frame.planes = vec![y_plane, u_plane, v_plane];
frame
}
#[test]
fn test_avif_encoder_creation() {
let config = AvifConfig::default();
let encoder = AvifEncoder::new(config);
assert!(encoder.is_ok());
}
#[test]
fn test_avif_encoder_rejects_invalid_bit_depth() {
let config = AvifConfig {
bit_depth: 7,
..Default::default()
};
assert!(AvifEncoder::new(config).is_err());
}
#[test]
fn test_avif_encoder_rejects_zero_dimensions() {
let mut encoder = AvifEncoder::new(AvifConfig::default()).expect("ok");
let frame = VideoFrame::new(PixelFormat::Yuv420p, 0, 0);
assert!(encoder.encode(&frame).is_err());
}
#[test]
fn test_avif_encode_produces_valid_container() {
let mut encoder = AvifEncoder::new(AvifConfig::default()).expect("ok");
let frame = make_test_frame(32, 32);
let result = encoder.encode(&frame);
assert!(result.is_ok(), "encode failed: {:?}", result);
let data = result.expect("ok");
assert!(data.len() > 12, "output too short");
assert_eq!(&data[4..8], b"ftyp");
assert_eq!(&data[8..12], b"avif");
}
#[test]
fn test_avif_encode_contains_mdat() {
let mut encoder = AvifEncoder::new(AvifConfig::default()).expect("ok");
let frame = make_test_frame(32, 32);
let data = encoder.encode(&frame).expect("encode failed");
let mut found_mdat = false;
let mut pos = 0usize;
while pos + 8 <= data.len() {
let sz = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
as usize;
if sz < 8 {
break;
}
if &data[pos + 4..pos + 8] == b"mdat" {
found_mdat = true;
break;
}
pos += sz;
}
assert!(found_mdat, "no mdat box in AVIF output");
}
#[test]
fn test_avif_decoder_rejects_empty() {
let mut decoder = AvifDecoder::new();
assert!(decoder.decode(&[]).is_err());
}
#[test]
fn test_avif_decoder_rejects_garbage() {
let mut decoder = AvifDecoder::new();
let garbage = vec![0xFFu8; 64];
let _ = decoder.decode(&garbage);
}
#[test]
fn test_avif_roundtrip() {
let mut encoder = AvifEncoder::new(AvifConfig::default()).expect("ok");
let frame_in = make_test_frame(32, 32);
let avif_data = encoder.encode(&frame_in).expect("encode failed");
let mut decoder = AvifDecoder::new();
let frame_out = decoder.decode(&avif_data);
match frame_out {
Ok(f) => {
assert_eq!(f.width, 32);
assert_eq!(f.height, 32);
}
Err(_) => {
}
}
}
#[test]
fn test_avif_config_defaults() {
let cfg = AvifConfig::default();
assert_eq!(cfg.bit_depth, 8);
assert!((cfg.crf - 28.0).abs() < f32::EPSILON);
assert_eq!(cfg.speed, 6);
}
}