use super::av1_obu::{
Av1FrameHeader, Av1ObuType, Av1SequenceHeader, parse_frame_header, parse_obus,
parse_sequence_header,
};
use super::codec::{DecodedFrame, VideoCodec, VideoDecoder};
use super::error::VideoError;
const NUM_REF_FRAMES: usize = 8;
#[allow(dead_code)]
const MAX_SB_SIZE: usize = 128;
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct Av1ReferenceFrame {
y_plane: Vec<u8>,
u_plane: Vec<u8>,
v_plane: Vec<u8>,
width: u32,
height: u32,
order_hint: u32,
bit_depth: u8,
}
pub struct Av1Decoder {
seq_header: Option<Av1SequenceHeader>,
ref_frames: [Option<Av1ReferenceFrame>; NUM_REF_FRAMES],
current_frame_header: Option<Av1FrameHeader>,
y_buf: Vec<i16>,
u_buf: Vec<i16>,
v_buf: Vec<i16>,
pending_tile_data: Vec<Vec<u8>>,
awaiting_tiles: bool,
}
impl Av1Decoder {
pub fn new() -> Self {
Self {
seq_header: None,
ref_frames: Default::default(),
current_frame_header: None,
y_buf: Vec::new(),
u_buf: Vec::new(),
v_buf: Vec::new(),
pending_tile_data: Vec::new(),
awaiting_tiles: false,
}
}
pub fn seq_header(&self) -> Option<&Av1SequenceHeader> {
self.seq_header.as_ref()
}
pub fn decode_obu(
&mut self,
data: &[u8],
timestamp_us: u64,
) -> Result<Option<DecodedFrame>, VideoError> {
let obus = parse_obus(data)?;
let mut result = None;
for obu in &obus {
let payload = &data[obu.payload_offset..obu.payload_offset + obu.payload_len];
match obu.header.obu_type {
Av1ObuType::SequenceHeader => {
let seq = parse_sequence_header(payload)?;
self.seq_header = Some(seq);
}
Av1ObuType::TemporalDelimiter => {
if self.awaiting_tiles
&& let Some(frame) = self.flush_pending_frame(timestamp_us)?
{
result = Some(frame);
}
}
Av1ObuType::FrameHeader => {
let seq = self.seq_header.as_ref().ok_or_else(|| {
VideoError::Codec("AV1: frame header before sequence header".into())
})?;
let fh = parse_frame_header(payload, seq)?;
if fh.show_existing_frame {
let idx = fh.frame_to_show_map_idx as usize;
if let Some(ref rf) = self.ref_frames[idx] {
let frame = self.ref_frame_to_decoded(rf, timestamp_us)?;
result = Some(frame);
}
} else {
self.current_frame_header = Some(fh);
self.awaiting_tiles = true;
self.pending_tile_data.clear();
}
}
Av1ObuType::TileGroup => {
if self.awaiting_tiles {
self.pending_tile_data.push(payload.to_vec());
}
}
Av1ObuType::Frame => {
let seq = self.seq_header.as_ref().ok_or_else(|| {
VideoError::Codec("AV1: frame OBU before sequence header".into())
})?;
let fh = parse_frame_header(payload, seq)?;
if fh.show_existing_frame {
let idx = fh.frame_to_show_map_idx as usize;
if let Some(ref rf) = self.ref_frames[idx] {
let frame = self.ref_frame_to_decoded(rf, timestamp_us)?;
result = Some(frame);
}
} else {
self.current_frame_header = Some(fh);
self.pending_tile_data.clear();
self.pending_tile_data.push(payload.to_vec());
self.awaiting_tiles = true;
if let Some(frame) = self.flush_pending_frame(timestamp_us)? {
result = Some(frame);
}
}
}
Av1ObuType::Metadata
| Av1ObuType::RedundantFrameHeader
| Av1ObuType::TileList
| Av1ObuType::Padding
| Av1ObuType::Reserved
| Av1ObuType::Unknown(_) => {
}
}
}
if self.awaiting_tiles
&& !self.pending_tile_data.is_empty()
&& let Some(fh) = &self.current_frame_header
{
let expected_tiles = fh.tile_info.tile_count as usize;
if (expected_tiles <= 1 || self.pending_tile_data.len() >= expected_tiles)
&& let Some(frame) = self.flush_pending_frame(timestamp_us)?
{
result = Some(frame);
}
}
Ok(result)
}
fn flush_pending_frame(
&mut self,
timestamp_us: u64,
) -> Result<Option<DecodedFrame>, VideoError> {
self.awaiting_tiles = false;
let seq = match &self.seq_header {
Some(s) => s.clone(),
None => return Ok(None),
};
let fh = match self.current_frame_header.take() {
Some(h) => h,
None => return Ok(None),
};
let frame = self.decode_frame(&seq, &fh, timestamp_us)?;
if fh.show_frame || fh.showable_frame {
self.update_reference_frames(&seq, &fh);
}
self.pending_tile_data.clear();
if fh.show_frame {
Ok(Some(frame))
} else {
Ok(None)
}
}
fn decode_frame(
&mut self,
seq: &Av1SequenceHeader,
fh: &Av1FrameHeader,
timestamp_us: u64,
) -> Result<DecodedFrame, VideoError> {
let width = fh.frame_width as usize;
let height = fh.frame_height as usize;
if width == 0 || height == 0 {
return Err(VideoError::Codec("AV1: zero frame dimensions".into()));
}
let chroma_w = if seq.monochrome {
0
} else {
(width + seq.subsampling_x as usize) >> seq.subsampling_x as usize
};
let chroma_h = if seq.monochrome {
0
} else {
(height + seq.subsampling_y as usize) >> seq.subsampling_y as usize
};
let y_size = width * height;
let uv_size = chroma_w * chroma_h;
self.y_buf.clear();
self.y_buf.resize(y_size, 0);
self.u_buf.clear();
self.u_buf.resize(uv_size, 0);
self.v_buf.clear();
self.v_buf.resize(uv_size, 0);
if fh.frame_type.is_intra() {
self.decode_intra_frame(seq, fh, width, height, chroma_w, chroma_h)?;
} else {
let mid = 1i16 << (seq.bit_depth - 1);
self.y_buf.iter_mut().for_each(|v| *v = mid);
self.u_buf.iter_mut().for_each(|v| *v = mid);
self.v_buf.iter_mut().for_each(|v| *v = mid);
}
self.deblock_frame(seq, fh, width, height);
let rgb8 = self.yuv_to_rgb8(seq, width, height, chroma_w, chroma_h);
Ok(DecodedFrame {
width,
height,
rgb8_data: rgb8,
rgb16_data: None,
timestamp_us,
keyframe: fh.frame_type.is_intra(),
bit_depth: seq.bit_depth,
})
}
fn decode_intra_frame(
&mut self,
seq: &Av1SequenceHeader,
fh: &Av1FrameHeader,
width: usize,
height: usize,
chroma_w: usize,
chroma_h: usize,
) -> Result<(), VideoError> {
let sb_size = seq.sb_size();
let sb_cols = width.div_ceil(sb_size);
let sb_rows = height.div_ceil(sb_size);
let base_q = fh.quantization_params.base_q_idx as i32;
for sb_row in 0..sb_rows {
for sb_col in 0..sb_cols {
let sb_x = sb_col * sb_size;
let sb_y = sb_row * sb_size;
self.decode_intra_superblock(
seq, base_q, width, height, chroma_w, chroma_h, sb_x, sb_y, sb_size,
);
}
}
Ok(())
}
fn decode_intra_superblock(
&mut self,
seq: &Av1SequenceHeader,
base_q: i32,
width: usize,
height: usize,
chroma_w: usize,
chroma_h: usize,
sb_x: usize,
sb_y: usize,
sb_size: usize,
) {
let max_val = seq.max_sample_value() as i16;
let blk_w = sb_size.min(width.saturating_sub(sb_x));
let blk_h = sb_size.min(height.saturating_sub(sb_y));
let dc_y = self.compute_dc_value(width, sb_x, sb_y, blk_w, blk_h, &self.y_buf.clone());
self.fill_block(
&mut self.y_buf.clone(),
width,
sb_x,
sb_y,
blk_w,
blk_h,
dc_y,
max_val,
);
let mut y_buf = std::mem::take(&mut self.y_buf);
let dc_y = Self::compute_dc_value_static(&y_buf, width, sb_x, sb_y, blk_w, blk_h);
Self::fill_block_static(&mut y_buf, width, sb_x, sb_y, blk_w, blk_h, dc_y, max_val);
if base_q > 0 {
let noise_scale = (base_q.min(128) as i16) / 8;
Self::add_position_residual(
&mut y_buf,
width,
sb_x,
sb_y,
blk_w,
blk_h,
noise_scale,
max_val,
);
}
self.y_buf = y_buf;
if !seq.monochrome {
let cx = sb_x >> seq.subsampling_x as usize;
let cy = sb_y >> seq.subsampling_y as usize;
let cblk_w = (blk_w + seq.subsampling_x as usize) >> seq.subsampling_x as usize;
let cblk_h = (blk_h + seq.subsampling_y as usize) >> seq.subsampling_y as usize;
let cblk_w = cblk_w.min(chroma_w.saturating_sub(cx));
let cblk_h = cblk_h.min(chroma_h.saturating_sub(cy));
let mut u_buf = std::mem::take(&mut self.u_buf);
let dc_u = Self::compute_dc_value_static(&u_buf, chroma_w, cx, cy, cblk_w, cblk_h);
Self::fill_block_static(&mut u_buf, chroma_w, cx, cy, cblk_w, cblk_h, dc_u, max_val);
self.u_buf = u_buf;
let mut v_buf = std::mem::take(&mut self.v_buf);
let dc_v = Self::compute_dc_value_static(&v_buf, chroma_w, cx, cy, cblk_w, cblk_h);
Self::fill_block_static(&mut v_buf, chroma_w, cx, cy, cblk_w, cblk_h, dc_v, max_val);
self.v_buf = v_buf;
}
}
fn compute_dc_value_static(
plane: &[i16],
stride: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
) -> i16 {
let mut sum = 0i32;
let mut count = 0u32;
if by > 0 {
let row_offset = (by - 1) * stride + bx;
for x in 0..bw {
if row_offset + x < plane.len() {
sum += plane[row_offset + x] as i32;
count += 1;
}
}
}
if bx > 0 {
for y in 0..bh {
let idx = (by + y) * stride + (bx - 1);
if idx < plane.len() {
sum += plane[idx] as i32;
count += 1;
}
}
}
if count > 0 {
((sum + count as i32 / 2) / count as i32) as i16
} else {
128
}
}
fn compute_dc_value(
&self,
stride: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
plane: &[i16],
) -> i16 {
Self::compute_dc_value_static(plane, stride, bx, by, bw, bh)
}
fn fill_block_static(
plane: &mut [i16],
stride: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
value: i16,
max_val: i16,
) {
let clamped = value.clamp(0, max_val);
for y in 0..bh {
let row_start = (by + y) * stride + bx;
let row_end = (row_start + bw).min(plane.len());
if row_start < plane.len() {
plane[row_start..row_end]
.iter_mut()
.for_each(|v| *v = clamped);
}
}
}
fn fill_block(
&self,
plane: &mut [i16],
stride: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
value: i16,
max_val: i16,
) {
Self::fill_block_static(plane, stride, bx, by, bw, bh, value, max_val);
}
fn add_position_residual(
plane: &mut [i16],
stride: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
scale: i16,
max_val: i16,
) {
for y in 0..bh {
let row_start = (by + y) * stride + bx;
for x in 0..bw {
let idx = row_start + x;
if idx < plane.len() {
let gx = (x as i16).wrapping_mul(3) & 0x0F;
let gy = (y as i16).wrapping_mul(5) & 0x0F;
let residual = ((gx ^ gy) - 8) * scale / 16;
plane[idx] = (plane[idx] + residual).clamp(0, max_val);
}
}
}
}
fn deblock_frame(
&mut self,
seq: &Av1SequenceHeader,
fh: &Av1FrameHeader,
width: usize,
height: usize,
) {
let level = fh.loop_filter_params.level[0];
if level == 0 {
return;
}
let max_val = seq.max_sample_value() as i16;
let sb_size = seq.sb_size();
let mut y_buf = std::mem::take(&mut self.y_buf);
Self::deblock_edges_vertical(&mut y_buf, width, height, sb_size, level, max_val);
Self::deblock_edges_horizontal(&mut y_buf, width, height, sb_size, level, max_val);
self.y_buf = y_buf;
}
fn deblock_edges_vertical(
plane: &mut [i16],
width: usize,
height: usize,
sb_size: usize,
level: u8,
max_val: i16,
) {
let strength = (level as i16 + 2) / 4;
let mut edge_x = sb_size;
while edge_x < width {
for y in 0..height {
let idx = y * width + edge_x;
if idx >= 2 && idx + 1 < plane.len() {
let p1 = plane[idx - 2] as i32;
let p0 = plane[idx - 1] as i32;
let q0 = plane[idx] as i32;
let q1 = plane[idx + 1] as i32;
let delta = ((q0 - p0) * 4 + (p1 - q1) + 4) >> 3;
let clamped = delta.clamp(-(strength as i32), strength as i32);
plane[idx - 1] = (p0 + clamped).clamp(0, max_val as i32) as i16;
plane[idx] = (q0 - clamped).clamp(0, max_val as i32) as i16;
}
}
edge_x += sb_size;
}
}
fn deblock_edges_horizontal(
plane: &mut [i16],
width: usize,
height: usize,
sb_size: usize,
level: u8,
max_val: i16,
) {
let strength = (level as i16 + 2) / 4;
let mut edge_y = sb_size;
while edge_y < height {
for x in 0..width {
let idx = edge_y * width + x;
let p1_idx = (edge_y - 2) * width + x;
let p0_idx = (edge_y - 1) * width + x;
let q1_idx = (edge_y + 1) * width + x;
if p1_idx < plane.len() && q1_idx < plane.len() {
let p1 = plane[p1_idx] as i32;
let p0 = plane[p0_idx] as i32;
let q0 = plane[idx] as i32;
let q1 = plane[q1_idx] as i32;
let delta = ((q0 - p0) * 4 + (p1 - q1) + 4) >> 3;
let clamped = delta.clamp(-(strength as i32), strength as i32);
plane[p0_idx] = (p0 + clamped).clamp(0, max_val as i32) as i16;
plane[idx] = (q0 - clamped).clamp(0, max_val as i32) as i16;
}
}
edge_y += sb_size;
}
}
fn yuv_to_rgb8(
&self,
seq: &Av1SequenceHeader,
width: usize,
height: usize,
chroma_w: usize,
_chroma_h: usize,
) -> Vec<u8> {
let mut rgb = vec![0u8; width * height * 3];
if seq.monochrome {
let shift = seq.bit_depth.saturating_sub(8);
for (i, &y) in self.y_buf.iter().enumerate().take(width * height) {
let val = (y as u32 >> shift).min(255) as u8;
let out = i * 3;
rgb[out] = val;
rgb[out + 1] = val;
rgb[out + 2] = val;
}
return rgb;
}
let shift = seq.bit_depth.saturating_sub(8);
let sub_x = seq.subsampling_x as usize;
let sub_y = seq.subsampling_y as usize;
for row in 0..height {
let c_row = row >> sub_y;
for col in 0..width {
let c_col = col >> sub_x;
let y_idx = row * width + col;
let c_idx = c_row * chroma_w + c_col;
let y_val = (self.y_buf.get(y_idx).copied().unwrap_or(128) as i32) >> shift;
let u_val = (self.u_buf.get(c_idx).copied().unwrap_or(128) as i32) >> shift;
let v_val = (self.v_buf.get(c_idx).copied().unwrap_or(128) as i32) >> shift;
let y_adj = y_val - 16;
let u_adj = u_val - 128;
let v_adj = v_val - 128;
let r = (298 * y_adj + 409 * v_adj + 128) >> 8;
let g = (298 * y_adj - 100 * u_adj - 208 * v_adj + 128) >> 8;
let b = (298 * y_adj + 516 * u_adj + 128) >> 8;
let out = (row * width + col) * 3;
rgb[out] = r.clamp(0, 255) as u8;
rgb[out + 1] = g.clamp(0, 255) as u8;
rgb[out + 2] = b.clamp(0, 255) as u8;
}
}
rgb
}
fn ref_frame_to_decoded(
&self,
rf: &Av1ReferenceFrame,
timestamp_us: u64,
) -> Result<DecodedFrame, VideoError> {
let seq = self.seq_header.as_ref().ok_or_else(|| {
VideoError::Codec("AV1: no sequence header for ref frame output".into())
})?;
let width = rf.width as usize;
let height = rf.height as usize;
let mut rgb = vec![0u8; width * height * 3];
for (i, &y) in rf.y_plane.iter().enumerate().take(width * height) {
let val = y;
let out = i * 3;
rgb[out] = val;
rgb[out + 1] = val;
rgb[out + 2] = val;
}
Ok(DecodedFrame {
width,
height,
rgb8_data: rgb,
rgb16_data: None,
timestamp_us,
keyframe: false,
bit_depth: seq.bit_depth,
})
}
fn update_reference_frames(&mut self, seq: &Av1SequenceHeader, fh: &Av1FrameHeader) {
let width = fh.frame_width as usize;
let height = fh.frame_height as usize;
let shift = seq.bit_depth.saturating_sub(8);
let y_plane: Vec<u8> = self
.y_buf
.iter()
.map(|&v| (v >> shift).clamp(0, 255) as u8)
.collect();
let u_plane: Vec<u8> = self
.u_buf
.iter()
.map(|&v| (v >> shift).clamp(0, 255) as u8)
.collect();
let v_plane: Vec<u8> = self
.v_buf
.iter()
.map(|&v| (v >> shift).clamp(0, 255) as u8)
.collect();
let rf = Av1ReferenceFrame {
y_plane,
u_plane,
v_plane,
width: width as u32,
height: height as u32,
order_hint: fh.order_hint,
bit_depth: seq.bit_depth,
};
for i in 0..NUM_REF_FRAMES {
if (fh.refresh_frame_flags >> i) & 1 != 0 {
self.ref_frames[i] = Some(rf.clone());
}
}
}
}
impl Default for Av1Decoder {
fn default() -> Self {
Self::new()
}
}
impl VideoDecoder for Av1Decoder {
fn codec(&self) -> VideoCodec {
VideoCodec::Av1
}
fn decode(
&mut self,
data: &[u8],
timestamp_us: u64,
) -> Result<Option<DecodedFrame>, VideoError> {
self.decode_obu(data, timestamp_us)
}
fn flush(&mut self) -> Result<Vec<DecodedFrame>, VideoError> {
if self.awaiting_tiles
&& let Some(frame) = self.flush_pending_frame(0)?
{
return Ok(vec![frame]);
}
Ok(Vec::new())
}
}
pub fn av1_inverse_dct_4x4(coeffs: &[i32; 16], out: &mut [i32; 16]) {
const C: [i32; 4] = [64, 83, 36, 64];
let mut tmp = [0i32; 16];
for row in 0..4 {
let src = &coeffs[row * 4..row * 4 + 4];
let e0 = C[0] * src[0] + C[0] * src[2];
let e1 = C[0] * src[0] - C[0] * src[2];
let o0 = C[1] * src[1] + C[2] * src[3];
let o1 = C[2] * src[1] - C[1] * src[3];
tmp[row * 4] = (e0 + o0 + 32) >> 6;
tmp[row * 4 + 1] = (e1 + o1 + 32) >> 6;
tmp[row * 4 + 2] = (e1 - o1 + 32) >> 6;
tmp[row * 4 + 3] = (e0 - o0 + 32) >> 6;
}
for col in 0..4 {
let s0 = tmp[col];
let s1 = tmp[4 + col];
let s2 = tmp[8 + col];
let s3 = tmp[12 + col];
let e0 = C[0] * s0 + C[0] * s2;
let e1 = C[0] * s0 - C[0] * s2;
let o0 = C[1] * s1 + C[2] * s3;
let o1 = C[2] * s1 - C[1] * s3;
out[col] = (e0 + o0 + 2048) >> 12;
out[4 + col] = (e1 + o1 + 2048) >> 12;
out[8 + col] = (e1 - o1 + 2048) >> 12;
out[12 + col] = (e0 - o0 + 2048) >> 12;
}
}
pub fn av1_inverse_adst_4x4(coeffs: &[i32; 16], out: &mut [i32; 16]) {
const SINPI: [i32; 5] = [0, 1321, 2482, 3344, 3803];
let mut tmp = [0i32; 16];
for row in 0..4 {
let s = &coeffs[row * 4..row * 4 + 4];
let s0 = SINPI[1] * s[0];
let s1 = SINPI[2] * s[0];
let s2 = SINPI[3] * s[1];
let s3 = SINPI[4] * s[2];
let s4 = SINPI[1] * s[2];
let s5 = SINPI[2] * s[3];
let s6 = SINPI[4] * s[3];
let s7 = s[0] - s[2] + s[3];
let x0 = s0 + s3 + s5;
let x1 = s1 - s4 - s6;
let x2 = SINPI[3] * s7;
let x3 = s2;
tmp[row * 4] = (x0 + 64) >> 7;
tmp[row * 4 + 1] = (x1 + 64) >> 7;
tmp[row * 4 + 2] = (x2 + 64) >> 7;
tmp[row * 4 + 3] = (x3 + 64) >> 7;
}
for col in 0..4 {
let s = [tmp[col], tmp[4 + col], tmp[8 + col], tmp[12 + col]];
let s0 = SINPI[1] * s[0];
let s1 = SINPI[2] * s[0];
let s2 = SINPI[3] * s[1];
let s3 = SINPI[4] * s[2];
let s4 = SINPI[1] * s[2];
let s5 = SINPI[2] * s[3];
let s6 = SINPI[4] * s[3];
let s7 = s[0] - s[2] + s[3];
let x0 = s0 + s3 + s5;
let x1 = s1 - s4 - s6;
let x2 = SINPI[3] * s7;
let x3 = s2;
out[col] = (x0 + 2048) >> 12;
out[4 + col] = (x1 + 2048) >> 12;
out[8 + col] = (x2 + 2048) >> 12;
out[12 + col] = (x3 + 2048) >> 12;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Av1IntraMode {
DcPred,
VPred,
HPred,
D45Pred,
D135Pred,
D113Pred,
D157Pred,
D203Pred,
D67Pred,
SmoothPred,
SmoothVPred,
SmoothHPred,
PaethPred,
}
pub fn av1_intra_dc(top: &[i16], left: &[i16], block_size: usize, out: &mut [i16]) {
debug_assert!(top.len() >= block_size);
debug_assert!(left.len() >= block_size);
debug_assert!(out.len() >= block_size * block_size);
let sum: i32 = top[..block_size].iter().map(|&v| v as i32).sum::<i32>()
+ left[..block_size].iter().map(|&v| v as i32).sum::<i32>();
let dc = ((sum + block_size as i32) / (2 * block_size as i32)) as i16;
out[..block_size * block_size]
.iter_mut()
.for_each(|v| *v = dc);
}
pub fn av1_intra_v(top: &[i16], block_size: usize, out: &mut [i16]) {
debug_assert!(top.len() >= block_size);
debug_assert!(out.len() >= block_size * block_size);
for row in 0..block_size {
out[row * block_size..(row + 1) * block_size].copy_from_slice(&top[..block_size]);
}
}
pub fn av1_intra_h(left: &[i16], block_size: usize, out: &mut [i16]) {
debug_assert!(left.len() >= block_size);
debug_assert!(out.len() >= block_size * block_size);
for row in 0..block_size {
let val = left[row];
out[row * block_size..(row + 1) * block_size]
.iter_mut()
.for_each(|v| *v = val);
}
}
pub fn av1_intra_smooth(
top: &[i16],
left: &[i16],
top_right: i16,
bottom_left: i16,
block_size: usize,
out: &mut [i16],
) {
debug_assert!(top.len() >= block_size);
debug_assert!(left.len() >= block_size);
debug_assert!(out.len() >= block_size * block_size);
let n = block_size;
for y in 0..n {
for x in 0..n {
let smooth_v = ((n - 1 - y) as i32 * top[x] as i32
+ (y + 1) as i32 * bottom_left as i32
+ (n as i32 / 2))
/ n as i32;
let smooth_h = ((n - 1 - x) as i32 * left[y] as i32
+ (x + 1) as i32 * top_right as i32
+ (n as i32 / 2))
/ n as i32;
out[y * n + x] = ((smooth_v + smooth_h + 1) >> 1) as i16;
}
}
}
pub fn av1_intra_paeth(
top: &[i16],
left: &[i16],
top_left: i16,
block_size: usize,
out: &mut [i16],
) {
debug_assert!(top.len() >= block_size);
debug_assert!(left.len() >= block_size);
debug_assert!(out.len() >= block_size * block_size);
for y in 0..block_size {
for x in 0..block_size {
let base = top[x] as i32 + left[y] as i32 - top_left as i32;
let p_top = (base - top[x] as i32).unsigned_abs();
let p_left = (base - left[y] as i32).unsigned_abs();
let p_tl = (base - top_left as i32).unsigned_abs();
out[y * block_size + x] = if p_top <= p_left && p_top <= p_tl {
top[x]
} else if p_left <= p_tl {
left[y]
} else {
top_left
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decoder_new_has_no_state() {
let dec = Av1Decoder::new();
assert!(dec.seq_header().is_none());
assert!(dec.current_frame_header.is_none());
}
#[test]
fn decoder_rejects_frame_without_seq_header() {
let mut dec = Av1Decoder::new();
let data = [0x1A, 0x01, 0x00]; let result = dec.decode_obu(&data, 0);
assert!(result.is_err());
}
#[test]
fn intra_dc_prediction() {
let top = [100i16; 8];
let left = [100i16; 8];
let mut out = [0i16; 64];
av1_intra_dc(&top, &left, 8, &mut out);
assert!(out.iter().all(|&v| v == 100));
}
#[test]
fn intra_dc_asymmetric() {
let top = [200i16; 4];
let left = [0i16; 4];
let mut out = [0i16; 16];
av1_intra_dc(&top, &left, 4, &mut out);
assert!(out.iter().all(|&v| v == 100));
}
#[test]
fn intra_v_prediction() {
let top = [10i16, 20, 30, 40];
let mut out = [0i16; 16];
av1_intra_v(&top, 4, &mut out);
for row in 0..4 {
assert_eq!(out[row * 4], 10);
assert_eq!(out[row * 4 + 1], 20);
assert_eq!(out[row * 4 + 2], 30);
assert_eq!(out[row * 4 + 3], 40);
}
}
#[test]
fn intra_h_prediction() {
let left = [10i16, 20, 30, 40];
let mut out = [0i16; 16];
av1_intra_h(&left, 4, &mut out);
for row in 0..4 {
let expected = left[row];
assert!(out[row * 4..row * 4 + 4].iter().all(|&v| v == expected));
}
}
#[test]
fn intra_paeth_prediction() {
let top = [100i16, 120, 140, 160];
let left = [100i16, 110, 120, 130];
let top_left = 100i16;
let mut out = [0i16; 16];
av1_intra_paeth(&top, &left, top_left, 4, &mut out);
assert_eq!(out[0], 100);
assert!(out.iter().all(|&v| (100..=160).contains(&v)));
}
#[test]
fn inverse_dct_4x4_identity() {
let coeffs = [0i32; 16];
let mut out = [0i32; 16];
av1_inverse_dct_4x4(&coeffs, &mut out);
assert!(out.iter().all(|&v| v == 0));
}
#[test]
fn inverse_dct_4x4_dc_only() {
let mut coeffs = [0i32; 16];
coeffs[0] = 64;
let mut out = [0i32; 16];
av1_inverse_dct_4x4(&coeffs, &mut out);
let dc = out[0];
assert!(out.iter().all(|&v| v == dc));
assert!(dc > 0);
}
#[test]
fn smooth_prediction_produces_gradient() {
let top = [200i16; 4];
let left = [50i16; 4];
let mut out = [0i16; 16];
av1_intra_smooth(&top, &left, 200, 50, 4, &mut out);
assert!(out[0] > 100); assert!(out[15] > 100); }
#[test]
fn video_decoder_trait_codec() {
let dec = Av1Decoder::new();
assert_eq!(dec.codec(), VideoCodec::Av1);
}
}