#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_lossless)]
#![allow(dead_code)]
use crate::error::{CodecError, CodecResult};
use crate::frame::{FrameType, VideoFrame};
use crate::rate_control::{CbrController, CrfController, FrameStats, RcConfig};
use crate::traits::{BitrateMode, EncodedPacket, EncoderConfig, VideoEncoder};
use oximedia_core::CodecId;
#[derive(Debug)]
struct BoolWriter {
data: Vec<u8>,
range: u32,
bottom: u64,
bit_count: i32,
}
impl BoolWriter {
fn new() -> Self {
Self {
data: Vec::with_capacity(4096),
range: 255,
bottom: 0,
bit_count: -24,
}
}
fn write_bool(&mut self, value: bool, prob: u8) {
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
if value {
self.bottom += u64::from(split);
self.range -= split;
} else {
self.range = split;
}
let mut shift = self.range.leading_zeros().saturating_sub(24);
self.range <<= shift;
self.bit_count += shift as i32;
self.bottom <<= shift;
while self.bit_count >= 0 {
let byte = (self.bottom >> 32) as u8;
self.data.push(byte);
self.bottom = (self.bottom & 0xFFFF_FFFF) << 8;
self.bit_count -= 8;
shift = shift.saturating_sub(8);
}
}
fn write_literal(&mut self, value: u32, n: u8) {
for i in (0..n).rev() {
self.write_bool((value >> i) & 1 != 0, 128);
}
}
fn finalise(mut self) -> Vec<u8> {
for _ in 0..32 {
self.write_bool(false, 128);
}
self.data
}
}
#[derive(Clone, Debug)]
pub struct Vp9EncoderConfig {
pub crf: f32,
pub keyint: u32,
pub speed: u8,
pub tile_cols_log2: u32,
pub error_resilient: bool,
}
impl Default for Vp9EncoderConfig {
fn default() -> Self {
Self {
crf: 28.0,
keyint: 250,
speed: 5,
tile_cols_log2: 0,
error_resilient: false,
}
}
}
impl Vp9EncoderConfig {
fn base_qindex(&self) -> u8 {
let normalised = (self.crf / 63.0).clamp(0.0, 1.0);
(normalised * 255.0) as u8
}
}
#[derive(Debug)]
enum Vp9RateController {
Crf(CrfController),
Cbr(CbrController),
}
impl Vp9RateController {
fn from_config(config: &EncoderConfig, vp9_cfg: &Vp9EncoderConfig) -> Self {
match config.bitrate {
BitrateMode::Cbr(bps) => {
let rc = RcConfig::cbr(bps);
Self::Cbr(CbrController::new(&rc))
}
BitrateMode::Vbr { target, .. } => {
let rc = RcConfig::cbr(target);
Self::Cbr(CbrController::new(&rc))
}
BitrateMode::Crf(c) => {
let rc = RcConfig::crf(c);
Self::Crf(CrfController::new(&rc))
}
BitrateMode::Lossless => {
let rc = RcConfig::crf(0.0);
Self::Crf(CrfController::new(&rc))
}
}
}
fn base_qindex(&mut self, vp9_cfg: &Vp9EncoderConfig, is_keyframe: bool) -> u8 {
match self {
Self::Crf(_) => vp9_cfg.base_qindex(),
Self::Cbr(cbr) => {
let frame_type = if is_keyframe {
FrameType::Key
} else {
FrameType::Inter
};
let out = cbr.get_rc(frame_type);
((u16::from(out.qp) * 255 + 31) / 63).min(255) as u8
}
}
}
fn update_frame(&mut self, encoded_bytes: usize, is_keyframe: bool) {
if let Self::Cbr(cbr) = self {
let stats = FrameStats {
bits: (encoded_bytes * 8) as u64,
frame_type: if is_keyframe {
FrameType::Key
} else {
FrameType::Inter
},
..Default::default()
};
cbr.update(&stats);
}
}
}
#[derive(Debug)]
pub struct Vp9Encoder {
config: EncoderConfig,
vp9_config: Vp9EncoderConfig,
frame_count: u64,
output_queue: Vec<EncodedPacket>,
rate_controller: Vp9RateController,
flushing: bool,
}
impl Vp9Encoder {
pub fn new(config: EncoderConfig) -> CodecResult<Self> {
if config.width == 0 || config.height == 0 {
return Err(CodecError::InvalidParameter(
"Invalid frame dimensions".to_string(),
));
}
if config.codec != CodecId::Vp9 {
return Err(CodecError::InvalidParameter(
"Expected VP9 codec".to_string(),
));
}
let crf_val = match config.bitrate {
BitrateMode::Crf(c) => c,
BitrateMode::Lossless => 0.0,
_ => 28.0,
};
let vp9_config = Vp9EncoderConfig {
crf: crf_val,
keyint: config.keyint,
speed: config.preset.speed().min(10),
..Vp9EncoderConfig::default()
};
let rate_controller = Vp9RateController::from_config(&config, &vp9_config);
Ok(Self {
config,
vp9_config,
frame_count: 0,
output_queue: Vec::new(),
rate_controller,
flushing: false,
})
}
pub fn with_vp9_config(
config: EncoderConfig,
vp9_config: Vp9EncoderConfig,
) -> CodecResult<Self> {
if config.width == 0 || config.height == 0 {
return Err(CodecError::InvalidParameter(
"Invalid frame dimensions".to_string(),
));
}
let rate_controller = Vp9RateController::from_config(&config, &vp9_config);
Ok(Self {
config,
vp9_config,
frame_count: 0,
output_queue: Vec::new(),
rate_controller,
flushing: false,
})
}
#[must_use]
pub fn vp9_config(&self) -> &Vp9EncoderConfig {
&self.vp9_config
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
fn encode_frame(&mut self, frame: &VideoFrame) {
let is_keyframe = self.frame_count == 0
|| (self.vp9_config.keyint > 0
&& self.frame_count % u64::from(self.vp9_config.keyint) == 0);
let qindex = self
.rate_controller
.base_qindex(&self.vp9_config, is_keyframe);
let data = self.write_frame(frame, is_keyframe, qindex);
self.rate_controller.update_frame(data.len(), is_keyframe);
let pts = frame.timestamp.pts;
self.output_queue.push(EncodedPacket {
data,
pts,
dts: pts,
keyframe: is_keyframe,
duration: None,
});
self.frame_count += 1;
}
fn write_frame(&self, frame: &VideoFrame, keyframe: bool, qindex: u8) -> Vec<u8> {
let mut data = Vec::with_capacity(1024);
if keyframe {
self.write_keyframe_header(&mut data, frame, qindex);
} else {
self.write_interframe_header(&mut data, qindex);
}
let payload = self.write_compressed_payload(frame, qindex);
data.extend_from_slice(&payload);
data
}
fn write_keyframe_header(&self, buf: &mut Vec<u8>, frame: &VideoFrame, qindex: u8) {
let error_flag = u8::from(self.vp9_config.error_resilient);
let byte0: u8 = 0b1000_0000 | (0 << 5) | (0 << 4) | (1 << 3) | (error_flag << 2);
buf.push(byte0);
buf.extend_from_slice(&[0x49, 0x83, 0x42]);
buf.push(0x00); let w = frame.width;
let h = frame.height;
buf.push((w & 0xFF) as u8);
buf.push(((w >> 8) & 0xFF) as u8);
buf.push((h & 0xFF) as u8);
buf.push(((h >> 8) & 0xFF) as u8);
buf.push(0x00);
buf.push(qindex);
}
fn write_interframe_header(&self, buf: &mut Vec<u8>, qindex: u8) {
let error_flag = u8::from(self.vp9_config.error_resilient);
let byte0: u8 = 0b1000_0000 | (1 << 5) | (1 << 3) | (error_flag << 2);
buf.push(byte0);
buf.push(0x01);
buf.push(0x00);
buf.push(qindex);
}
fn write_compressed_payload(&self, frame: &VideoFrame, qindex: u8) -> Vec<u8> {
let mut bw = BoolWriter::new();
let plane = frame.plane(0);
let width = plane.width() as usize;
let height = plane.height() as usize;
let stride = plane.stride() as usize;
let luma = plane.data();
let blk_size = 16usize;
let blocks_x = width / blk_size.max(1);
let blocks_y = height / blk_size.max(1);
let q_step = (u32::from(qindex) + 1).max(1);
for by in 0..blocks_y {
for bx in 0..blocks_x {
let dc = Self::block_average(luma, stride, bx * blk_size, by * blk_size, blk_size);
let quantised = (dc / q_step).min(255);
bw.write_literal(quantised, 8);
}
}
bw.finalise()
}
fn block_average(data: &[u8], stride: usize, x0: usize, y0: usize, size: usize) -> u32 {
let mut sum = 0u32;
let mut count = 0u32;
for y in 0..size {
let row = (y0 + y) * stride + x0;
if row + size > data.len() {
continue;
}
for x in 0..size {
sum += u32::from(data[row + x]);
count += 1;
}
}
sum.checked_div(count).unwrap_or(128)
}
}
impl VideoEncoder for Vp9Encoder {
fn codec(&self) -> CodecId {
CodecId::Vp9
}
fn send_frame(&mut self, frame: &VideoFrame) -> CodecResult<()> {
if self.flushing {
return Err(CodecError::InvalidParameter(
"Encoder is flushing".to_string(),
));
}
self.encode_frame(frame);
Ok(())
}
fn receive_packet(&mut self) -> CodecResult<Option<EncodedPacket>> {
if self.output_queue.is_empty() {
return Ok(None);
}
Ok(Some(self.output_queue.remove(0)))
}
fn flush(&mut self) -> CodecResult<()> {
self.flushing = true;
Ok(())
}
fn config(&self) -> &EncoderConfig {
&self.config
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Vp9Profile {
Profile0,
Profile2,
}
impl Default for Vp9Profile {
fn default() -> Self {
Self::Profile0
}
}
#[derive(Clone, Debug)]
pub struct Vp9EncConfig {
pub width: u32,
pub height: u32,
pub target_bitrate: u32,
pub keyframe_interval: u32,
pub quality: u8,
pub speed: u8,
pub profile: Vp9Profile,
}
impl Default for Vp9EncConfig {
fn default() -> Self {
Self {
width: 1280,
height: 720,
target_bitrate: 2000,
keyframe_interval: 250,
quality: 28,
speed: 4,
profile: Vp9Profile::Profile0,
}
}
}
#[derive(Clone, Debug)]
pub struct Vp9Packet {
pub data: Vec<u8>,
pub is_keyframe: bool,
pub pts: i64,
pub dts: i64,
}
#[derive(Debug)]
pub struct SimpleVp9Encoder {
config: Vp9EncConfig,
frame_count: u64,
keyframe_counter: u32,
}
impl SimpleVp9Encoder {
pub fn new(config: Vp9EncConfig) -> Result<Self, CodecError> {
if config.width == 0 || config.height == 0 {
return Err(CodecError::InvalidParameter(
"SimpleVp9Encoder: width and height must be non-zero".to_string(),
));
}
if config.quality > 63 {
return Err(CodecError::InvalidParameter(format!(
"SimpleVp9Encoder: quality {} exceeds maximum of 63",
config.quality
)));
}
if config.speed > 8 {
return Err(CodecError::InvalidParameter(format!(
"SimpleVp9Encoder: speed {} exceeds maximum of 8",
config.speed
)));
}
Ok(Self {
config,
frame_count: 0,
keyframe_counter: 0,
})
}
pub fn encode_frame(
&mut self,
frame: &[u8],
force_keyframe: bool,
) -> Result<Vp9Packet, CodecError> {
let expected = (self.config.width as usize) * (self.config.height as usize) * 3 / 2;
if frame.len() < expected {
return Err(CodecError::InvalidData(format!(
"SimpleVp9Encoder: frame too small ({} < {expected} bytes)",
frame.len()
)));
}
let is_keyframe = force_keyframe
|| self.frame_count == 0
|| (self.config.keyframe_interval > 0
&& self.keyframe_counter >= self.config.keyframe_interval);
let pts = self.frame_count as i64;
let data = self.build_packet(frame, is_keyframe)?;
if is_keyframe {
self.keyframe_counter = 0;
} else {
self.keyframe_counter += 1;
}
self.frame_count += 1;
Ok(Vp9Packet {
data,
is_keyframe,
pts,
dts: pts,
})
}
pub fn flush(&mut self) -> Result<Vec<Vp9Packet>, CodecError> {
Ok(Vec::new())
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
fn build_packet(&self, frame: &[u8], keyframe: bool) -> Result<Vec<u8>, CodecError> {
let mut buf = Vec::with_capacity(256);
if keyframe {
self.write_simple_keyframe_header(&mut buf);
} else {
self.write_simple_interframe_header(&mut buf);
}
let base_q_idx = (u16::from(self.config.quality) * 4).min(255) as u8;
buf.push(base_q_idx); buf.push(0x00);
let y_len = (self.config.width as usize) * (self.config.height as usize);
let luma = &frame[..y_len.min(frame.len())];
let payload = Self::encode_luma_statistics(luma, self.config.width as usize, base_q_idx);
buf.extend_from_slice(&payload);
Ok(buf)
}
fn write_simple_keyframe_header(&self, buf: &mut Vec<u8>) {
let (profile_low, profile_high) = match self.config.profile {
Vp9Profile::Profile0 => (0u8, 0u8),
Vp9Profile::Profile2 => (0u8, 1u8),
};
let byte0: u8 = 0b10_00_0000
| (profile_low << 5)
| (profile_high << 4)
| (0 << 3) | (0 << 2) | (1 << 1) | 0; buf.push(byte0);
buf.extend_from_slice(&[0x49, 0x83, 0x42]);
buf.push(0x03);
let w = self.config.width.saturating_sub(1);
let h = self.config.height.saturating_sub(1);
buf.push((w & 0xFF) as u8);
buf.push(((w >> 8) & 0xFF) as u8);
buf.push((h & 0xFF) as u8);
buf.push(((h >> 8) & 0xFF) as u8);
buf.push(0x00);
}
fn write_simple_interframe_header(&self, buf: &mut Vec<u8>) {
let (profile_low, profile_high) = match self.config.profile {
Vp9Profile::Profile0 => (0u8, 0u8),
Vp9Profile::Profile2 => (0u8, 1u8),
};
let byte0: u8 = 0b10_00_0000
| (profile_low << 5)
| (profile_high << 4)
| (0 << 3) | (1 << 2) | (1 << 1) | 0; buf.push(byte0);
buf.push(0x01);
buf.push(0x00);
buf.push(0x00);
buf.push(0x00);
}
fn encode_luma_statistics(luma: &[u8], width: usize, base_q_idx: u8) -> Vec<u8> {
let mut bw = BoolWriter::new();
let blk = 16usize;
let stride = width;
let height = luma.len().checked_div(stride).unwrap_or(0);
let blocks_x = width / blk.max(1);
let blocks_y = height / blk.max(1);
let q_step = u32::from(base_q_idx).saturating_add(1);
for by in 0..blocks_y {
for bx in 0..blocks_x {
let dc = Self::block_avg(luma, stride, bx * blk, by * blk, blk);
let quantised = (dc / q_step).min(255);
bw.write_literal(quantised, 8);
}
}
bw.finalise()
}
fn block_avg(data: &[u8], stride: usize, x0: usize, y0: usize, size: usize) -> u32 {
let mut sum = 0u32;
let mut count = 0u32;
for y in 0..size {
let row = (y0 + y) * stride + x0;
if row + size > data.len() {
continue;
}
for x in 0..size {
sum += u32::from(data[row + x]);
count += 1;
}
}
sum.checked_div(count).unwrap_or(128)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::{Plane, VideoFrame};
use crate::traits::EncoderPreset;
use oximedia_core::{PixelFormat, Rational};
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_encoder_creation() {
let config = EncoderConfig::vp9(320, 240).with_crf(24.0);
let encoder = Vp9Encoder::new(config);
assert!(encoder.is_ok());
}
#[test]
fn test_encoder_rejects_zero_dimensions() {
let mut config = EncoderConfig::vp9(320, 240);
config.width = 0;
assert!(Vp9Encoder::new(config).is_err());
}
#[test]
fn test_encoder_rejects_wrong_codec() {
let mut config = EncoderConfig::vp9(320, 240);
config.codec = CodecId::Av1;
assert!(Vp9Encoder::new(config).is_err());
}
#[test]
fn test_encode_single_frame() {
let config = EncoderConfig::vp9(64, 64).with_crf(28.0);
let mut encoder = Vp9Encoder::new(config).expect("encoder creation failed");
let frame = make_test_frame(64, 64);
encoder.send_frame(&frame).expect("send_frame failed");
let packet = encoder.receive_packet().expect("receive_packet failed");
assert!(packet.is_some());
let pkt = packet.expect("expected Some packet");
assert!(pkt.keyframe);
assert!(!pkt.data.is_empty());
}
#[test]
fn test_keyframe_interval() {
let mut config = EncoderConfig::vp9(64, 64).with_crf(28.0);
config.keyint = 3;
let mut encoder = Vp9Encoder::new(config).expect("encoder creation failed");
let frame = make_test_frame(64, 64);
let mut keyframes = Vec::new();
for i in 0..9 {
encoder.send_frame(&frame).expect("send_frame failed");
let pkt = encoder
.receive_packet()
.expect("receive failed")
.expect("expected packet");
if pkt.keyframe {
keyframes.push(i);
}
}
assert_eq!(keyframes, vec![0, 3, 6]);
}
#[test]
fn test_crf_affects_qindex() {
let low_crf = Vp9EncoderConfig {
crf: 10.0,
..Default::default()
};
let high_crf = Vp9EncoderConfig {
crf: 55.0,
..Default::default()
};
assert!(low_crf.base_qindex() < high_crf.base_qindex());
}
#[test]
fn test_bool_writer_deterministic() {
let mut bw = BoolWriter::new();
bw.write_bool(true, 128);
bw.write_bool(false, 128);
bw.write_literal(42, 8);
let out1 = bw.finalise();
let mut bw2 = BoolWriter::new();
bw2.write_bool(true, 128);
bw2.write_bool(false, 128);
bw2.write_literal(42, 8);
let out2 = bw2.finalise();
assert_eq!(out1, out2);
}
#[test]
fn test_with_vp9_config() {
let config = EncoderConfig::vp9(128, 96);
let vp9cfg = Vp9EncoderConfig {
crf: 18.0,
keyint: 60,
speed: 8,
tile_cols_log2: 1,
error_resilient: true,
};
let encoder = Vp9Encoder::with_vp9_config(config, vp9cfg).expect("encoder creation failed");
assert!((encoder.vp9_config().crf - 18.0).abs() < f32::EPSILON);
assert!(encoder.vp9_config().error_resilient);
}
#[test]
fn test_flush() {
let config = EncoderConfig::vp9(64, 64).with_crf(28.0);
let mut encoder = Vp9Encoder::new(config).expect("encoder creation failed");
encoder.flush().expect("flush failed");
let frame = make_test_frame(64, 64);
assert!(encoder.send_frame(&frame).is_err());
}
#[test]
fn test_codec_id() {
let config = EncoderConfig::vp9(64, 64);
let encoder = Vp9Encoder::new(config).expect("encoder creation failed");
assert_eq!(encoder.codec(), CodecId::Vp9);
}
#[test]
fn test_block_average() {
let data = vec![100u8; 64 * 64];
let avg = Vp9Encoder::block_average(&data, 64, 0, 0, 16);
assert_eq!(avg, 100);
}
#[test]
fn test_multiple_frames_output_sizes() {
let config = EncoderConfig::vp9(64, 64).with_crf(20.0);
let mut encoder = Vp9Encoder::new(config).expect("encoder creation failed");
let frame = make_test_frame(64, 64);
let mut sizes = Vec::new();
for _ in 0..5 {
encoder.send_frame(&frame).expect("send_frame failed");
let pkt = encoder
.receive_packet()
.expect("receive failed")
.expect("expected packet");
sizes.push(pkt.data.len());
}
assert!(sizes.iter().all(|&s| s > 0));
}
#[test]
fn test_lossless_crf_zero() {
let config = EncoderConfig {
codec: CodecId::Vp9,
width: 64,
height: 64,
bitrate: BitrateMode::Lossless,
keyint: 10,
..EncoderConfig::default()
};
let encoder = Vp9Encoder::new(config).expect("encoder creation failed");
assert!((encoder.vp9_config().crf - 0.0).abs() < f32::EPSILON);
}
fn make_yuv420_frame(width: u32, height: u32, luma: u8) -> Vec<u8> {
let y_size = (width * height) as usize;
let uv_size = ((width / 2) * (height / 2)) as usize;
let mut buf = vec![luma; y_size];
buf.extend(vec![128u8; uv_size * 2]); buf
}
#[test]
fn test_simple_encoder_creation_valid() {
let config = Vp9EncConfig {
width: 320,
height: 240,
quality: 28,
speed: 4,
..Vp9EncConfig::default()
};
assert!(SimpleVp9Encoder::new(config).is_ok());
}
#[test]
fn test_simple_encoder_rejects_zero_width() {
let config = Vp9EncConfig {
width: 0,
height: 240,
..Vp9EncConfig::default()
};
assert!(SimpleVp9Encoder::new(config).is_err());
}
#[test]
fn test_simple_encoder_rejects_zero_height() {
let config = Vp9EncConfig {
width: 320,
height: 0,
..Vp9EncConfig::default()
};
assert!(SimpleVp9Encoder::new(config).is_err());
}
#[test]
fn test_simple_encoder_rejects_quality_over_63() {
let config = Vp9EncConfig {
width: 320,
height: 240,
quality: 64,
..Vp9EncConfig::default()
};
assert!(SimpleVp9Encoder::new(config).is_err());
}
#[test]
fn test_simple_encoder_rejects_speed_over_8() {
let config = Vp9EncConfig {
width: 320,
height: 240,
speed: 9,
..Vp9EncConfig::default()
};
assert!(SimpleVp9Encoder::new(config).is_err());
}
#[test]
fn test_simple_encoder_first_frame_is_keyframe() {
let config = Vp9EncConfig {
width: 64,
height: 64,
keyframe_interval: 10,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 128);
let pkt = enc.encode_frame(&frame, false).expect("encode failed");
assert!(pkt.is_keyframe, "first frame must be a keyframe");
}
#[test]
fn test_simple_encoder_subsequent_frames_are_inter() {
let config = Vp9EncConfig {
width: 64,
height: 64,
keyframe_interval: 100,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 128);
let _kf = enc.encode_frame(&frame, false).expect("encode frame 0");
let pkt = enc.encode_frame(&frame, false).expect("encode frame 1");
assert!(!pkt.is_keyframe, "second frame should be inter");
}
#[test]
fn test_simple_encoder_force_keyframe() {
let config = Vp9EncConfig {
width: 64,
height: 64,
keyframe_interval: 100,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 128);
let _kf = enc.encode_frame(&frame, false).expect("first frame");
let _inter = enc.encode_frame(&frame, false).expect("second frame");
let forced = enc.encode_frame(&frame, true).expect("forced keyframe");
assert!(
forced.is_keyframe,
"force_keyframe=true must produce a keyframe"
);
}
#[test]
fn test_simple_encoder_keyframe_interval() {
let config = Vp9EncConfig {
width: 64,
height: 64,
keyframe_interval: 3,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 100);
let mut kf_indices: Vec<u64> = Vec::new();
for i in 0..9u64 {
let pkt = enc.encode_frame(&frame, false).expect("encode");
if pkt.is_keyframe {
kf_indices.push(i);
}
}
assert_eq!(kf_indices, vec![0, 4, 8]);
}
#[test]
fn test_simple_encoder_flush_returns_empty() {
let config = Vp9EncConfig {
width: 64,
height: 64,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let pkts = enc.flush().expect("flush failed");
assert!(pkts.is_empty(), "flush should return no buffered packets");
}
#[test]
fn test_simple_encoder_frame_count_increments() {
let config = Vp9EncConfig {
width: 64,
height: 64,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 128);
assert_eq!(enc.frame_count(), 0);
enc.encode_frame(&frame, false).expect("frame 0");
assert_eq!(enc.frame_count(), 1);
enc.encode_frame(&frame, false).expect("frame 1");
assert_eq!(enc.frame_count(), 2);
}
#[test]
fn test_simple_encoder_packet_non_empty() {
let config = Vp9EncConfig {
width: 64,
height: 64,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 200);
let pkt = enc.encode_frame(&frame, false).expect("encode");
assert!(!pkt.data.is_empty(), "encoded packet must not be empty");
}
#[test]
fn test_simple_encoder_pts_increments() {
let config = Vp9EncConfig {
width: 64,
height: 64,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let frame = make_yuv420_frame(64, 64, 128);
let p0 = enc.encode_frame(&frame, false).expect("frame 0");
let p1 = enc.encode_frame(&frame, false).expect("frame 1");
assert_eq!(p0.pts, 0);
assert_eq!(p1.pts, 1);
assert_eq!(p0.dts, p0.pts);
assert_eq!(p1.dts, p1.pts);
}
#[test]
fn test_simple_encoder_profile2_different_header() {
let cfg0 = Vp9EncConfig {
width: 64,
height: 64,
profile: Vp9Profile::Profile0,
..Vp9EncConfig::default()
};
let cfg2 = Vp9EncConfig {
width: 64,
height: 64,
profile: Vp9Profile::Profile2,
..Vp9EncConfig::default()
};
let frame = make_yuv420_frame(64, 64, 128);
let mut enc0 = SimpleVp9Encoder::new(cfg0).expect("profile0");
let mut enc2 = SimpleVp9Encoder::new(cfg2).expect("profile2");
let pkt0 = enc0.encode_frame(&frame, false).expect("encode p0");
let pkt2 = enc2.encode_frame(&frame, false).expect("encode p2");
assert_ne!(
pkt0.data[0], pkt2.data[0],
"Profile0 and Profile2 headers should differ in byte 0"
);
}
#[test]
fn test_simple_encoder_rejects_undersized_frame() {
let config = Vp9EncConfig {
width: 64,
height: 64,
..Vp9EncConfig::default()
};
let mut enc = SimpleVp9Encoder::new(config).expect("creation failed");
let tiny = vec![0u8; 100];
let result = enc.encode_frame(&tiny, false);
assert!(result.is_err(), "undersized frame should return an error");
}
}