use super::hevc_cabac::CabacDecoder;
use super::hevc_decoder::{HevcSliceType, HevcSps};
use super::hevc_syntax::HevcSliceCabacState;
#[derive(Debug, Clone)]
pub struct HevcReferencePicture {
pub poc: i32,
pub luma: Vec<u16>,
pub cb: Vec<u16>,
pub cr: Vec<u16>,
pub width: usize,
pub height: usize,
pub is_long_term: bool,
}
#[derive(Debug)]
pub struct HevcDpb {
pictures: Vec<HevcReferencePicture>,
max_size: usize,
}
impl HevcDpb {
pub fn new(max_size: usize) -> Self {
Self {
pictures: Vec::new(),
max_size: max_size.max(1),
}
}
pub fn add(&mut self, pic: HevcReferencePicture) {
if self.pictures.len() >= self.max_size {
self.bump();
}
self.pictures.push(pic);
}
pub fn get_by_poc(&self, poc: i32) -> Option<&HevcReferencePicture> {
self.pictures.iter().find(|p| p.poc == poc)
}
pub fn mark_unused(&mut self, poc: i32) {
self.pictures.retain(|p| p.poc != poc);
}
pub fn bump(&mut self) {
if self.pictures.is_empty() {
return;
}
let min_idx = self
.pictures
.iter()
.enumerate()
.min_by_key(|(_, p)| p.poc)
.map(|(i, _)| i)
.unwrap_or(0);
self.pictures.remove(min_idx);
}
pub fn clear(&mut self) {
self.pictures.clear();
}
pub fn len(&self) -> usize {
self.pictures.len()
}
pub fn is_empty(&self) -> bool {
self.pictures.is_empty()
}
pub fn max_size(&self) -> usize {
self.max_size
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct HevcMv {
pub x: i16,
pub y: i16,
}
impl HevcMv {
pub fn from_fullpel(x: i16, y: i16) -> Self {
Self { x: x * 4, y: y * 4 }
}
pub fn add(self, other: HevcMv) -> HevcMv {
HevcMv {
x: self.x.saturating_add(other.x),
y: self.y.saturating_add(other.y),
}
}
pub fn negate(self) -> HevcMv {
HevcMv {
x: self.x.saturating_neg(),
y: self.y.saturating_neg(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct HevcMvField {
pub mv: [HevcMv; 2],
pub ref_idx: [i8; 2],
pub pred_flag: [bool; 2],
}
impl HevcMvField {
pub fn unavailable() -> Self {
Self {
mv: [HevcMv::default(); 2],
ref_idx: [-1, -1],
pred_flag: [false, false],
}
}
pub fn is_available(&self) -> bool {
self.pred_flag[0] || self.pred_flag[1]
}
}
const HEVC_LUMA_FILTER: [[i16; 8]; 4] = [
[0, 0, 0, 64, 0, 0, 0, 0], [-1, 4, -10, 58, 17, -5, 1, 0], [-1, 4, -11, 40, 40, -11, 4, -1], [0, 1, -5, 17, 58, -10, 4, -1], ];
#[inline(always)]
fn ref_sample(pic: &HevcReferencePicture, x: i32, y: i32) -> i16 {
let cx = x.clamp(0, pic.width as i32 - 1) as usize;
let cy = y.clamp(0, pic.height as i32 - 1) as usize;
pic.luma[cy * pic.width + cx] as i16
}
#[inline(always)]
fn mc_in_bounds(pic: &HevcReferencePicture, int_x: i32, int_y: i32, bw: usize, bh: usize) -> bool {
int_x - 3 >= 0
&& int_y - 3 >= 0
&& (int_x + bw as i32 + 4) <= pic.width as i32
&& (int_y + bh as i32 + 4) <= pic.height as i32
}
#[inline(always)]
fn filter_h_row_inbounds(
src: &[u16],
src_offset: usize,
filter: &[i16; 8],
out: &mut [i16],
out_offset: usize,
count: usize,
) {
for col in 0..count {
let base = src_offset + col;
let sum = src[base] as i32 * filter[0] as i32
+ src[base + 1] as i32 * filter[1] as i32
+ src[base + 2] as i32 * filter[2] as i32
+ src[base + 3] as i32 * filter[3] as i32
+ src[base + 4] as i32 * filter[4] as i32
+ src[base + 5] as i32 * filter[5] as i32
+ src[base + 6] as i32 * filter[6] as i32
+ src[base + 7] as i32 * filter[7] as i32;
out[out_offset + col] = ((sum + 32) >> 6) as i16;
}
}
#[inline(always)]
fn filter_h_row_inbounds_i32(
src: &[u16],
src_offset: usize,
filter: &[i16; 8],
out: &mut [i32],
out_offset: usize,
count: usize,
) {
for col in 0..count {
let base = src_offset + col;
let sum = src[base] as i32 * filter[0] as i32
+ src[base + 1] as i32 * filter[1] as i32
+ src[base + 2] as i32 * filter[2] as i32
+ src[base + 3] as i32 * filter[3] as i32
+ src[base + 4] as i32 * filter[4] as i32
+ src[base + 5] as i32 * filter[5] as i32
+ src[base + 6] as i32 * filter[6] as i32
+ src[base + 7] as i32 * filter[7] as i32;
out[out_offset + col] = sum;
}
}
#[cfg(target_arch = "aarch64")]
#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
mod neon_mc {
use std::arch::aarch64::*;
#[target_feature(enable = "neon")]
#[inline]
pub unsafe fn filter_h8_neon(src: *const u16, filter: &[i16; 8]) -> int16x8_t {
let f0 = vdupq_n_s16(filter[0]);
let f1 = vdupq_n_s16(filter[1]);
let f2 = vdupq_n_s16(filter[2]);
let f3 = vdupq_n_s16(filter[3]);
let f4 = vdupq_n_s16(filter[4]);
let f5 = vdupq_n_s16(filter[5]);
let f6 = vdupq_n_s16(filter[6]);
let f7 = vdupq_n_s16(filter[7]);
let s0 = vreinterpretq_s16_u16(vld1q_u16(src));
let s1 = vreinterpretq_s16_u16(vld1q_u16(src.add(1)));
let s2 = vreinterpretq_s16_u16(vld1q_u16(src.add(2)));
let s3 = vreinterpretq_s16_u16(vld1q_u16(src.add(3)));
let s4 = vreinterpretq_s16_u16(vld1q_u16(src.add(4)));
let s5 = vreinterpretq_s16_u16(vld1q_u16(src.add(5)));
let s6 = vreinterpretq_s16_u16(vld1q_u16(src.add(6)));
let s7 = vreinterpretq_s16_u16(vld1q_u16(src.add(7)));
let mut acc = vmulq_s16(s0, f0);
acc = vmlaq_s16(acc, s1, f1);
acc = vmlaq_s16(acc, s2, f2);
acc = vmlaq_s16(acc, s3, f3);
acc = vmlaq_s16(acc, s4, f4);
acc = vmlaq_s16(acc, s5, f5);
acc = vmlaq_s16(acc, s6, f6);
acc = vmlaq_s16(acc, s7, f7);
let round = vdupq_n_s16(32);
vshrq_n_s16(vaddq_s16(acc, round), 6)
}
}
#[cfg(target_arch = "x86_64")]
#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
mod sse2_mc {
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[target_feature(enable = "sse2")]
#[inline]
pub unsafe fn filter_h8_sse2(src: *const u16, filter: &[i16; 8]) -> __m128i {
let f0 = _mm_set1_epi16(filter[0]);
let f1 = _mm_set1_epi16(filter[1]);
let f2 = _mm_set1_epi16(filter[2]);
let f3 = _mm_set1_epi16(filter[3]);
let f4 = _mm_set1_epi16(filter[4]);
let f5 = _mm_set1_epi16(filter[5]);
let f6 = _mm_set1_epi16(filter[6]);
let f7 = _mm_set1_epi16(filter[7]);
let s0 = _mm_loadu_si128(src as *const __m128i);
let s1 = _mm_loadu_si128(src.add(1) as *const __m128i);
let s2 = _mm_loadu_si128(src.add(2) as *const __m128i);
let s3 = _mm_loadu_si128(src.add(3) as *const __m128i);
let s4 = _mm_loadu_si128(src.add(4) as *const __m128i);
let s5 = _mm_loadu_si128(src.add(5) as *const __m128i);
let s6 = _mm_loadu_si128(src.add(6) as *const __m128i);
let s7 = _mm_loadu_si128(src.add(7) as *const __m128i);
let mut acc = _mm_mullo_epi16(s0, f0);
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s1, f1));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s2, f2));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s3, f3));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s4, f4));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s5, f5));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s6, f6));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(s7, f7));
let round = _mm_set1_epi16(32);
_mm_srai_epi16(_mm_add_epi16(acc, round), 6)
}
#[target_feature(enable = "sse2")]
#[inline]
pub unsafe fn filter_v8_sse2(src: *const u16, stride: usize, filter: &[i16; 8]) -> __m128i {
let r0 = _mm_loadu_si128(src as *const __m128i);
let r1 = _mm_loadu_si128(src.add(stride) as *const __m128i);
let r2 = _mm_loadu_si128(src.add(2 * stride) as *const __m128i);
let r3 = _mm_loadu_si128(src.add(3 * stride) as *const __m128i);
let r4 = _mm_loadu_si128(src.add(4 * stride) as *const __m128i);
let r5 = _mm_loadu_si128(src.add(5 * stride) as *const __m128i);
let r6 = _mm_loadu_si128(src.add(6 * stride) as *const __m128i);
let r7 = _mm_loadu_si128(src.add(7 * stride) as *const __m128i);
let f0 = _mm_set1_epi16(filter[0]);
let f1 = _mm_set1_epi16(filter[1]);
let f2 = _mm_set1_epi16(filter[2]);
let f3 = _mm_set1_epi16(filter[3]);
let f4 = _mm_set1_epi16(filter[4]);
let f5 = _mm_set1_epi16(filter[5]);
let f6 = _mm_set1_epi16(filter[6]);
let f7 = _mm_set1_epi16(filter[7]);
let mut acc = _mm_mullo_epi16(r0, f0);
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r1, f1));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r2, f2));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r3, f3));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r4, f4));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r5, f5));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r6, f6));
acc = _mm_add_epi16(acc, _mm_mullo_epi16(r7, f7));
let round = _mm_set1_epi16(32);
_mm_srai_epi16(_mm_add_epi16(acc, round), 6)
}
}
#[allow(unsafe_code, clippy::too_many_arguments)]
pub fn hevc_mc_luma(
ref_pic: &HevcReferencePicture,
x: i32,
y: i32,
mv: HevcMv,
block_w: usize,
block_h: usize,
output: &mut [i16],
) {
debug_assert!(output.len() >= block_w * block_h);
let frac_x = ((mv.x as i32) & 3) as usize;
let frac_y = ((mv.y as i32) & 3) as usize;
let int_x = x + (mv.x as i32 >> 2);
let int_y = y + (mv.y as i32 >> 2);
let filter_h = &HEVC_LUMA_FILTER[frac_x];
let filter_v = &HEVC_LUMA_FILTER[frac_y];
if frac_x == 0 && frac_y == 0 {
if mc_in_bounds(ref_pic, int_x, int_y, block_w, block_h) {
let ux = int_x as usize;
let uy = int_y as usize;
let stride = ref_pic.width;
for row in 0..block_h {
let src_start = (uy + row) * stride + ux;
let src_row = &ref_pic.luma[src_start..src_start + block_w];
let dst = &mut output[row * block_w..(row + 1) * block_w];
for (d, &s) in dst.iter_mut().zip(src_row.iter()) {
*d = s as i16;
}
}
} else {
for row in 0..block_h {
for col in 0..block_w {
output[row * block_w + col] =
ref_sample(ref_pic, int_x + col as i32, int_y + row as i32);
}
}
}
return;
}
let inbounds = mc_in_bounds(ref_pic, int_x, int_y, block_w, block_h);
if frac_y == 0 {
if inbounds {
let ux = (int_x - 3) as usize;
let uy = int_y as usize;
let stride = ref_pic.width;
#[cfg(target_arch = "aarch64")]
{
let mut row = 0;
while row < block_h {
let src_base = (uy + row) * stride + ux;
let mut col = 0;
while col + 8 <= block_w {
unsafe {
let src_ptr = ref_pic.luma.as_ptr().add(src_base + col);
let result = neon_mc::filter_h8_neon(src_ptr, filter_h);
let out_off = row * block_w + col;
std::arch::aarch64::vst1q_s16(output.as_mut_ptr().add(out_off), result);
}
col += 8;
}
if col < block_w {
filter_h_row_inbounds(
&ref_pic.luma,
src_base + col,
filter_h,
output,
row * block_w + col,
block_w - col,
);
}
row += 1;
}
}
#[cfg(target_arch = "x86_64")]
{
for row in 0..block_h {
let src_base = (uy + row) * stride + ux;
let mut col = 0;
while col + 8 <= block_w {
unsafe {
let src_ptr = ref_pic.luma.as_ptr().add(src_base + col);
let result = sse2_mc::filter_h8_sse2(src_ptr, filter_h);
std::arch::x86_64::_mm_storeu_si128(
output.as_mut_ptr().add(row * block_w + col)
as *mut std::arch::x86_64::__m128i,
result,
);
}
col += 8;
}
if col < block_w {
filter_h_row_inbounds(
&ref_pic.luma,
src_base + col,
filter_h,
output,
row * block_w + col,
block_w - col,
);
}
}
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
for row in 0..block_h {
let src_base = (uy + row) * stride + ux;
filter_h_row_inbounds(
&ref_pic.luma,
src_base,
filter_h,
output,
row * block_w,
block_w,
);
}
}
} else {
for row in 0..block_h {
for col in 0..block_w {
let sy = int_y + row as i32;
let mut sum = 0i32;
for k in 0..8 {
let sx = int_x + col as i32 + k as i32 - 3;
sum += ref_sample(ref_pic, sx, sy) as i32 * filter_h[k] as i32;
}
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
}
}
}
return;
}
if frac_x == 0 {
if inbounds {
let ux = int_x as usize;
let stride = ref_pic.width;
#[cfg(target_arch = "aarch64")]
{
for row in 0..block_h {
let base_y = (int_y + row as i32 - 3) as usize;
let base_idx = base_y * stride + ux;
let mut col = 0;
while col + 8 <= block_w {
unsafe {
use std::arch::aarch64::*;
let f0 = vdupq_n_s16(filter_v[0]);
let f1 = vdupq_n_s16(filter_v[1]);
let f2 = vdupq_n_s16(filter_v[2]);
let f3 = vdupq_n_s16(filter_v[3]);
let f4 = vdupq_n_s16(filter_v[4]);
let f5 = vdupq_n_s16(filter_v[5]);
let f6 = vdupq_n_s16(filter_v[6]);
let f7 = vdupq_n_s16(filter_v[7]);
let p = ref_pic.luma.as_ptr().add(base_idx + col);
let s0 = vreinterpretq_s16_u16(vld1q_u16(p));
let s1 = vreinterpretq_s16_u16(vld1q_u16(p.add(stride)));
let s2 = vreinterpretq_s16_u16(vld1q_u16(p.add(2 * stride)));
let s3 = vreinterpretq_s16_u16(vld1q_u16(p.add(3 * stride)));
let s4 = vreinterpretq_s16_u16(vld1q_u16(p.add(4 * stride)));
let s5 = vreinterpretq_s16_u16(vld1q_u16(p.add(5 * stride)));
let s6 = vreinterpretq_s16_u16(vld1q_u16(p.add(6 * stride)));
let s7 = vreinterpretq_s16_u16(vld1q_u16(p.add(7 * stride)));
let mut acc = vmulq_s16(s0, f0);
acc = vmlaq_s16(acc, s1, f1);
acc = vmlaq_s16(acc, s2, f2);
acc = vmlaq_s16(acc, s3, f3);
acc = vmlaq_s16(acc, s4, f4);
acc = vmlaq_s16(acc, s5, f5);
acc = vmlaq_s16(acc, s6, f6);
acc = vmlaq_s16(acc, s7, f7);
let round = vdupq_n_s16(32);
let result = vshrq_n_s16(vaddq_s16(acc, round), 6);
vst1q_s16(output.as_mut_ptr().add(row * block_w + col), result);
}
col += 8;
}
while col < block_w {
let bi = base_idx + col;
let sum = ref_pic.luma[bi] as i32 * filter_v[0] as i32
+ ref_pic.luma[bi + stride] as i32 * filter_v[1] as i32
+ ref_pic.luma[bi + 2 * stride] as i32 * filter_v[2] as i32
+ ref_pic.luma[bi + 3 * stride] as i32 * filter_v[3] as i32
+ ref_pic.luma[bi + 4 * stride] as i32 * filter_v[4] as i32
+ ref_pic.luma[bi + 5 * stride] as i32 * filter_v[5] as i32
+ ref_pic.luma[bi + 6 * stride] as i32 * filter_v[6] as i32
+ ref_pic.luma[bi + 7 * stride] as i32 * filter_v[7] as i32;
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
col += 1;
}
}
}
#[cfg(target_arch = "x86_64")]
{
for row in 0..block_h {
let base_y = (int_y + row as i32 - 3) as usize;
let base_idx = base_y * stride + ux;
let mut col = 0;
while col + 8 <= block_w {
unsafe {
let src_ptr = ref_pic.luma.as_ptr().add(base_idx + col);
let result = sse2_mc::filter_v8_sse2(src_ptr, stride, filter_v);
std::arch::x86_64::_mm_storeu_si128(
output.as_mut_ptr().add(row * block_w + col)
as *mut std::arch::x86_64::__m128i,
result,
);
}
col += 8;
}
while col < block_w {
let bi = base_idx + col;
let sum = ref_pic.luma[bi] as i32 * filter_v[0] as i32
+ ref_pic.luma[bi + stride] as i32 * filter_v[1] as i32
+ ref_pic.luma[bi + 2 * stride] as i32 * filter_v[2] as i32
+ ref_pic.luma[bi + 3 * stride] as i32 * filter_v[3] as i32
+ ref_pic.luma[bi + 4 * stride] as i32 * filter_v[4] as i32
+ ref_pic.luma[bi + 5 * stride] as i32 * filter_v[5] as i32
+ ref_pic.luma[bi + 6 * stride] as i32 * filter_v[6] as i32
+ ref_pic.luma[bi + 7 * stride] as i32 * filter_v[7] as i32;
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
col += 1;
}
}
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
for row in 0..block_h {
for col in 0..block_w {
let base_y = (int_y + row as i32 - 3) as usize;
let base_idx = base_y * stride + ux + col;
let sum = ref_pic.luma[base_idx] as i32 * filter_v[0] as i32
+ ref_pic.luma[base_idx + stride] as i32 * filter_v[1] as i32
+ ref_pic.luma[base_idx + 2 * stride] as i32 * filter_v[2] as i32
+ ref_pic.luma[base_idx + 3 * stride] as i32 * filter_v[3] as i32
+ ref_pic.luma[base_idx + 4 * stride] as i32 * filter_v[4] as i32
+ ref_pic.luma[base_idx + 5 * stride] as i32 * filter_v[5] as i32
+ ref_pic.luma[base_idx + 6 * stride] as i32 * filter_v[6] as i32
+ ref_pic.luma[base_idx + 7 * stride] as i32 * filter_v[7] as i32;
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
}
}
}
} else {
for row in 0..block_h {
for col in 0..block_w {
let sx = int_x + col as i32;
let mut sum = 0i32;
for k in 0..8 {
let sy = int_y + row as i32 + k as i32 - 3;
sum += ref_sample(ref_pic, sx, sy) as i32 * filter_v[k] as i32;
}
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
}
}
}
return;
}
let ext_h = block_h + 7; let tmp_size = ext_h * block_w;
let mut tmp_buf = [0i32; 71 * 64];
let tmp = &mut tmp_buf[..tmp_size];
if inbounds {
let ux = (int_x - 3) as usize;
let stride = ref_pic.width;
for row in 0..ext_h {
let sy = (int_y + row as i32 - 3) as usize;
let src_base = sy * stride + ux;
filter_h_row_inbounds_i32(
&ref_pic.luma,
src_base,
filter_h,
tmp,
row * block_w,
block_w,
);
}
} else {
for row in 0..ext_h {
let sy = int_y + row as i32 - 3;
for col in 0..block_w {
let mut sum = 0i32;
for k in 0..8 {
let sx = int_x + col as i32 + k as i32 - 3;
sum += ref_sample(ref_pic, sx, sy) as i32 * filter_h[k] as i32;
}
tmp[row * block_w + col] = sum;
}
}
}
for row in 0..block_h {
for col in 0..block_w {
let mut sum = 0i64;
for k in 0..8 {
sum += tmp[(row + k) * block_w + col] as i64 * filter_v[k] as i64;
}
output[row * block_w + col] = ((sum + 2048) >> 12) as i16;
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn hevc_mc_chroma(
chroma_plane: &[u16],
chroma_w: usize,
chroma_h: usize,
x: i32,
y: i32,
mv: HevcMv,
block_w: usize,
block_h: usize,
output: &mut [i16],
) {
debug_assert!(output.len() >= block_w * block_h);
let frac_x = ((mv.x as i32) & 7) as usize;
let frac_y = ((mv.y as i32) & 7) as usize;
let int_x = x + (mv.x as i32 >> 3);
let int_y = y + (mv.y as i32 >> 3);
let filter_h = &super::hevc_filter::HEVC_CHROMA_FILTER[frac_x];
let filter_v = &super::hevc_filter::HEVC_CHROMA_FILTER[frac_y];
let clamp_x = |cx: i32| -> usize { cx.clamp(0, chroma_w as i32 - 1) as usize };
let clamp_y = |cy: i32| -> usize { cy.clamp(0, chroma_h as i32 - 1) as usize };
let sample =
|cx: i32, cy: i32| -> i16 { chroma_plane[clamp_y(cy) * chroma_w + clamp_x(cx)] as i16 };
if frac_x == 0 && frac_y == 0 {
for row in 0..block_h {
for col in 0..block_w {
output[row * block_w + col] = sample(int_x + col as i32, int_y + row as i32);
}
}
} else if frac_y == 0 {
for row in 0..block_h {
for col in 0..block_w {
let sy = int_y + row as i32;
let mut sum = 0i32;
for k in 0..4 {
sum +=
sample(int_x + col as i32 + k as i32 - 1, sy) as i32 * filter_h[k] as i32;
}
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
}
}
} else if frac_x == 0 {
for row in 0..block_h {
for col in 0..block_w {
let sx = int_x + col as i32;
let mut sum = 0i32;
for k in 0..4 {
sum +=
sample(sx, int_y + row as i32 + k as i32 - 1) as i32 * filter_v[k] as i32;
}
output[row * block_w + col] = ((sum + 32) >> 6) as i16;
}
}
} else {
let ext_h = block_h + 3;
let mut tmp = vec![0i32; ext_h * block_w];
for row in 0..ext_h {
let sy = int_y + row as i32 - 1;
for col in 0..block_w {
let mut sum = 0i32;
for k in 0..4 {
sum +=
sample(int_x + col as i32 + k as i32 - 1, sy) as i32 * filter_h[k] as i32;
}
tmp[row * block_w + col] = sum;
}
}
for row in 0..block_h {
for col in 0..block_w {
let mut sum = 0i64;
for k in 0..4 {
sum += tmp[(row + k) * block_w + col] as i64 * filter_v[k] as i64;
}
output[row * block_w + col] = ((sum + 2048) >> 12) as i16;
}
}
}
}
#[allow(unsafe_code)]
pub fn hevc_bipred_average(pred_l0: &[i16], pred_l1: &[i16], output: &mut [u8], size: usize) {
debug_assert!(pred_l0.len() >= size);
debug_assert!(pred_l1.len() >= size);
debug_assert!(output.len() >= size);
#[cfg(target_arch = "aarch64")]
{
let mut i = 0;
while i + 8 <= size {
unsafe {
use std::arch::aarch64::*;
let l0 = vld1q_s16(pred_l0.as_ptr().add(i));
let l1 = vld1q_s16(pred_l1.as_ptr().add(i));
let avg = vrhaddq_s16(l0, l1);
let clamped = vqmovun_s16(avg);
std::ptr::copy_nonoverlapping(
&clamped as *const uint8x8_t as *const u8,
output.as_mut_ptr().add(i),
8,
);
}
i += 8;
}
while i < size {
let avg = (pred_l0[i] as i32 + pred_l1[i] as i32 + 1) >> 1;
output[i] = avg.clamp(0, 255) as u8;
i += 1;
}
}
#[cfg(target_arch = "x86_64")]
{
let mut i = 0;
while i + 8 <= size {
unsafe {
use std::arch::x86_64::*;
let l0 = _mm_loadu_si128(pred_l0.as_ptr().add(i) as *const __m128i);
let l1 = _mm_loadu_si128(pred_l1.as_ptr().add(i) as *const __m128i);
let sum = _mm_add_epi16(l0, l1);
let one = _mm_set1_epi16(1);
let avg = _mm_srai_epi16(_mm_add_epi16(sum, one), 1);
let clamped = _mm_packus_epi16(avg, avg); std::ptr::copy_nonoverlapping(
&clamped as *const __m128i as *const u8,
output.as_mut_ptr().add(i),
8,
);
}
i += 8;
}
while i < size {
let avg = (pred_l0[i] as i32 + pred_l1[i] as i32 + 1) >> 1;
output[i] = avg.clamp(0, 255) as u8;
i += 1;
}
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
for i in 0..size {
let avg = (pred_l0[i] as i32 + pred_l1[i] as i32 + 1) >> 1;
output[i] = avg.clamp(0, 255) as u8;
}
}
}
#[allow(unsafe_code)]
pub fn hevc_unipred_clip(pred: &[i16], output: &mut [u8], size: usize) {
debug_assert!(pred.len() >= size);
debug_assert!(output.len() >= size);
#[cfg(target_arch = "aarch64")]
{
let mut i = 0;
while i + 8 <= size {
unsafe {
use std::arch::aarch64::*;
let v = vld1q_s16(pred.as_ptr().add(i));
let clamped = vqmovun_s16(v);
std::ptr::copy_nonoverlapping(
&clamped as *const uint8x8_t as *const u8,
output.as_mut_ptr().add(i),
8,
);
}
i += 8;
}
while i < size {
output[i] = (pred[i] as i32).clamp(0, 255) as u8;
i += 1;
}
}
#[cfg(target_arch = "x86_64")]
{
let mut i = 0;
while i + 8 <= size {
unsafe {
use std::arch::x86_64::*;
let v = _mm_loadu_si128(pred.as_ptr().add(i) as *const __m128i);
let clamped = _mm_packus_epi16(v, v); std::ptr::copy_nonoverlapping(
&clamped as *const __m128i as *const u8,
output.as_mut_ptr().add(i),
8,
);
}
i += 8;
}
while i < size {
output[i] = (pred[i] as i32).clamp(0, 255) as u8;
i += 1;
}
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
for i in 0..size {
output[i] = (pred[i] as i32).clamp(0, 255) as u8;
}
}
}
const MIN_PU_SIZE: usize = 4;
pub fn build_merge_candidates(
mv_field: &[HevcMvField],
pic_width_in_min_pu: usize,
x: usize,
y: usize,
block_w: usize,
block_h: usize,
) -> Vec<HevcMvField> {
let mut candidates: Vec<HevcMvField> = Vec::with_capacity(5);
let max_candidates = 5usize;
let pu_x = x / MIN_PU_SIZE;
let pu_y = y / MIN_PU_SIZE;
let pu_w = block_w / MIN_PU_SIZE;
let pu_h = block_h / MIN_PU_SIZE;
let get = |px: usize, py: usize| -> Option<HevcMvField> {
if px < pic_width_in_min_pu {
let idx = py * pic_width_in_min_pu + px;
if idx < mv_field.len() {
let f = mv_field[idx];
if f.is_available() {
return Some(f);
}
}
}
None
};
if pu_x > 0
&& let Some(c) = get(pu_x - 1, pu_y + pu_h - 1)
{
candidates.push(c);
}
if candidates.len() < max_candidates
&& pu_y > 0
&& let Some(c) = get(pu_x + pu_w - 1, pu_y - 1)
&& (candidates.is_empty() || candidates[candidates.len() - 1] != c)
{
candidates.push(c);
}
if candidates.len() < max_candidates
&& pu_y > 0
&& let Some(c) = get(pu_x + pu_w, pu_y - 1)
&& candidates.last() != Some(&c)
{
candidates.push(c);
}
if candidates.len() < max_candidates
&& pu_x > 0
&& let Some(c) = get(pu_x - 1, pu_y + pu_h)
&& candidates.last() != Some(&c)
{
candidates.push(c);
}
if candidates.len() < max_candidates
&& pu_x > 0
&& pu_y > 0
&& let Some(c) = get(pu_x - 1, pu_y - 1)
&& candidates.last() != Some(&c)
{
candidates.push(c);
}
while candidates.len() < max_candidates {
candidates.push(HevcMvField {
mv: [HevcMv::default(); 2],
ref_idx: [0, -1],
pred_flag: [true, false],
});
}
candidates
}
pub fn parse_merge_idx(cabac: &mut CabacDecoder<'_>, max_merge_cand: u32) -> u32 {
if max_merge_cand <= 1 {
return 0;
}
let mut idx = 0u32;
if cabac.decode_bypass() {
idx += 1;
while idx < max_merge_cand - 1 {
if cabac.decode_bypass() {
idx += 1;
} else {
break;
}
}
}
idx
}
pub fn build_amvp_candidates(
mv_field: &[HevcMvField],
pic_width_in_min_pu: usize,
x: usize,
y: usize,
ref_idx: i8,
list: usize,
) -> [HevcMv; 2] {
let mut cands = [HevcMv::default(); 2];
let mut count = 0usize;
let pu_x = x / MIN_PU_SIZE;
let pu_y = y / MIN_PU_SIZE;
let get = |px: usize, py: usize| -> Option<HevcMv> {
if px < pic_width_in_min_pu {
let idx = py * pic_width_in_min_pu + px;
if idx < mv_field.len() {
let f = &mv_field[idx];
if f.pred_flag[list] && f.ref_idx[list] == ref_idx {
return Some(f.mv[list]);
}
let other = 1 - list;
if f.pred_flag[other] && f.ref_idx[other] == ref_idx {
return Some(f.mv[other]);
}
}
}
None
};
if pu_x > 0 {
if let Some(mv) = get(pu_x - 1, pu_y) {
cands[count] = mv;
count += 1;
} else if pu_y > 0
&& let Some(mv) = get(pu_x - 1, pu_y - 1)
{
cands[count] = mv;
count += 1;
}
}
if count < 2
&& pu_y > 0
&& let Some(mv) = get(pu_x, pu_y - 1)
&& (count == 0 || cands[0] != mv)
{
cands[count] = mv;
count += 1;
}
if count < 2
&& pu_x > 0
&& pu_y > 0
&& let Some(mv) = get(pu_x - 1, pu_y - 1)
&& (count == 0 || cands[0] != mv)
{
cands[count] = mv;
}
cands
}
pub fn parse_mvd(cabac: &mut CabacDecoder<'_>) -> HevcMv {
let abs_x_gt0 = cabac.decode_bypass();
let abs_y_gt0 = cabac.decode_bypass();
let abs_x_gt1 = if abs_x_gt0 {
cabac.decode_bypass()
} else {
false
};
let abs_y_gt1 = if abs_y_gt0 {
cabac.decode_bypass()
} else {
false
};
let mut abs_x: i16 = 0;
if abs_x_gt0 {
abs_x = 1;
if abs_x_gt1 {
abs_x += 1 + cabac.decode_eg(1) as i16;
}
}
let mut abs_y: i16 = 0;
if abs_y_gt0 {
abs_y = 1;
if abs_y_gt1 {
abs_y += 1 + cabac.decode_eg(1) as i16;
}
}
let sign_x = if abs_x_gt0 {
cabac.decode_bypass()
} else {
false
};
let sign_y = if abs_y_gt0 {
cabac.decode_bypass()
} else {
false
};
HevcMv {
x: if sign_x { -abs_x } else { abs_x },
y: if sign_y { -abs_y } else { abs_y },
}
}
#[allow(clippy::too_many_arguments)]
pub fn parse_inter_prediction(
state: &mut HevcSliceCabacState<'_>,
sps: &HevcSps,
slice_type: HevcSliceType,
mv_field: &[HevcMvField],
pic_width_in_min_pu: usize,
x: usize,
y: usize,
cu_size: usize,
) -> HevcMvField {
let merge_flag = state.cabac.decode_bypass();
if merge_flag {
let max_merge = 5u32;
let merge_idx = parse_merge_idx(&mut state.cabac, max_merge);
let candidates =
build_merge_candidates(mv_field, pic_width_in_min_pu, x, y, cu_size, cu_size);
let idx = (merge_idx as usize).min(candidates.len().saturating_sub(1));
return candidates[idx];
}
match slice_type {
HevcSliceType::P => {
let ref_idx_l0 = if sps.num_short_term_ref_pic_sets > 1 {
let mut idx = 0i8;
while (idx as u8) < sps.num_short_term_ref_pic_sets.saturating_sub(1) {
if state.cabac.decode_bypass() {
idx += 1;
} else {
break;
}
}
idx
} else {
0i8
};
let mvd = parse_mvd(&mut state.cabac);
let amvp = build_amvp_candidates(mv_field, pic_width_in_min_pu, x, y, ref_idx_l0, 0);
let mvp_flag = state.cabac.decode_bypass();
let predictor = if mvp_flag { amvp[1] } else { amvp[0] };
HevcMvField {
mv: [predictor.add(mvd), HevcMv::default()],
ref_idx: [ref_idx_l0, -1],
pred_flag: [true, false],
}
}
HevcSliceType::B => {
let ref_idx_l0 = 0i8;
let ref_idx_l1 = 0i8;
let mvd_l0 = parse_mvd(&mut state.cabac);
let mvd_l1 = parse_mvd(&mut state.cabac);
let amvp_l0 = build_amvp_candidates(mv_field, pic_width_in_min_pu, x, y, ref_idx_l0, 0);
let amvp_l1 = build_amvp_candidates(mv_field, pic_width_in_min_pu, x, y, ref_idx_l1, 1);
let mvp0_flag = state.cabac.decode_bypass();
let mvp1_flag = state.cabac.decode_bypass();
let pred0 = if mvp0_flag { amvp_l0[1] } else { amvp_l0[0] };
let pred1 = if mvp1_flag { amvp_l1[1] } else { amvp_l1[0] };
HevcMvField {
mv: [pred0.add(mvd_l0), pred1.add(mvd_l1)],
ref_idx: [ref_idx_l0, ref_idx_l1],
pred_flag: [true, true],
}
}
HevcSliceType::I => {
HevcMvField::unavailable()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dpb_new_is_empty() {
let dpb = HevcDpb::new(5);
assert!(dpb.is_empty());
assert_eq!(dpb.len(), 0);
assert_eq!(dpb.max_size(), 5);
}
#[test]
fn dpb_add_and_get_by_poc() {
let mut dpb = HevcDpb::new(4);
dpb.add(HevcReferencePicture {
poc: 0,
luma: vec![128; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
dpb.add(HevcReferencePicture {
poc: 2,
luma: vec![200; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
assert_eq!(dpb.len(), 2);
assert!(dpb.get_by_poc(0).is_some());
assert_eq!(dpb.get_by_poc(0).unwrap().luma[0], 128);
assert!(dpb.get_by_poc(2).is_some());
assert!(dpb.get_by_poc(99).is_none());
}
#[test]
fn dpb_bump_removes_oldest() {
let mut dpb = HevcDpb::new(3);
for poc in 0..3 {
dpb.add(HevcReferencePicture {
poc,
luma: vec![poc as u16; 16],
cb: vec![128u16; 4],
cr: vec![128u16; 4],
width: 4,
height: 4,
is_long_term: false,
});
}
assert_eq!(dpb.len(), 3);
dpb.add(HevcReferencePicture {
poc: 5,
luma: vec![5; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
assert_eq!(dpb.len(), 3);
assert!(dpb.get_by_poc(0).is_none());
assert!(dpb.get_by_poc(1).is_some());
assert!(dpb.get_by_poc(5).is_some());
}
#[test]
fn dpb_mark_unused() {
let mut dpb = HevcDpb::new(4);
dpb.add(HevcReferencePicture {
poc: 10,
luma: vec![0; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
dpb.add(HevcReferencePicture {
poc: 20,
luma: vec![0; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
dpb.mark_unused(10);
assert_eq!(dpb.len(), 1);
assert!(dpb.get_by_poc(10).is_none());
assert!(dpb.get_by_poc(20).is_some());
}
#[test]
fn dpb_clear() {
let mut dpb = HevcDpb::new(4);
for poc in 0..4 {
dpb.add(HevcReferencePicture {
poc,
luma: vec![0; 16],
cb: vec![128; 4],
cr: vec![128; 4],
width: 4,
height: 4,
is_long_term: false,
});
}
assert_eq!(dpb.len(), 4);
dpb.clear();
assert!(dpb.is_empty());
assert_eq!(dpb.len(), 0);
}
#[test]
fn mv_from_fullpel() {
let mv = HevcMv::from_fullpel(3, -5);
assert_eq!(mv.x, 12);
assert_eq!(mv.y, -20);
}
#[test]
fn mv_add() {
let a = HevcMv { x: 10, y: -4 };
let b = HevcMv { x: -3, y: 7 };
let c = a.add(b);
assert_eq!(c.x, 7);
assert_eq!(c.y, 3);
}
#[test]
fn mv_negate() {
let mv = HevcMv { x: 5, y: -8 };
let neg = mv.negate();
assert_eq!(neg.x, -5);
assert_eq!(neg.y, 8);
}
#[test]
fn mv_default_is_zero() {
let mv = HevcMv::default();
assert_eq!(mv.x, 0);
assert_eq!(mv.y, 0);
}
fn make_ref_pic(w: usize, h: usize, val: u8) -> HevcReferencePicture {
HevcReferencePicture {
poc: 0,
luma: vec![val as u16; w * h],
cb: vec![128u16; (w / 2) * (h / 2)],
cr: vec![128u16; (w / 2) * (h / 2)],
width: w,
height: h,
is_long_term: false,
}
}
#[test]
fn mc_luma_integer_pel() {
let pic = make_ref_pic(16, 16, 100);
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 2, 2, HevcMv { x: 0, y: 0 }, 4, 4, &mut out);
for v in &out {
assert_eq!(*v, 100);
}
}
#[test]
fn mc_luma_half_pel_uniform() {
let pic = make_ref_pic(32, 32, 80);
let mut out = vec![0i16; 8 * 8];
hevc_mc_luma(&pic, 4, 4, HevcMv { x: 2, y: 0 }, 8, 8, &mut out);
for v in &out {
assert_eq!(*v, 80);
}
}
#[test]
fn mc_luma_quarter_pel_uniform() {
let pic = make_ref_pic(32, 32, 60);
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 4, 4, HevcMv { x: 1, y: 0 }, 4, 4, &mut out);
for v in &out {
assert_eq!(*v, 60);
}
}
#[test]
fn mc_luma_three_quarter_pel_uniform() {
let pic = make_ref_pic(32, 32, 120);
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 4, 4, HevcMv { x: 3, y: 0 }, 4, 4, &mut out);
for v in &out {
assert_eq!(*v, 120);
}
}
#[test]
fn mc_luma_vertical_half_pel_uniform() {
let pic = make_ref_pic(32, 32, 90);
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 4, 4, HevcMv { x: 0, y: 2 }, 4, 4, &mut out);
for v in &out {
assert_eq!(*v, 90);
}
}
#[test]
fn mc_luma_diagonal_half_pel_uniform() {
let pic = make_ref_pic(32, 32, 70);
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 4, 4, HevcMv { x: 2, y: 2 }, 4, 4, &mut out);
for v in &out {
assert_eq!(*v, 70);
}
}
#[test]
fn mc_luma_gradient_horizontal() {
let w = 32usize;
let h = 16usize;
let mut luma = vec![0u16; w * h];
for row in 0..h {
for col in 0..w {
luma[row * w + col] = (col * 8).min(255) as u16;
}
}
let pic = HevcReferencePicture {
poc: 0,
luma,
cb: vec![128u16; (w / 2) * (h / 2)],
cr: vec![128u16; (w / 2) * (h / 2)],
width: w,
height: h,
is_long_term: false,
};
let mut out = vec![0i16; 4 * 4];
hevc_mc_luma(&pic, 4, 2, HevcMv { x: 0, y: 0 }, 4, 4, &mut out);
assert_eq!(out[0], 32);
assert_eq!(out[1], 40);
}
#[test]
fn bipred_average_uniform() {
let l0 = vec![100i16; 16];
let l1 = vec![200i16; 16];
let mut out = vec![0u8; 16];
hevc_bipred_average(&l0, &l1, &mut out, 16);
for v in &out {
assert_eq!(*v, 150);
}
}
#[test]
fn bipred_average_clamping() {
let l0 = vec![255i16; 4];
let l1 = vec![255i16; 4];
let mut out = vec![0u8; 4];
hevc_bipred_average(&l0, &l1, &mut out, 4);
for v in &out {
assert_eq!(*v, 255);
}
let l0n = vec![-10i16; 4];
let l1n = vec![-20i16; 4];
let mut outn = vec![255u8; 4];
hevc_bipred_average(&l0n, &l1n, &mut outn, 4);
for v in &outn {
assert_eq!(*v, 0);
}
}
#[test]
fn unipred_clip_basic() {
let pred = vec![128i16, -5, 300, 0];
let mut out = vec![0u8; 4];
hevc_unipred_clip(&pred, &mut out, 4);
assert_eq!(out[0], 128);
assert_eq!(out[1], 0);
assert_eq!(out[2], 255);
assert_eq!(out[3], 0);
}
#[test]
fn merge_candidates_no_neighbours() {
let field = vec![HevcMvField::unavailable(); 16 * 16];
let cands = build_merge_candidates(&field, 16, 0, 0, 8, 8);
assert_eq!(cands.len(), 5);
for c in &cands {
assert!(c.pred_flag[0]);
}
}
#[test]
fn merge_candidates_with_left_neighbour() {
let pw = 16usize; let mut field = vec![HevcMvField::unavailable(); pw * pw];
let left = HevcMvField {
mv: [HevcMv { x: 8, y: 4 }, HevcMv::default()],
ref_idx: [0, -1],
pred_flag: [true, false],
};
field[0] = left;
let cands = build_merge_candidates(&field, pw, 4, 0, 4, 4);
assert_eq!(cands.len(), 5);
assert_eq!(cands[0].mv[0].x, 8);
assert_eq!(cands[0].mv[0].y, 4);
}
#[test]
fn amvp_no_neighbours() {
let field = vec![HevcMvField::unavailable(); 16 * 16];
let cands = build_amvp_candidates(&field, 16, 4, 4, 0, 0);
assert_eq!(cands[0], HevcMv::default());
assert_eq!(cands[1], HevcMv::default());
}
#[test]
fn amvp_with_left_neighbour() {
let pw = 16usize;
let mut field = vec![HevcMvField::unavailable(); pw * pw];
let left = HevcMvField {
mv: [HevcMv { x: 12, y: -8 }, HevcMv::default()],
ref_idx: [0, -1],
pred_flag: [true, false],
};
field[pw] = left;
let cands = build_amvp_candidates(&field, pw, 4, 4, 0, 0);
assert_eq!(cands[0].x, 12);
assert_eq!(cands[0].y, -8);
}
#[test]
fn parse_mvd_zero() {
let data = [0x00u8; 16];
let mut cabac = CabacDecoder::new(&data);
let mv = parse_mvd(&mut cabac);
assert_eq!(mv.x, 0);
assert_eq!(mv.y, 0);
}
#[test]
fn parse_mvd_deterministic() {
let data = [0xFFu8; 32];
let mut cabac = CabacDecoder::new(&data);
let mv1 = parse_mvd(&mut cabac);
let mut cabac2 = CabacDecoder::new(&data);
let mv2 = parse_mvd(&mut cabac2);
assert_eq!(mv1, mv2);
}
#[test]
fn parse_merge_idx_single_candidate() {
let data = [0xFFu8; 8];
let mut cabac = CabacDecoder::new(&data);
let idx = parse_merge_idx(&mut cabac, 1);
assert_eq!(idx, 0);
}
#[test]
fn parse_merge_idx_zero_stream() {
let data = [0x00u8; 16];
let mut cabac = CabacDecoder::new(&data);
let idx = parse_merge_idx(&mut cabac, 5);
assert_eq!(idx, 0);
}
#[test]
fn mvfield_unavailable() {
let f = HevcMvField::unavailable();
assert!(!f.is_available());
assert_eq!(f.ref_idx[0], -1);
assert_eq!(f.ref_idx[1], -1);
}
#[test]
fn mvfield_available() {
let f = HevcMvField {
mv: [HevcMv { x: 1, y: 2 }, HevcMv::default()],
ref_idx: [0, -1],
pred_flag: [true, false],
};
assert!(f.is_available());
}
#[test]
fn parse_inter_prediction_p_slice_synthetic() {
let data = [0x55u8; 128];
let mut state = HevcSliceCabacState::new(&data, 26);
let sps = test_sps();
let field = vec![HevcMvField::unavailable(); 16 * 16];
let mvf = parse_inter_prediction(&mut state, &sps, HevcSliceType::P, &field, 16, 0, 0, 8);
assert!(mvf.pred_flag[0] || mvf.pred_flag[1]);
}
#[test]
fn parse_inter_prediction_b_slice_synthetic() {
let data = [0xAAu8; 128];
let mut state = HevcSliceCabacState::new(&data, 26);
let sps = test_sps();
let field = vec![HevcMvField::unavailable(); 16 * 16];
let mvf = parse_inter_prediction(&mut state, &sps, HevcSliceType::B, &field, 16, 0, 0, 8);
assert!(mvf.pred_flag[0] || mvf.pred_flag[1]);
}
#[test]
fn parse_inter_prediction_merge_mode() {
let data = [0xFFu8; 128];
let mut state = HevcSliceCabacState::new(&data, 26);
let sps = test_sps();
let field = vec![HevcMvField::unavailable(); 16 * 16];
let mvf = parse_inter_prediction(&mut state, &sps, HevcSliceType::P, &field, 16, 4, 4, 8);
assert!(mvf.pred_flag[0]);
}
#[test]
fn luma_filter_coefficients_sum() {
for (i, row) in HEVC_LUMA_FILTER.iter().enumerate() {
let sum: i16 = row.iter().sum();
assert_eq!(sum, 64, "filter row {i} sums to {sum}, expected 64");
}
}
fn test_sps() -> HevcSps {
HevcSps {
sps_id: 0,
vps_id: 0,
max_sub_layers: 1,
chroma_format_idc: 1,
pic_width: 64,
pic_height: 64,
bit_depth_luma: 8,
bit_depth_chroma: 8,
log2_max_pic_order_cnt: 4,
log2_min_cb_size: 3,
log2_diff_max_min_cb_size: 3,
log2_min_transform_size: 2,
log2_diff_max_min_transform_size: 3,
max_transform_hierarchy_depth_inter: 1,
max_transform_hierarchy_depth_intra: 1,
sample_adaptive_offset_enabled: false,
pcm_enabled: false,
num_short_term_ref_pic_sets: 0,
long_term_ref_pics_present: false,
sps_temporal_mvp_enabled: false,
strong_intra_smoothing_enabled: false,
}
}
}