From 77c25c7b24cc20357dff1bdacf8561e9ec1e57f2 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Thu, 22 Dec 2022 14:03:26 +0100 Subject: [PATCH] Indeo 3 encoder --- nihav-allstuff/src/lib.rs | 1 + nihav-indeo/Cargo.toml | 12 +- nihav-indeo/src/codecs/indeo3enc/cell.rs | 967 ++++++++++++++++++++ nihav-indeo/src/codecs/indeo3enc/mod.rs | 660 +++++++++++++ nihav-indeo/src/codecs/indeo3enc/mv.rs | 153 ++++ nihav-indeo/src/codecs/indeo3enc/ratectl.rs | 77 ++ nihav-indeo/src/codecs/indeo3enc/tree.rs | 432 +++++++++ nihav-indeo/src/codecs/mod.rs | 18 +- nihav-indeo/src/lib.rs | 1 + 9 files changed, 2317 insertions(+), 4 deletions(-) create mode 100644 nihav-indeo/src/codecs/indeo3enc/cell.rs create mode 100644 nihav-indeo/src/codecs/indeo3enc/mod.rs create mode 100644 nihav-indeo/src/codecs/indeo3enc/mv.rs create mode 100644 nihav-indeo/src/codecs/indeo3enc/ratectl.rs create mode 100644 nihav-indeo/src/codecs/indeo3enc/tree.rs diff --git a/nihav-allstuff/src/lib.rs b/nihav-allstuff/src/lib.rs index 90ee6c3..f655b95 100644 --- a/nihav-allstuff/src/lib.rs +++ b/nihav-allstuff/src/lib.rs @@ -67,6 +67,7 @@ pub fn nihav_register_all_raw_demuxers(rd: &mut RegisteredRawDemuxers) { pub fn nihav_register_all_encoders(re: &mut RegisteredEncoders) { flash_register_all_encoders(re); generic_register_all_encoders(re); + indeo_register_all_encoders(re); duck_register_all_encoders(re); llaudio_register_all_encoders(re); ms_register_all_encoders(re); diff --git a/nihav-indeo/Cargo.toml b/nihav-indeo/Cargo.toml index 4243e43..2594903 100644 --- a/nihav-indeo/Cargo.toml +++ b/nihav-indeo/Cargo.toml @@ -12,10 +12,10 @@ path = "../nihav-codec-support" features = ["h263", "fft", "dsp_window"] [dev-dependencies] -nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers"] } +nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers", "muxer_avi"] } [features] -default = ["all_decoders", "all_demuxers"] +default = ["all_decoders", "all_demuxers", "all_encoders"] all_decoders = ["all_video_decoders", "all_audio_decoders"] all_video_decoders = ["decoder_indeo2", "decoder_indeo3", "decoder_indeo4", "decoder_indeo5", "decoder_intel263"] @@ -32,4 +32,10 @@ decoder_intel263 = ["decoders"] all_demuxers = ["demuxer_ivf"] demuxers = [] -demuxer_ivf = ["demuxers"] \ No newline at end of file +demuxer_ivf = ["demuxers"] + +all_encoders = ["all_video_encoders"] +all_video_encoders = ["encoder_indeo3"] +encoders = [] + +encoder_indeo3 = ["encoders"] \ No newline at end of file diff --git a/nihav-indeo/src/codecs/indeo3enc/cell.rs b/nihav-indeo/src/codecs/indeo3enc/cell.rs new file mode 100644 index 0000000..6f641eb --- /dev/null +++ b/nihav-indeo/src/codecs/indeo3enc/cell.rs @@ -0,0 +1,967 @@ +use super::CB_SELECTORS; +use super::mv::MV; +use super::super::indeo3data::*; +use super::{Indeo3Cell, Plane}; + +pub const MAX_CELL_SIZE: usize = 2400; +const DEFAULT_PIXEL: u8 = 0x40; +const INTRA_FLAT_THRESHOLD: u32 = 8; +const INTER_FLAT_THRESHOLD: u32 = 8; + +struct IndexWriter<'a> { + dst: &'a mut [u8], + pos: usize, + do_rle: bool, +} + +const SKIP_CELL: u8 = 0xFD; +const ZERO_CELL: u8 = 0xFD; + +impl<'a> IndexWriter<'a> { + fn new(dst: &'a mut [u8], do_rle: bool) -> Self { + Self { + dst, + pos: 0, + do_rle, + } + } + fn write_pair(&mut self, idx0: u8, idx1: u8, quad_radix: u8, esc: u8) { + //xxx: in theory one in theory the other index just has to make output fit byte range + if idx0 < quad_radix && idx1 < quad_radix { + let quad = idx1 * quad_radix + idx0 + esc; + self.dst[self.pos] = quad; + self.pos += 1; + } else { + self.dst[self.pos] = idx0; + self.pos += 1; + self.dst[self.pos] = idx1; + self.pos += 1; + } + } + fn write_byte(&mut self, val: u8) { + self.dst[self.pos] = val; + self.pos += 1; + } + fn compact_cell(&mut self, esc_vals: [u8; 4]) { + if !self.do_rle { + return; + } + let tail = &self.dst[self.pos - 4..][..4]; + let mut count = 0; + for (&a, &b) in tail.iter().zip(esc_vals.iter()).rev() { + if a != b { + break; + } + count += 1; + } + if count > 1 { + self.pos -= count; + self.dst[self.pos] = ZERO_CELL; + self.pos += 1; + } + } + fn compact_all_cells(&mut self) { + if !self.do_rle { + return; + } + if self.pos > 2 { + let mut i = 0; + while i + 2 < self.pos { + if self.dst[i] == ZERO_CELL && self.dst[i + 1] == ZERO_CELL { + let mut last_idx = i; + for j in (i + 1)..self.pos { + if self.dst[j] != ZERO_CELL { + break; + } + last_idx = j; + } + let len = (last_idx - i + 1).min(31); + if len == 2 { + self.dst[i] = 0xFC; + move_tail(&mut self.dst[i + 1..self.pos], 1); + self.pos -= 1; + } else { + self.dst[i] = 0xFB; + self.dst[i + 1] = len as u8; + move_tail(&mut self.dst[i + 2..self.pos], len - 2); + self.pos -= len - 2; + } + } + i += 1; + } + } + } + fn end(self) -> usize { + self.pos + } +} + +fn move_tail(buf: &mut [u8], off: usize) { + let len = buf.len(); + for i in off..len { + buf[i - off] = buf[i]; + } +} + +#[derive(Default)] +struct CodebookSuggester { + count: [u16; 16], +} + +const BINNING_FACTORS: [u8; 16] = [3, 7, 9, 12, 14, 16, 18, 40, 2, 3, 4, 5, 6, 7, 8, 9]; + +impl CodebookSuggester { + fn new() -> Self { Self::default() } + fn merge(cs1: &Self, cs2: &Self) -> Self { + let mut count = [0; 16]; + for (dst, (&src1, &src2)) in count.iter_mut().zip(cs1.count.iter().zip(cs2.count.iter())) { + *dst = src1 + src2; + } + Self { count } + } + fn add_delta(&mut self, delta: u8) { + for (i, &fac) in BINNING_FACTORS.iter().enumerate() { + let val = if i < 8 { delta + fac - 1 } else { delta }; + if (val % fac) == 0 { + self.count[i] += 1; + } + } + } + fn add_line(&mut self, src: &[i8]) { + for &delta in src.iter() { + if delta == 0 { + continue; + } + let delta = delta.abs() as u8; + self.add_delta(delta); + } + } + fn add_line_half(&mut self, src: &[i8]) { + for &delta in src.iter().step_by(2) { + if delta == 0 { + continue; + } + let delta = delta.abs() as u8; + self.add_delta(delta); + } + } + fn get_best(&self) -> u8 { + let mut idx = 0; + for (i, &cnt) in self.count.iter().enumerate().skip(1) { + if cnt > self.count[idx] { + idx = i; + } + } + idx as u8 + } +} + +pub struct CellEncoder { + buf: [u8; MAX_CELL_SIZE + 160], + rbuf: [u8; MAX_CELL_SIZE + 160], + deltas: [i8; MAX_CELL_SIZE], + cell: Indeo3Cell, + pub out: [u8; MAX_CELL_SIZE / 2 + 1], + pub osize: usize, + + pub flat_thr_i: u32, + pub flat_thr_p: u32, + pub do_rle: bool, + pub quant: Option, +} + +impl CellEncoder { + pub fn new() -> Self { + Self { + buf: [0; MAX_CELL_SIZE + 160], + rbuf: [0; MAX_CELL_SIZE + 160], + deltas: [0; MAX_CELL_SIZE], + cell: Indeo3Cell::new(0, 0, false), + out: [0; MAX_CELL_SIZE / 2 + 1], + osize: 0, + + flat_thr_i: INTRA_FLAT_THRESHOLD, + flat_thr_p: INTER_FLAT_THRESHOLD, + do_rle: true, + quant: None, + } + } + pub fn read_buffer(&mut self, plane: &Plane, cell: Indeo3Cell) { + self.cell = cell; + + let src = &plane.data[cell.get_x() + cell.get_y() * plane.width..]; + let dst_w = cell.get_width(); + for (dline, sline) in self.buf.chunks_mut(dst_w).skip(1).zip(src.chunks(plane.width)).take(cell.get_height()) { + dline.copy_from_slice(&sline[..dst_w]); + } + if cell.get_y() > 0 { + self.buf[..dst_w].copy_from_slice(&plane.data[cell.get_x() + (cell.get_y() - 1) * plane.width..][..dst_w]); + } else { + for el in self.buf[..dst_w].iter_mut() { + *el = DEFAULT_PIXEL; + } + } + } + pub fn read_mv_buffer(&mut self, plane: &Plane, cell: Indeo3Cell, mv: MV) { + self.cell = cell; + + let xoff = (cell.get_x() as isize + isize::from(mv.x)) as usize; + let yoff = (cell.get_y() as isize + isize::from(mv.y)) as usize; + let src = &plane.data[xoff + yoff * plane.width..]; + let dst_w = cell.get_width(); + for (dline, sline) in self.rbuf.chunks_mut(dst_w).skip(1).zip(src.chunks(plane.width)).take(cell.get_height()) { + dline.copy_from_slice(&sline[..dst_w]); + } + } + pub fn null_mv(&mut self) { + let stride = self.cell.get_width(); + self.buf[stride..].copy_from_slice(&self.rbuf[stride..]); + } + pub fn gen_diffs_intra(&mut self) { + let stride = self.cell.get_width(); + let mut start = stride; + for dline in self.deltas.chunks_mut(stride).take(self.cell.get_height()) { + let (pprev, cur) = self.buf.split_at(start); + let prev = &pprev[pprev.len() - stride..]; + + for (dst, (&cur, &top)) in dline.iter_mut().zip(cur.iter().zip(prev.iter())) { + *dst = (cur as i8) - (top as i8); + } + + start += stride; + } + } + pub fn gen_diffs_inter(&mut self) { + let stride = self.cell.get_width(); + let prev_iter = self.rbuf.chunks(stride).skip(1); + let cur_iter = self.buf.chunks(stride).skip(1); + for (dline, (cur, prev)) in self.deltas.chunks_mut(stride).take(self.cell.get_height()).zip(cur_iter.zip(prev_iter)) { + for (dst, (&cur, &prev)) in dline.iter_mut().zip(cur.iter().zip(prev.iter())) { + *dst = (cur as i8) - (prev as i8); + } + } + } + pub fn put_buffer(&self, plane: &mut Plane) { + let to_skip = if !self.cell.is_intra() || self.cell.get_y() == 0 { 1 } else { 0 }; + + let dst = &mut plane.data[self.cell.get_x() + (self.cell.get_y() + to_skip - 1) * plane.width..]; + let src_w = self.cell.get_width(); + for (sline, dline) in self.buf.chunks(src_w).skip(to_skip).zip(dst.chunks_mut(plane.width)).take(self.cell.get_height() + 1 - to_skip) { + dline[..src_w].copy_from_slice(sline); + } + } + fn determine_mode(&self, intra: bool, mut mode_hint: u8) -> (u8, [u8; 2]) { + if let Some(qmode) = self.quant { + if intra { + return (mode_hint, [qmode as u8, qmode as u8]); + } else { + let qmode = (qmode & 7) as u8; + return (mode_hint, [qmode, qmode]); + } + } + + let stride = self.cell.get_width(); + + let mut cb_p = CodebookSuggester::new(); + let mut cb_s = CodebookSuggester::new(); + if !intra && (self.cell.get_height() & 7 == 0) { + let mut vdiff = 0; + let mut hdiff = 0; + for line_pair in self.deltas.chunks(stride * 2).take(self.cell.get_height() / 2) { + let (line1, line2) = line_pair.split_at(stride); + for (&el1, &el2) in line1.iter().zip(line2.iter()) { + let diff = i32::from(el1) - i32::from(el2); + vdiff += (diff * diff) as u32; + } + } + for line in self.deltas.chunks(stride).take(self.cell.get_height()) { + for pair in line.chunks(2) { + let diff = i32::from(pair[1]) - i32::from(pair[0]); + hdiff += (diff * diff) as u32; + } + } + vdiff /= (self.cell.get_width() * self.cell.get_height() / 2) as u32; + hdiff /= (self.cell.get_width() * self.cell.get_height() / 2) as u32; + + mode_hint = match ((vdiff > self.flat_thr_p), (hdiff > self.flat_thr_p)) { + (false, false) if (self.cell.get_width() & 7) == 0 => 10, + (false, _) => 11, + _ => 0, + }; + } + match mode_hint { + 0 => { + for line_pair in self.deltas.chunks(stride * 2).take(self.cell.get_height() / 2) { + let (line1, line2) = line_pair.split_at(stride); + cb_p.add_line(line1); + cb_s.add_line(line2); + } + }, + 3 => { + for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) { + let (line01, line23) = line_quad.split_at(stride * 2); + let (_line0, line1) = line01.split_at(stride); + let (_line2, line3) = line23.split_at(stride); + cb_p.add_line(line1); + cb_s.add_line(line3); + } + }, + 10 => { + for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) { + let (line01, line23) = line_quad.split_at(stride * 2); + let (_line0, line1) = line01.split_at(stride); + let (_line2, line3) = line23.split_at(stride); + cb_p.add_line_half(line1); + cb_s.add_line_half(line3); + } + }, + 11 => { + for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) { + let (line01, line23) = line_quad.split_at(stride * 2); + let (_line0, line1) = line01.split_at(stride); + let (_line2, line3) = line23.split_at(stride); + cb_p.add_line(line1); + cb_s.add_line(line3); + } + }, + _ => unreachable!(), + }; + let cb_f = CodebookSuggester::merge(&cb_p, &cb_s).get_best(); + let cb_p = cb_p.get_best(); + let mut cb_s = cb_s.get_best(); + + let mut use_single = !intra || mode_hint == 10 || cb_p == cb_s; + if !use_single { + if cb_s == 0 { // we can adjust to the CB_SELECTORS here + cb_s = (((cb_p & 7) + 1) * 2).min(15); + } + let ncb = (cb_p << 4) + cb_s; + use_single = !CB_SELECTORS.contains(&ncb); + } + + if use_single { + if intra || cb_f < 8 { // we don't want requant happening in inter mode + (mode_hint, [cb_f, cb_f]) + } else { + (mode_hint, [0, 0]) + } + } else { + (mode_hint + 1, [cb_p, cb_s]) + } + } + pub fn compress_intra(&mut self, mode_hint: u8) { + let (mode, vq_idx) = self.determine_mode(true, mode_hint); + + let cb_no1 = usize::from(vq_idx[1]); + let cb_no2 = usize::from(vq_idx[0]); + let cb1 = IVI3_DELTA_CBS[cb_no1]; + let cb2 = IVI3_DELTA_CBS[cb_no2]; + + let mut requant_idx = None; + if (mode == 1) || (mode == 4) { + let aq_idx = (vq_idx[0] << 4) | vq_idx[1]; + let mut idx = 42; + for (i, &el) in CB_SELECTORS.iter().enumerate() { + if el == aq_idx { + idx = i; + break; + } + } + self.out[0] = (mode << 4) | (idx as u8); + + if idx >= 8 { + requant_idx = Some(idx - 8); + } + } else { + self.out[0] = (mode << 4) | (cb_no1 as u8); + + if (8..=15).contains(&cb_no1) { + requant_idx = Some(cb_no1 - 8); + } + } + if self.cell.get_y() == 0 { + requant_idx = None; + } + + let start = 1; + let mut iwriter = IndexWriter::new(&mut self.out[start..], self.do_rle); + + let esc_val1 = (cb1.data.len() / 2) as u8; + let esc_val2 = (cb2.data.len() / 2) as u8; + + let cbs = [cb1, cb2, cb1, cb2]; + let esc_vals = [esc_val1, esc_val2, esc_val1, esc_val2]; + + let mut first_line = self.cell.get_y() == 0; + let stride = self.cell.get_width(); + + if let Some(ridx) = requant_idx {// && !first_line { + requant(&mut self.buf[..stride], ridx); + } + + let mut cell4 = [0; 20]; + match mode { + 0 | 1 | 2 => { + for y in (0..self.cell.get_height()).step_by(4) { + for x in (0..self.cell.get_width()).step_by(4) { + Self::get_cell4(&self.buf, x, y, stride, &mut cell4); + // first check if the cell can be coded with zero predictor + let mut diff = 0; + let mut pivot = 4; + for _y in 0..4 { + let (top, cur) = cell4.split_at(pivot); + let top = &top[top.len() - 4..]; + for (&tval, &cval) in top.iter().zip(cur.iter()) { + let cdiff = i32::from(tval) - i32::from(cval); + diff += cdiff * cdiff; + } + pivot += 4; + } + if (diff as u32) < self.flat_thr_i { + iwriter.write_byte(ZERO_CELL); + let (top, tail) = cell4.split_at_mut(4); + for dline in tail.chunks_mut(4) { + dline.copy_from_slice(top); + } + Self::put_cell4(&mut self.buf, x, y, stride, &cell4); + continue; + } + + compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals); + Self::put_cell4(&mut self.buf, x, y, stride, &cell4); + } + } + }, + 3 | 4 => { + for y in (0..self.cell.get_height()).step_by(8) { + for x in (0..self.cell.get_width()).step_by(4) { + Self::get_cell_mode3(&self.buf, x, y, stride, &mut cell4); + compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals); + Self::put_cell_mode3(&mut self.buf, x, y, stride, &cell4, first_line); + } + first_line = false; + } + }, + 10 => { + for y in (0..self.cell.get_height()).step_by(8) { + for x in (0..self.cell.get_width()).step_by(8) { + Self::get_cell_mode10i(&self.buf, x, y, stride, &mut cell4); + compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals); + Self::put_cell_mode10i(&mut self.buf, x, y, stride, &cell4, first_line); + } + first_line = false; + } + }, + _ => unreachable!(), + }; + iwriter.compact_all_cells(); + + self.osize = iwriter.end() + start; + } + pub fn compress_inter(&mut self) { + let (mode, vq_idx) = self.determine_mode(false, 0); + + let cb_no1 = usize::from(vq_idx[1]); + let cb_no2 = usize::from(vq_idx[0]); + let cb1 = IVI3_DELTA_CBS[cb_no1]; + let cb2 = IVI3_DELTA_CBS[cb_no2]; + + if (mode == 1) || (mode == 4) { + let aq_idx = (vq_idx[0] << 4) | vq_idx[1]; + let mut idx = 42; + for (i, &el) in CB_SELECTORS.iter().enumerate() { + if el == aq_idx { + idx = i; + break; + } + } + self.out[0] = (mode << 4) | (idx as u8); + } else { + self.out[0] = (mode << 4) | (cb_no1 as u8); + } + let start = 1; + let mut iwriter = IndexWriter::new(&mut self.out[start..], self.do_rle); + + let esc_val1 = (cb1.data.len() / 2) as u8; + let esc_val2 = (cb2.data.len() / 2) as u8; + + let cbs = [cb1, cb2, cb1, cb2]; + let esc_vals = [esc_val1, esc_val2, esc_val1, esc_val2]; + + + let stride = self.cell.get_width(); + let mut ccell4 = [0; 20]; + let mut pcell4 = [0; 20]; + match mode { + 0 | 1 | 2 => { + for y in (0..self.cell.get_height()).step_by(4) { + for x in (0..self.cell.get_width()).step_by(4) { + Self::get_cell4(&self.buf, x, y, stride, &mut ccell4); + Self::get_cell4(&self.rbuf, x, y, stride, &mut pcell4); + // first check if the cell can be coded with zero predictor + let mut diff = 0; + for (&pval, &cval) in pcell4[4..].iter().zip(ccell4[4..].iter()) { + let cdiff = i32::from(pval) - i32::from(cval); + diff += cdiff * cdiff; + } + if diff < 8 { + iwriter.write_byte(SKIP_CELL); + Self::put_cell4(&mut self.buf, x, y, stride, &pcell4); + continue; + } + + compress_inter_cell(&mut iwriter, &mut ccell4, &pcell4, &cbs, esc_vals); + Self::put_cell4(&mut self.buf, x, y, stride, &ccell4); + } + } + }, + 10 => { + let mut offset = 0; + let mut ref_cell = [0; 64]; + let mut avg_diff: [i16; 16]; + for _y in (0..self.cell.get_height()).step_by(8) { + for x in (0..self.cell.get_width()).step_by(8) { + for (dline, sline) in ref_cell.chunks_mut(8).zip(self.rbuf[offset + stride + x..].chunks(stride)) { + dline.copy_from_slice(&sline[..8]); + } + avg_diff = [0; 16]; + for j in 0..8 { + for i in 0..8 { + avg_diff[i / 2 + (j / 2) * 4] += i16::from(self.deltas[offset + x + i + j * stride]); + } + } + for el in avg_diff.iter_mut() { + *el = (*el + 2) >> 2; + } + compress_inter_cell_mode10(&mut iwriter, &mut ref_cell, &avg_diff, &cbs, esc_vals); + for (sline, dline) in ref_cell.chunks(8).zip(self.buf[offset + stride + x..].chunks_mut(stride)) { + dline[..8].copy_from_slice(sline); + } + } + offset += stride * 8; + } + }, + 11 => { + let mut offset = 0; + let mut ref_cell = [0; 32]; + let mut avg_diff: [i16; 16]; + for _y in (0..self.cell.get_height()).step_by(8) { + for x in (0..self.cell.get_width()).step_by(4) { + for (dline, sline) in ref_cell.chunks_mut(4).zip(self.rbuf[offset + stride + x..].chunks(stride)) { + dline.copy_from_slice(&sline[..4]); + } + avg_diff = [0; 16]; + for j in 0..8 { + for i in 0..4 { + avg_diff[i + (j / 2) * 4] += i16::from(self.deltas[offset + x + i + j * stride]); + } + } + for el in avg_diff.iter_mut() { + *el = (*el + 1) >> 1; + } + + compress_inter_cell_mode11(&mut iwriter, &mut ref_cell, &avg_diff, &cbs, esc_vals); + for (sline, dline) in ref_cell.chunks(4).zip(self.buf[offset + stride + x..].chunks_mut(stride)) { + dline[..4].copy_from_slice(sline); + } + } + offset += stride * 8; + } + }, + _ => unreachable!(), + }; + iwriter.compact_all_cells(); + + self.osize = iwriter.end() + start; + } + + fn get_cell4(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) { + for (dst, src) in cell.chunks_mut(4).zip(data[x + y * stride..].chunks(stride)) { + dst.copy_from_slice(&src[..4]); + } + } + fn put_cell4(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20]) { + for (src, dst) in cell.chunks(4).zip(data[x + y * stride..].chunks_mut(stride)).skip(1) { + dst[..4].copy_from_slice(src); + } + } + fn get_cell_mode3(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) { + let src = &data[x + y * stride..]; + for (dline, slines) in cell.chunks_mut(4).zip(src.chunks(stride * 2)) { + dline.copy_from_slice(&slines[..4]); + } + } + fn put_cell_mode3(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20], first_line: bool) { + let dst = &mut data[x + y * stride..]; + let mut dst_idx = stride; + for line in 0..4 { + for x in 0..4 { + let top = cell[line * 4 + x]; + let cur = cell[(line + 1) * 4 + x]; + dst[dst_idx + x] = (top + cur) >> 1; + dst[dst_idx + stride + x] = cur; + } + dst_idx += stride * 2; + } + if first_line { + dst[stride..][..4].copy_from_slice(&cell[4..8]); + } + } + fn get_cell_mode10i(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) { + let src = &data[x + y * stride..]; + for (dline, src_pair) in cell.chunks_mut(4).zip(src.chunks(stride * 2)) { + for (dst, src) in dline.iter_mut().zip(src_pair.chunks(2)) { + *dst = src[0]; + } + } + } + fn put_cell_mode10i(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20], first_line: bool) { + let dst = &mut data[x + y * stride..]; + let mut dst_idx = stride; + for line in 0..4 { + for x in 0..4 { + let top = dst[dst_idx - stride + x * 2]; + let cur = cell[(line + 1) * 4 + x]; + dst[dst_idx + x * 2] = (top + cur) >> 1; + dst[dst_idx + x * 2 + 1] = (top + cur) >> 1; + dst[dst_idx + stride + x * 2] = cur; + dst[dst_idx + stride + x * 2 + 1] = cur; + } + dst_idx += stride * 2; + } + if first_line { + let (top, tail) = dst[stride..].split_at_mut(stride); + top[..8].copy_from_slice(&tail[..8]); + } + } +} + +fn requant(line: &mut [u8], rq_index: usize) { + let tab = &REQUANT_TAB[rq_index]; + for el in line.iter_mut() { + *el = tab[usize::from(*el)]; + } +} + +fn compress_intra_cell(iwriter: &mut IndexWriter, cell4: &mut [u8; 20], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) { + let mut pivot = 4; + for y in 0..4 { + let cb = cbs[y]; + let esc_val = esc_vals[y]; + + let (prev, cur) = cell4.split_at_mut(pivot); + let prev = &prev[prev.len() - 4..]; + let cur = &mut cur[..4]; + let (idx0, idx1) = find_quad(&cb.data, prev, cur); + + cur[0] = ((prev[0] as i8) + cb.data[usize::from(idx1) * 2]) as u8; + cur[1] = ((prev[1] as i8) + cb.data[usize::from(idx1) * 2 + 1]) as u8; + cur[2] = ((prev[2] as i8) + cb.data[usize::from(idx0) * 2]) as u8; + cur[3] = ((prev[3] as i8) + cb.data[usize::from(idx0) * 2 + 1]) as u8; + + iwriter.write_pair(idx0, idx1, cb.quad_radix, esc_val); + + pivot += 4; + } + iwriter.compact_cell(esc_vals); +} + +fn compress_inter_cell(iwriter: &mut IndexWriter, ccell4: &mut [u8; 20], pcell: &[u8; 20], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) { + for (y, (prev, cur)) in pcell[4..].chunks(4).zip(ccell4[4..].chunks_mut(4)).enumerate() { + let cb = cbs[y]; + let esc_val = esc_vals[y]; + + let (idx0, idx1) = find_quad(&cb.data, prev, cur); + + cur[0] = ((prev[0] as i8) + cb.data[usize::from(idx1) * 2]) as u8; + cur[1] = ((prev[1] as i8) + cb.data[usize::from(idx1) * 2 + 1]) as u8; + cur[2] = ((prev[2] as i8) + cb.data[usize::from(idx0) * 2]) as u8; + cur[3] = ((prev[3] as i8) + cb.data[usize::from(idx0) * 2 + 1]) as u8; + + iwriter.write_pair(idx0, idx1, cb.quad_radix, esc_val); + } + iwriter.compact_cell(esc_vals); +} + +fn compress_inter_cell_mode10(iwriter: &mut IndexWriter, cell: &mut [u8; 64], diffs: &[i16; 16], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) { + for y in 0..4 { + let cb = cbs[y]; + let esc_val = esc_vals[y]; + let mut indices = [0, 0]; + for pair_no in (0..4).step_by(2) { + let src_idx = y * 8 * 2 + pair_no * 2; + let src0 = [cell[src_idx], cell[src_idx + 1], cell[src_idx + 8], cell[src_idx + 9]]; + let src1 = [cell[src_idx + 2], cell[src_idx + 3], cell[src_idx + 10], cell[src_idx + 11]]; + + let cur_diff = [diffs[y * 4 + pair_no] as i8, diffs[y * 4 + pair_no + 1] as i8]; + + let mut best_idx = 0; + let mut best_dist = pair_dist(&cur_diff, &[0, 0]); + for (idx, cbpair) in cb.data.chunks(2).enumerate().skip(1) { + let dist = pair_dist(&cur_diff, cbpair); + if dist < best_dist { + let mut fits = true; + for &el in src0.iter() { + if !in_range(el as i8, cbpair[0]) { + fits = false; + break; + } + } + for &el in src1.iter() { + if !in_range(el as i8, cbpair[1]) { + fits = false; + break; + } + } + if fits { + best_dist = dist; + best_idx = idx; + } + } + } + + indices[pair_no / 2] = best_idx as u8; + + let cb_pair = &cb.data[best_idx * 2..]; + for row in cell[src_idx..].chunks_mut(8).take(2) { + row[0] = ((row[0] as i8) + cb_pair[0]) as u8; + row[1] = ((row[1] as i8) + cb_pair[0]) as u8; + row[2] = ((row[2] as i8) + cb_pair[1]) as u8; + row[3] = ((row[3] as i8) + cb_pair[1]) as u8; + } + } + iwriter.write_pair(indices[1], indices[0], cb.quad_radix, esc_val); + } + iwriter.compact_cell(esc_vals); +} + +fn compress_inter_cell_mode11(iwriter: &mut IndexWriter, cell: &mut [u8; 32], diffs: &[i16; 16], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) { + for y in 0..4 { + let cb = cbs[y]; + let esc_val = esc_vals[y]; + let mut indices = [0, 0]; + for pair_no in (0..4).step_by(2) { + let src_idx = y * 4 * 2 + pair_no; + let src0 = [cell[src_idx], cell[src_idx + 4]]; + let src1 = [cell[src_idx + 1], cell[src_idx + 5]]; + + let cur_diff = [diffs[y * 4 + pair_no] as i8, diffs[y * 4 + pair_no + 1] as i8]; + + let mut best_idx = 0; + let mut best_dist = pair_dist(&cur_diff, &[0, 0]); + for (idx, cbpair) in cb.data.chunks(2).enumerate().skip(1) { + let dist = pair_dist(&cur_diff, cbpair); + if dist < best_dist { + let mut fits = true; + for &el in src0.iter() { + if !in_range(el as i8, cbpair[0]) { + fits = false; + break; + } + } + for &el in src1.iter() { + if !in_range(el as i8, cbpair[1]) { + fits = false; + break; + } + } + if fits { + best_dist = dist; + best_idx = idx; + } + } + } + + indices[pair_no / 2] = best_idx as u8; + + let cb_pair = &cb.data[best_idx * 2..]; + cell[src_idx] = ((cell[src_idx] as i8) + cb_pair[0]) as u8; + cell[src_idx + 4] = ((cell[src_idx + 4] as i8) + cb_pair[0]) as u8; + cell[src_idx + 1] = ((cell[src_idx + 1] as i8) + cb_pair[1]) as u8; + cell[src_idx + 5] = ((cell[src_idx + 5] as i8) + cb_pair[1]) as u8; + } + iwriter.write_pair(indices[1], indices[0], cb.quad_radix, esc_val); + } + iwriter.compact_cell(esc_vals); +} + +fn pair_dist(src: &[i8], pair: &[i8]) -> u32 { + let d0 = (i32::from(src[0]) - i32::from(pair[0])).abs() as u32; + let d1 = (i32::from(src[1]) - i32::from(pair[1])).abs() as u32; + d0 * d0 + d1 * d1 +} + +fn in_range(base: i8, delta: i8) -> bool { + if let Some(val) = base.checked_add(delta) { + val >= 0 + } else { + false + } +} + +fn find_pair(cb_data: &[i8], ppair: &[u8], cpair: &[u8]) -> u8 { + let ppair = [ppair[0] as i8, ppair[1] as i8]; + let diff = [(cpair[0] as i8) - ppair[0], (cpair[1] as i8) - ppair[1]]; + // pair 0 is always zero; + if diff == [0, 0] { + return 0; + } + let mut best_idx = 0; + let mut best_dist = pair_dist(&diff, &[0, 0]); + for (idx, cbpair) in cb_data.chunks(2).enumerate().skip(1) { + let dist = pair_dist(&diff, cbpair); + if dist < best_dist && in_range(ppair[0], cbpair[0]) && in_range(ppair[1], cbpair[1]) { + best_dist = dist; + best_idx = idx; + } + } + best_idx as u8 +} + +fn find_quad(cb_data: &[i8], prev: &[u8], cur: &[u8]) -> (u8, u8) { + let (ppair1, ppair0) = prev.split_at(2); + let (cpair1, cpair0) = cur.split_at(2); + let idx1 = find_pair(cb_data, ppair1, cpair1); + let idx0 = find_pair(cb_data, ppair0, cpair0); + (idx0, idx1) +} + +const REQUANT_TAB: [[u8; 128]; 8] = [ + [ + 0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x06, 0x08, + 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0e, 0x0e, 0x10, + 0x10, 0x12, 0x12, 0x14, 0x14, 0x16, 0x16, 0x18, + 0x18, 0x1a, 0x1a, 0x1c, 0x1c, 0x1e, 0x1e, 0x20, + 0x20, 0x22, 0x22, 0x24, 0x24, 0x26, 0x26, 0x28, + 0x28, 0x2a, 0x2a, 0x2c, 0x2c, 0x2e, 0x2e, 0x30, + 0x30, 0x32, 0x32, 0x34, 0x34, 0x36, 0x36, 0x38, + 0x38, 0x3a, 0x3a, 0x3c, 0x3c, 0x3e, 0x3e, 0x40, + 0x40, 0x42, 0x42, 0x44, 0x44, 0x46, 0x46, 0x48, + 0x48, 0x4a, 0x4a, 0x4c, 0x4c, 0x4e, 0x4e, 0x50, + 0x50, 0x52, 0x52, 0x54, 0x54, 0x56, 0x56, 0x58, + 0x58, 0x5a, 0x5a, 0x5c, 0x5c, 0x5e, 0x5e, 0x60, + 0x60, 0x62, 0x62, 0x64, 0x64, 0x66, 0x66, 0x68, + 0x68, 0x6a, 0x6a, 0x6c, 0x6c, 0x6e, 0x6e, 0x70, + 0x70, 0x72, 0x72, 0x74, 0x74, 0x76, 0x76, 0x78, + 0x78, 0x7a, 0x7a, 0x7c, 0x7c, 0x7e, 0x7e, 0x7e + ], [ + 0x01, 0x01, 0x04, 0x04, 0x04, 0x07, 0x07, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, 0x10, 0x10, + 0x10, 0x13, 0x13, 0x13, 0x16, 0x16, 0x16, 0x19, + 0x19, 0x19, 0x1c, 0x1c, 0x1c, 0x1f, 0x1f, 0x1f, + 0x22, 0x22, 0x22, 0x25, 0x25, 0x25, 0x28, 0x28, + 0x28, 0x2b, 0x2b, 0x2b, 0x2e, 0x2e, 0x2e, 0x31, + 0x31, 0x31, 0x34, 0x34, 0x34, 0x37, 0x37, 0x37, + 0x3a, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x40, 0x40, + 0x40, 0x43, 0x43, 0x43, 0x46, 0x46, 0x46, 0x49, + 0x49, 0x49, 0x4c, 0x4c, 0x4c, 0x4f, 0x4f, 0x4f, + 0x52, 0x52, 0x52, 0x55, 0x55, 0x55, 0x58, 0x58, + 0x58, 0x5b, 0x5b, 0x5b, 0x5e, 0x5e, 0x5e, 0x61, + 0x61, 0x61, 0x64, 0x64, 0x64, 0x67, 0x67, 0x67, + 0x6a, 0x6a, 0x6a, 0x6d, 0x6d, 0x6d, 0x70, 0x70, + 0x70, 0x73, 0x73, 0x73, 0x76, 0x76, 0x76, 0x76, + 0x76, 0x79, 0x7c, 0x7c, 0x7c, 0x7f, 0x7f, 0x7f + ], [ + 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, + 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x10, 0x10, + 0x10, 0x10, 0x14, 0x14, 0x14, 0x14, 0x18, 0x18, + 0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x1c, 0x20, 0x20, + 0x20, 0x20, 0x24, 0x24, 0x24, 0x24, 0x28, 0x28, + 0x28, 0x28, 0x2c, 0x2c, 0x2c, 0x2c, 0x30, 0x30, + 0x30, 0x30, 0x34, 0x34, 0x34, 0x34, 0x38, 0x38, + 0x38, 0x38, 0x3c, 0x3c, 0x3c, 0x3c, 0x40, 0x40, + 0x40, 0x40, 0x44, 0x44, 0x44, 0x44, 0x48, 0x48, + 0x48, 0x48, 0x4c, 0x4c, 0x4c, 0x4c, 0x50, 0x50, + 0x50, 0x50, 0x54, 0x54, 0x54, 0x54, 0x58, 0x58, + 0x58, 0x58, 0x5c, 0x5c, 0x5c, 0x5c, 0x60, 0x60, + 0x60, 0x60, 0x64, 0x64, 0x64, 0x64, 0x68, 0x68, + 0x68, 0x68, 0x6c, 0x6c, 0x6c, 0x6c, 0x70, 0x70, + 0x70, 0x70, 0x74, 0x74, 0x74, 0x74, 0x78, 0x78, + 0x78, 0x78, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c + ], [ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x13, 0x13, 0x13, 0x13, 0x13, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x45, 0x45, 0x45, 0x45, + 0x45, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x54, 0x54, 0x54, 0x54, 0x54, + 0x59, 0x59, 0x59, 0x59, 0x59, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x63, 0x63, 0x63, 0x63, 0x63, 0x68, + 0x68, 0x68, 0x68, 0x68, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x72, 0x72, 0x72, 0x72, 0x72, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c + ], [ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x46, 0x46, 0x46, + 0x46, 0x46, 0x46, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, + 0x4c, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x5e, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x76, 0x76, 0x76, + 0x76, 0x76, 0x76, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c + ], [ + 0x01, 0x01, 0x01, 0x01, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6a, 0x6a, + 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x71, 0x71, 0x71, + 0x71, 0x71, 0x71, 0x71, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f + ], [ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x50, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x50, 0x50, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0x60, 0x68, 0x68, 0x68, 0x68, + 0x68, 0x68, 0x68, 0x68, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78 + ], [ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x5b, 0x5b, + 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, + 0x76, 0x76, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f + ] +]; diff --git a/nihav-indeo/src/codecs/indeo3enc/mod.rs b/nihav-indeo/src/codecs/indeo3enc/mod.rs new file mode 100644 index 0000000..8ff2aec --- /dev/null +++ b/nihav-indeo/src/codecs/indeo3enc/mod.rs @@ -0,0 +1,660 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +mod cell; +use cell::*; +mod mv; +use mv::*; +mod ratectl; +use ratectl::*; +mod tree; +pub use tree::{Indeo3Cell, Plane}; +use tree::Indeo3PrimaryTree; + +const OS_HEADER_SIZE: usize = 16; +const BITSTREAM_HEADER_SIZE: usize = 48; +const HDR_FIELD_2: u32 = 0; +const FRMH_TAG: u32 = ((b'F' as u32) << 24) | ((b'R' as u32) << 16) + | ((b'M' as u32) << 8) | (b'H' as u32); +const PLANE_OFFSETS: usize = 32; + +const CB_SELECTORS: [u8; 16] = [ + 0x02, 0x14, 0x26, 0x38, 0x4A, 0x5C, 0x6E, 0x7F, + 0x82, 0x94, 0xA6, 0xB8, 0xCA, 0xDC, 0xEE, 0xFF +]; + +const PLANE_ORDER: [usize; 3] = [1, 2, 0]; + +pub struct Indeo3Writer<'a> { + dst: &'a mut Vec, + bitbuf: u8, + bits: u8, + bitpos: Option, +} + +impl<'a> Indeo3Writer<'a> { + fn new(dst: &'a mut Vec) -> Self { + Self { + dst, + bitbuf: 0, + bits: 0, + bitpos: None, + } + } + pub fn put_byte(&mut self, b: u8) { + self.dst.push(b); + } + pub fn put_2bits(&mut self, val: u8) { + if self.bits == 0 { + self.bitpos = Some(self.dst.len()); + self.dst.push(0); + } + self.bitbuf |= val << (6 - self.bits); + self.bits += 2; + if self.bits == 8 { + let bpos = self.bitpos.unwrap_or(0); + self.dst[bpos] = self.bitbuf; + self.bitbuf = 0; + self.bits = 0; + self.bitpos = None; + } + } +} + +impl<'a> Drop for Indeo3Writer<'a> { + fn drop(&mut self) { + if self.bits != 0 { + let bpos = self.bitpos.unwrap_or(0); + self.dst[bpos] = self.bitbuf; + } + } +} + +#[derive(Default)] +struct Indeo3Frame { + plane: [Plane; 3], +} + +impl Indeo3Frame { + fn new() -> Self { Self::default() } + fn alloc(&mut self, width: usize, height: usize) { + self.plane[0].alloc(width, height, 40); + self.plane[1].alloc(width / 4, height / 4, 10); + self.plane[2].alloc(width / 4, height / 4, 10); + } + fn fill(&mut self, vbuf: &NAVideoBufferRef) { + let data = vbuf.get_data(); + for (plane_no, plane) in self.plane.iter_mut().enumerate() { + plane.fill(&data[vbuf.get_offset(plane_no)..], vbuf.get_stride(plane_no)); + } + } + fn clear_mvs(&mut self) { + for plane in self.plane.iter_mut() { + plane.clear_mvs(); + } + } +} + +struct Indeo3Encoder { + stream: Option, + pkt: Option, + cframe: Indeo3Frame, + pframe: Indeo3Frame, + cenc: CellEncoder, + mv_est: MotionEstimator, + rc: RateControl, + frameno: u32, + buf_sel: bool, + width: usize, + height: usize, + + debug_tree: bool, + debug_frm: bool, + try_again: bool, +} + +impl Indeo3Encoder { + fn new() -> Self { + Self { + stream: None, + pkt: None, + cframe: Indeo3Frame::new(), + pframe: Indeo3Frame::new(), + cenc: CellEncoder::new(), + mv_est: MotionEstimator::new(), + rc: RateControl::new(), + frameno: 0, + buf_sel: false, + width: 0, + height: 0, + + debug_tree: false, + debug_frm: false, + try_again: false, + } + } + fn encode_planes(&mut self, dbuf: &mut Vec, trees: &[Box], is_intra: bool) -> EncoderResult<()> { + for (&planeno, tree) in PLANE_ORDER.iter().zip(trees.iter()) { + let offset = dbuf.len(); + let ref_plane = &self.pframe.plane[planeno]; + + let mut mc_count = [0; 4]; + let mvs = &self.cframe.plane[planeno].mvs; + write_u32le(&mut mc_count, mvs.len() as u32)?; + dbuf.extend_from_slice(&mc_count); + for &(mv, _) in mvs.iter() { + dbuf.push(mv.y as u8); + dbuf.push(mv.x as u8); + } + + let mut iw = Indeo3Writer::new(dbuf); + self.cframe.plane[planeno].encode_tree(&mut iw, &tree, &mut self.cenc, is_intra, ref_plane); + drop(iw); + while (dbuf.len() & 3) != 0 { + dbuf.push(0); + } + + let plane_off = PLANE_OFFSETS + 4 * if planeno > 0 { planeno ^ 3 } else { 0 }; + write_u32le(&mut dbuf[plane_off..], (offset - OS_HEADER_SIZE) as u32)?; + } + + let mut checksum = 0; + for plane in self.cframe.plane.iter() { + checksum ^= plane.checksum(); + } + write_u16le(&mut dbuf[26..], checksum)?; + + let size = (dbuf.len() - OS_HEADER_SIZE) as u32; + write_u32le(&mut dbuf[8..], self.frameno ^ HDR_FIELD_2 ^ FRMH_TAG ^ size)?; + write_u32le(&mut dbuf[12..], size)?; + write_u32le(&mut dbuf[20..], size * 8)?; + + if is_intra { + dbuf.extend_from_slice(b"\x0d\x0aVer 3.99.00.00\x0d\x0a\x00"); + while (dbuf.len() & 3) != 0 { + dbuf.push(0); + } + } + + Ok(()) + } +} + +impl NAEncoder for Indeo3Encoder { + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => { + Ok(EncodeParameters { + format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV410_FORMAT)), + ..Default::default() + }) + }, + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + NACodecTypeInfo::Video(vinfo) => { + let pix_fmt = YUV410_FORMAT; + let outinfo = NAVideoInfo::new((vinfo.width + 15) & !15, (vinfo.height + 15) & !15, false, pix_fmt); + let mut ofmt = *encinfo; + ofmt.format = NACodecTypeInfo::Video(outinfo); + Ok(ofmt) + } + } + } + fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => Err(EncoderError::FormatError), + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + NACodecTypeInfo::Video(vinfo) => { + if vinfo.format != YUV410_FORMAT { + return Err(EncoderError::FormatError); + } + if ((vinfo.width | vinfo.height) & 15) != 0 { + return Err(EncoderError::FormatError); + } + if (vinfo.width > 640) || (vinfo.height > 480) { + return Err(EncoderError::FormatError); + } + + self.width = vinfo.width; + self.height = vinfo.height; + + let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, vinfo.format); + let info = NACodecInfo::new("indeo3", NACodecTypeInfo::Video(out_info), None); + let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0); + stream.set_num(stream_id as usize); + let stream = stream.into_ref(); + + self.stream = Some(stream.clone()); + + self.cframe.alloc(vinfo.width, vinfo.height); + self.pframe.alloc(vinfo.width, vinfo.height); + + self.rc.set_bitrate(encinfo.bitrate, encinfo.tb_num, encinfo.tb_den); + self.rc.set_quality(encinfo.quality); + + Ok(stream) + }, + } + } + fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { + let buf = frm.get_buffer(); + if self.debug_tree || self.debug_frm { + println!("frame {}:", self.frameno); + } + + let mut skip_frame = frm.get_frame_type() == FrameType::Skip; + if let NABufferType::None = buf { + skip_frame = true; + } + if skip_frame { + let mut dbuf = Vec::with_capacity(16); + let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); + let mut bw = ByteWriter::new(&mut gw); + + // OS header + bw.write_u32le(self.frameno)?; + bw.write_u32le(HDR_FIELD_2)?; + bw.write_u32le(0)?; // check + bw.write_u32le(0)?; // size + + // bitstream header + bw.write_u16le(32)?; // version + bw.write_u16le(0)?; + bw.write_u32le(0)?; // data size in bits + bw.write_byte(0)?; // cb offset + bw.write_byte(14)?; // reserved + bw.write_u16le(0)?; // checksum + bw.write_u16le(self.height as u16)?; + bw.write_u16le(self.width as u16)?; + + let size = (dbuf.len() - OS_HEADER_SIZE) as u32; + write_u32le(&mut dbuf[8..], self.frameno ^ HDR_FIELD_2 ^ FRMH_TAG ^ size)?; + write_u32le(&mut dbuf[12..], size)?; + write_u32le(&mut dbuf[20..], size * 8)?; + + let fsize = dbuf.len() as u32; + self.rc.advance(fsize); + + self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, false, dbuf)); + return Ok(()); + } + + if let Some(ref vbuf) = buf.get_vbuf() { + let mut dbuf = Vec::with_capacity(16); + let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); + let mut bw = ByteWriter::new(&mut gw); + + let (width, height) = vbuf.get_dimensions(0); + if width != self.width || height != self.height { + self.width = width; + self.height = height; + self.cframe.alloc(width, height); + self.pframe.alloc(width, height); + self.rc.reset(); + } + + let (is_intra, quant) = self.rc.get_quant(self.frameno); + self.cenc.quant = quant; + + if is_intra { + self.buf_sel = false; + } else { + self.buf_sel = !self.buf_sel; + } + + self.cframe.fill(vbuf); + self.cframe.clear_mvs(); + + // OS header + bw.write_u32le(self.frameno)?; + bw.write_u32le(HDR_FIELD_2)?; + bw.write_u32le(0)?; // check + bw.write_u32le(0)?; // size + + // bitstream header + bw.write_u16le(32)?; // version + let mut flags = 0; + if is_intra { + flags |= 0x5; + } else { + flags |= 1; + if self.buf_sel { + flags |= 1 << 9; + } + } + bw.write_u16le(flags)?; + bw.write_u32le(0)?; // data size in bits + bw.write_byte(0)?; // cb offset + bw.write_byte(14)?; // reserved + bw.write_u16le(0)?; // checksum + bw.write_u16le(height as u16)?; + bw.write_u16le(width as u16)?; + for _ in 0..3 { + bw.write_u32le(0)?; // plane data offset + } + bw.write_u32le(0)?; // reserved + bw.write_buf(&CB_SELECTORS)?; + + let mut trees = Vec::with_capacity(PLANE_ORDER.len()); + + // prepare plane data structure + for &planeno in PLANE_ORDER.iter() { + let ref_plane = &self.pframe.plane[planeno]; + let tree = self.cframe.plane[planeno].find_cells(is_intra, ref_plane, &self.mv_est); + if self.debug_tree { + println!(" tree for plane {}:", planeno); + tree.print(); + } + trees.push(tree); + let mvs = &mut self.cframe.plane[planeno].mvs; + compact_mvs(mvs); + } + + self.encode_planes(&mut dbuf, &trees, is_intra)?; + + let cur_quant = self.cenc.quant.unwrap_or(42); + if !is_intra && cur_quant < 8 { + let expected_size = self.rc.get_expected_size(); + if expected_size > 0 { + let cur_size = dbuf.len() as u32; + // try re-encoding frame if possible + if cur_size > expected_size * 3 / 2 { + self.cframe.fill(vbuf); + let new_quant = if cur_quant < 7 { + cur_quant + 1 + } else { + cur_quant - 1 + }; + self.cenc.quant = Some(new_quant); + dbuf.truncate(OS_HEADER_SIZE + BITSTREAM_HEADER_SIZE); + self.encode_planes(&mut dbuf, &trees, is_intra)?; + } + } + } + + if self.debug_frm { + for plane in self.cframe.plane.iter() { + for (y, line) in plane.data.chunks(plane.width).enumerate() { + print!(" {:3}:", y); + for &el in line.iter() { print!(" {:02X}", el); } + println!(); + } + println!(); + } + } + + std::mem::swap(&mut self.cframe, &mut self.pframe); + self.frameno += 1; + + let fsize = dbuf.len() as u32; + self.rc.advance(fsize); + + self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf)); + Ok(()) + } else { + Err(EncoderError::InvalidParameters) + } + } + fn get_packet(&mut self) -> EncoderResult> { + let mut npkt = None; + std::mem::swap(&mut self.pkt, &mut npkt); + Ok(npkt) + } + fn flush(&mut self) -> EncoderResult<()> { + Ok(()) + } +} + +const DEBUG_TREE_OPTION: &str = "debug_tree"; +const DEBUG_FRAME_OPTION: &str = "debug_frame"; +const MV_RANGE_OPTION: &str = "mv_range"; +const MV_FLAT_OPTION: &str = "mv_flat_threshold"; +const MV_THRESHOLD_OPTION: &str = "mv_threshold"; +const CELL_I_THRESHOLD_OPTION: &str = "cell_i_threshold"; +const CELL_P_THRESHOLD_OPTION: &str = "cell_p_threshold"; +const DO_RLE_OPTION: &str = "rle"; +const TRY_AGAIN_OPTION: &str = "try_recompress"; + +const ENCODER_OPTS: &[NAOptionDefinition] = &[ + NAOptionDefinition { + name: KEYFRAME_OPTION, description: KEYFRAME_OPTION_DESC, + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, + NAOptionDefinition { + name: DEBUG_TREE_OPTION, description: "Print frame trees", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: DEBUG_FRAME_OPTION, description: "Print encoder-reconstructed frames", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: MV_RANGE_OPTION, description: "Motion search range", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(120)) }, + NAOptionDefinition { + name: MV_FLAT_OPTION, description: "Threshold for coding cell as skipped one", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(1000)) }, + NAOptionDefinition { + name: MV_THRESHOLD_OPTION, description: "Threshold for coding cell as inter", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(1000)) }, + NAOptionDefinition { + name: CELL_I_THRESHOLD_OPTION, description: "Threshold for coding intra block as flat", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, + NAOptionDefinition { + name: CELL_P_THRESHOLD_OPTION, description: "Threshold for coding inter cell in coarser mode", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, + NAOptionDefinition { + name: DO_RLE_OPTION, description: "Perform zero run length compation", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: TRY_AGAIN_OPTION, description: "Try compressing the frame again for the better bitrate fit", + opt_type: NAOptionDefinitionType::Bool }, +]; + +impl NAOptionHandler for Indeo3Encoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS } + fn set_options(&mut self, options: &[NAOption]) { + for option in options.iter() { + for opt_def in ENCODER_OPTS.iter() { + if opt_def.check(option).is_ok() { + match option.name { + KEYFRAME_OPTION => { + if let NAValue::Int(val) = option.value { + self.rc.set_key_int(val as u32); + } + }, + DEBUG_TREE_OPTION => { + if let NAValue::Bool(val) = option.value { + self.debug_tree = val; + } + }, + DEBUG_FRAME_OPTION => { + if let NAValue::Bool(val) = option.value { + self.debug_frm = val; + } + }, + MV_RANGE_OPTION => { + if let NAValue::Int(val) = option.value { + self.mv_est.mv_range = val as i8; + } + }, + MV_FLAT_OPTION => { + if let NAValue::Int(val) = option.value { + self.mv_est.flat_thr = val as u16; + } + }, + MV_THRESHOLD_OPTION => { + if let NAValue::Int(val) = option.value { + self.mv_est.mv_thr = val as u16; + } + }, + CELL_I_THRESHOLD_OPTION => { + if let NAValue::Int(val) = option.value { + self.cenc.flat_thr_i = val as u32; + } + }, + CELL_P_THRESHOLD_OPTION => { + if let NAValue::Int(val) = option.value { + self.cenc.flat_thr_p = val as u32; + } + }, + DO_RLE_OPTION => { + if let NAValue::Bool(val) = option.value { + self.cenc.do_rle = val; + } + }, + TRY_AGAIN_OPTION => { + if let NAValue::Bool(val) = option.value { + self.try_again = val; + } + }, + _ => {}, + }; + } + } + } + } + fn query_option_value(&self, name: &str) -> Option { + match name { + KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.rc.get_key_int()))), + DEBUG_TREE_OPTION => Some(NAValue::Bool(self.debug_tree)), + DEBUG_FRAME_OPTION => Some(NAValue::Bool(self.debug_frm)), + MV_RANGE_OPTION => Some(NAValue::Int(i64::from(self.mv_est.mv_range))), + MV_FLAT_OPTION => Some(NAValue::Int(i64::from(self.mv_est.flat_thr))), + MV_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.mv_est.mv_thr))), + CELL_I_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.cenc.flat_thr_i))), + CELL_P_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.cenc.flat_thr_p))), + DO_RLE_OPTION => Some(NAValue::Bool(self.cenc.do_rle)), + TRY_AGAIN_OPTION => Some(NAValue::Bool(self.try_again)), + _ => None, + } + } +} + +pub fn get_encoder() -> Box { + Box::new(Indeo3Encoder::new()) +} + +#[cfg(test)] +mod test { + use crate::*; + use nihav_core::codecs::*; + use nihav_core::demuxers::*; + use nihav_core::muxers::*; + use nihav_commonfmt::*; + use nihav_codec_support::test::enc_video::*; + + #[allow(unused_variables)] + fn encode_test(name: &'static str, enc_options: &[NAOption], limit: Option, hash: &[u32; 4]) { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + indeo_register_all_decoders(&mut dec_reg); + let mut mux_reg = RegisteredMuxers::new(); + generic_register_all_muxers(&mut mux_reg); + let mut enc_reg = RegisteredEncoders::new(); + indeo_register_all_encoders(&mut enc_reg); + + let dec_config = DecoderTestParams { + demuxer: "avi", + in_name: "assets/Indeo/laser05.avi", + stream_type: StreamType::Video, + limit, + dmx_reg, dec_reg, + }; + let enc_config = EncoderTestParams { + muxer: "avi", + enc_name: "indeo3", + out_name: name, + mux_reg, enc_reg, + }; + let dst_vinfo = NAVideoInfo { + width: 0, + height: 0, + format: YUV410_FORMAT, + flipped: false, + bits: 9, + }; + let enc_params = EncodeParameters { + format: NACodecTypeInfo::Video(dst_vinfo), + quality: 0, + bitrate: 25000 * 8, + tb_num: 0, + tb_den: 0, + flags: 0, + }; + //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options); + test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash); + } + #[test] + fn test_indeo3_encoder1() { + let enc_options = &[ + NAOption { name: super::TRY_AGAIN_OPTION, value: NAValue::Bool(true) }, + ]; + encode_test("indeo3.avi", enc_options, Some(4), &[0x4cc927d3, 0x9872f824, 0x92dee9cb, 0xaf912ecc]); + } + /*#[test] + fn test_indeo3_roundtrip() { + const YPATTERN: [u8; 16] = [32, 72, 40, 106, 80, 20, 33, 58, 77, 140, 121, 100, 83, 57, 30, 11]; + const CPATTERN: [u8; 4] = [0x80; 4]; + + let dst_vinfo = NAVideoInfo { + width: 16, + height: 16, + format: YUV410_FORMAT, + flipped: false, + bits: 9, + }; + let enc_params = EncodeParameters { + format: NACodecTypeInfo::Video(dst_vinfo), + quality: 0, + bitrate: 0, + tb_num: 0, + tb_den: 0, + flags: 0, + }; + + let mut ienc = super::get_encoder(); + ienc.init(0, enc_params).unwrap(); + let mut buffer = alloc_video_buffer(dst_vinfo, 2).unwrap(); + if let NABufferType::Video(ref mut buf) = buffer { + let vbuf = NASimpleVideoFrame::from_video_buf(buf).unwrap(); + for i in 0..16 { + vbuf.data[vbuf.offset[0] + i * vbuf.stride[0]..][..16].copy_from_slice(&YPATTERN); + } + for plane in 1..3 { + for i in 0..4 { + vbuf.data[vbuf.offset[plane] + i * vbuf.stride[plane]..][..4].copy_from_slice(&CPATTERN); + } + } + } + let info = NACodecInfo::new("indeo3", NACodecTypeInfo::Video(dst_vinfo), None).into_ref(); + let frm = NAFrame::new(NATimeInfo::new(Some(0), None, None, 1, 12), FrameType::I, true, info.clone(), buffer); + ienc.encode(&frm).unwrap(); + let pkt = ienc.get_packet().unwrap().unwrap(); + println!(" pkt size {}", pkt.get_buffer().len()); + + let mut dec_reg = RegisteredDecoders::new(); + indeo_register_all_decoders(&mut dec_reg); + let decfunc = dec_reg.find_decoder("indeo3").unwrap(); + let mut dec = (decfunc)(); + let mut dsupp = Box::new(NADecoderSupport::new()); + dec.init(&mut dsupp, info).unwrap(); + let dst = dec.decode(&mut dsupp, &pkt).unwrap(); + if let NABufferType::Video(ref vbuf) = dst.get_buffer() { + for plane in 0..3 { + let size = if plane == 0 { 16 } else { 4 }; + let start = vbuf.get_offset(plane); + for line in vbuf.get_data()[start..].chunks(vbuf.get_stride(plane)).take(size) { + print!(" "); + for &el in line[..size].iter() { + print!(" {:02X}", el >> 1); + } + println!(); + } + if plane == 0 { + print!("ref"); + for &el in YPATTERN.iter() { print!(" {:02X}", el >> 1); } println!(); + } + println!(); + } + } + panic!("end"); + }*/ +} diff --git a/nihav-indeo/src/codecs/indeo3enc/mv.rs b/nihav-indeo/src/codecs/indeo3enc/mv.rs new file mode 100644 index 0000000..498a6ef --- /dev/null +++ b/nihav-indeo/src/codecs/indeo3enc/mv.rs @@ -0,0 +1,153 @@ +use super::{Indeo3Cell, Plane}; + +const MV_THRESHOLD: u16 = 64; +const FLAT_THRESHOLD: u16 = 8; + +const DIA_LARGE: [(i8, i8); 5] = [(0, 0), (2, 0), (0, 2), (-2, 0), (0, -2)]; +const DIA_SMALL: [(i8, i8); 5] = [(0, 0), (1, 0), (0, 1), (-1, 0), (0, -1)]; +const SEARCH_RANGE: i8 = 16; + +#[derive(Clone, Copy, PartialEq)] +pub struct MV { + pub x: i8, + pub y: i8 +} + +pub struct MotionEstimator { + pub mv_range: i8, + pub flat_thr: u16, + pub mv_thr: u16, +} + +impl MotionEstimator { + pub fn new() -> Self { + Self { + mv_range: SEARCH_RANGE, + flat_thr: FLAT_THRESHOLD, + mv_thr: MV_THRESHOLD, + } + } + pub fn mv_search(&self, cur: &Plane, prev: &Plane, cell: Indeo3Cell) -> Option<(MV, bool)> { + let plane_w = prev.width as isize; + let plane_h = prev.height as isize; + let cell_w = cell.get_width() as isize; + let cell_h = cell.get_height() as isize; + let start_x = cell.get_x() as isize; + let start_y = cell.get_y() as isize; + + let check_mv = |mv: MV| { + if mv.x.abs() < SEARCH_RANGE && mv.y.abs() < SEARCH_RANGE { + let new_x = start_x + isize::from(mv.x); + let new_y = start_y + isize::from(mv.y); + new_x >= 0 && new_x + cell_w <= plane_w && new_y >= 0 && new_y + cell_h <= plane_h + } else { + false + } + }; + + let area = (cell.get_width() * cell.get_height()) as u32; + let flat_thr = u32::from(self.flat_thr) * area; + + let mut best_mv = MV{ x: 0, y: 0 }; + let mut best_score = calc_mv_score(cur, prev, cell, best_mv); + + if best_score < flat_thr { + return Some((best_mv, true)); + } + + let mut found_better = true; + while found_better { + found_better = false; + for step in DIA_LARGE.iter() { + let new_mv = MV{ x: best_mv.x + step.0, y: best_mv.y + step.1 }; + if !check_mv(new_mv) { + continue; + } + let score = calc_mv_score(cur, prev, cell, new_mv); + if score < best_score { + best_mv = new_mv; + best_score = score; + found_better = true; + if best_score < flat_thr { + return Some((best_mv, true)); + } + } + } + } + for step in DIA_SMALL.iter() { + let new_mv = MV{ x: best_mv.x + step.0, y: best_mv.y + step.1 }; + if !check_mv(new_mv) { + continue; + } + let score = calc_mv_score(cur, prev, cell, new_mv); + if score < best_score { + best_mv = new_mv; + best_score = score; + if best_score < flat_thr { + break; + } + } + } + let score = (best_score / area) as u16; + if score < self.mv_thr { + Some((best_mv, false)) + } else { + None + } + } +} + +fn calc_cell_diff(src1: &[u8], src2: &[u8], stride: usize, width: usize, height: usize) -> u32 { + let mut score = 0; + for (line1, line2) in src1.chunks(stride).zip(src2.chunks(stride)).take(height) { + for (&a, &b) in line1.iter().zip(line2.iter()).take(width) { + let diff = if a >= b { u32::from(a - b) } else { u32::from(b - a) }; + score += diff * diff; + } + } + score +} + +fn calc_mv_score(cur: &Plane, prev: &Plane, cell: Indeo3Cell, mv: MV) -> u32 { + let xoff = (cell.get_x() as isize + isize::from(mv.x)) as usize; + let yoff = (cell.get_y() as isize + isize::from(mv.y)) as usize; + + let cur_ptr = &cur.data[cell.get_x() + (cell.get_y() + 1) * cur.width..]; + let ref_ptr = &prev.data[xoff + (yoff + 1) * prev.width..]; + + calc_cell_diff(cur_ptr, ref_ptr, cur.width, cell.get_width(), cell.get_height()) +} + +fn get_mv_diff(mv1: MV, mv2: MV) -> i8 { + (mv1.x - mv2.x).abs() + (mv1.y - mv2.y).abs() +} + +pub fn compact_mvs(mvs: &mut Vec<(MV, u16)>) { + mvs.sort_by(|a, b| a.1.cmp(&b.1)); + while mvs.len() > 256 { + let (mv, _) = mvs.pop().unwrap(); + if let Some(idx) = find_mv(mv, mvs) { + mvs[usize::from(idx)].1 += 1; + } + } +} + +pub fn find_mv(mv: MV, mvs: &[(MV, u16)]) -> Option { + let mut best_idx = None; + let mut best_cand_diff = 42; + let mut best_cand_count = 0; + for (i, &(cand_mv, count)) in mvs.iter().enumerate() { + if cand_mv == mv { + return Some(i as u8); + } + let diff = get_mv_diff(mv, cand_mv); + if diff <= 2 { + if diff < best_cand_diff || (diff == best_cand_diff && count > best_cand_count) { + best_idx = Some(i as u8); + best_cand_diff = diff; + best_cand_count = count; + } + } + } + best_idx +} diff --git a/nihav-indeo/src/codecs/indeo3enc/ratectl.rs b/nihav-indeo/src/codecs/indeo3enc/ratectl.rs new file mode 100644 index 0000000..4bb74c0 --- /dev/null +++ b/nihav-indeo/src/codecs/indeo3enc/ratectl.rs @@ -0,0 +1,77 @@ +#[derive(Default)] +pub struct RateControl { + bitrate: u32, + br_pool: u32, + f_pos: u32, + fracs: u32, + key_int: u32, + tb_num: u32, + tb_den: u32, + quality: Option, +} + +impl RateControl { + pub fn new() -> Self { Self{ key_int: 10, ..Default::default() } } + pub fn set_quality(&mut self, quality: u8) { + if quality > 0 { + self.quality = Some((quality - 1).min(15)); + } else { + self.quality = None; + } + } + pub fn set_bitrate(&mut self, br: u32, tb_num: u32, tb_den: u32) { + if tb_num > 0 && tb_den > 0 { + self.bitrate = br / 8; + self.tb_num = tb_num; + self.tb_den = tb_den; + } else { + self.bitrate = 0; + self.tb_num = 0; + self.tb_den = 0; + } + self.quality = Some(0); + self.reset(); + } + pub fn set_key_int(&mut self, key_int: u32) { + self.key_int = key_int; + self.reset(); + } + pub fn reset(&mut self) { + self.br_pool = self.bitrate; + self.f_pos = 0; + self.fracs = self.tb_den; + } + pub fn get_key_int(&self) -> u32 { self.key_int } + pub fn get_quant(&self, frameno: u32) -> (bool, Option) { + let is_intra = self.key_int == 0 || (frameno % self.key_int) == 0; + (is_intra, self.quality) + } + pub fn advance(&mut self, size: u32) { + if self.bitrate != 0 { + let expected = self.get_expected_size(); + let cur_quant = self.quality.unwrap_or(0); + if (size > expected + expected / 10) && (cur_quant < 7) { + self.quality = Some(cur_quant + 1); + } else if (size < expected - expected / 10) && (cur_quant > 0) { + self.quality = Some(cur_quant - 1); + } + + self.f_pos += self.tb_num; + while self.f_pos >= self.tb_den { + self.f_pos -= self.tb_den; + self.br_pool += self.bitrate; + self.fracs += self.tb_den; + } + self.fracs -= self.tb_num; + + self.br_pool = self.br_pool.saturating_sub(size).min(self.bitrate * 3); + } + } + pub fn get_expected_size(&self) -> u32 { + if self.bitrate != 0 { + self.br_pool * self.tb_num / self.fracs + } else { + 0 + } + } +} diff --git a/nihav-indeo/src/codecs/indeo3enc/tree.rs b/nihav-indeo/src/codecs/indeo3enc/tree.rs new file mode 100644 index 0000000..8c51ad7 --- /dev/null +++ b/nihav-indeo/src/codecs/indeo3enc/tree.rs @@ -0,0 +1,432 @@ +use super::Indeo3Writer; +use super::mv::*; +use super::cell::{CellEncoder, MAX_CELL_SIZE}; + +pub enum Indeo3PrimaryTree { + VSplit(Box, Box), + HSplit(Box, Box), + RelFill(MV, Box), + AbsFill(Box), +} + +impl Indeo3PrimaryTree { + pub fn print(&self) { + println!("Plane tree:"); + self.print1(1); + } + fn print1(&self, depth: u8) { + for _ in 0..depth { + print!(" "); + } + match self { + Indeo3PrimaryTree::VSplit(t1, t2) => { + println!("vertical split"); + t1.print1(depth + 1); + t2.print1(depth + 1); + }, + Indeo3PrimaryTree::HSplit(t1, t2) => { + println!("horizontal split"); + t1.print1(depth + 1); + t2.print1(depth + 1); + }, + Indeo3PrimaryTree::RelFill(mv, sec) => { + println!("relative fill {},{}", mv.x, mv.y); + sec.print1(depth + 1); + }, + Indeo3PrimaryTree::AbsFill(sec) => { + println!("absolute fill"); + sec.print1(depth + 1); + } + } + } +} + +pub enum Indeo3SecondaryTree { + VSplit(Box, Box), + HSplit(Box, Box), + VQData(u8), + VQNull(u8), +} + +impl Indeo3SecondaryTree { + fn print1(&self, depth: u8) { + for _ in 0..depth { + print!(" "); + } + match self { + Indeo3SecondaryTree::VSplit(t1, t2) => { + println!("vertical split"); + t1.print1(depth + 1); + t2.print1(depth + 1); + }, + Indeo3SecondaryTree::HSplit(t1, t2) => { + println!("horizontal split"); + t1.print1(depth + 1); + t2.print1(depth + 1); + }, + Indeo3SecondaryTree::VQData(mode) => { + println!("VQ data ({})", mode); + }, + Indeo3SecondaryTree::VQNull(mode) => { + println!("VQ Null ({})", mode); + } + } + } +} + +const THRESHOLD: u32 = 64; + +#[derive(Clone, Copy)] +pub struct Indeo3Cell { + x: u8, + y: u8, + w: u8, + h: u8, + intra: bool, +} + +impl Indeo3Cell { + pub fn new(width: usize, height: usize, intra: bool) -> Self { + Self { + x: 0, + y: 0, + w: (width / 4) as u8, + h: (height / 4) as u8, + intra, + } + } + + pub fn get_x(&self) -> usize { usize::from(self.x) * 4 } + pub fn get_y(&self) -> usize { usize::from(self.y) * 4 } + pub fn get_width(&self) -> usize { usize::from(self.w) * 4 } + pub fn get_height(&self) -> usize { usize::from(self.h) * 4 } + pub fn is_intra(&self) -> bool { self.intra } + + fn split_h(&self) -> (Self, Self) { + let h1 = if self.h > 2 { ((self.h + 2) >> 2) << 1 } else { 1 }; + let h2 = self.h - h1; + let mut cell1 = *self; + cell1.h = h1; + let mut cell2 = *self; + cell2.y += h1; + cell2.h = h2; + (cell1, cell2) + } + fn split_v(&self, stripw: u8) -> (Self, Self) { + let w1 = if self.w > stripw { + if self.w > stripw * 2 { stripw * 2 } else { stripw } + } else { + if self.w > 2 { ((self.w + 2) >> 2) << 1 } else { 1 } + }; + let w2 = self.w - w1; + let mut cell1 = *self; + cell1.w = w1; + let mut cell2 = *self; + cell2.x += w1; + cell2.w = w2; + (cell1, cell2) + } +} + +impl std::fmt::Display for Indeo3Cell { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "[{}x{} @ {},{}{}]", self.get_width(), self.get_height(), self.get_x(), self.get_y(), if self.intra { " intra" } else { "" }) + } +} + +#[derive(Default)] +pub struct Plane { + pub data: Vec, + pub width: usize, + pub height: usize, + pub stripw: u8, + pub mvs: Vec<(MV, u16)>, +} + +fn ssd(a: u8, b: u8) -> u32 { + let diff = i32::from(a) - i32::from(b); + (diff * diff) as u32 +} + +impl Plane { + pub fn alloc(&mut self, width: usize, height: usize, stripw: u8) { + self.data.resize(width * height, 0); + self.width = width; + self.height = height; + self.stripw = stripw; + if self.mvs.capacity() < 256 { + self.mvs.reserve(256); + } + } + pub fn fill(&mut self, src: &[u8], stride: usize) { + for (dline, sline) in self.data.chunks_mut(self.width).zip(src.chunks(stride)) { + for (dst, &src) in dline.iter_mut().zip(sline.iter()) { + *dst = src >> 1; + } + } + } + pub fn clear_mvs(&mut self){ + self.mvs.clear(); + } + pub fn checksum(&self) -> u16 { + let xors = self.data[self.width..].chunks(2).fold([0u8; 2], |acc, pair| [acc[0] ^ pair[0], acc[1] ^ pair[1]]); + u16::from(xors[0]) | (u16::from(xors[1]) * 256) + } + pub fn find_cells(&mut self, is_intra: bool, pplane: &Plane, mv_est: &MotionEstimator) -> Box { + let cell = Indeo3Cell::new(self.width, self.height, is_intra); + self.split_pri(cell, pplane, mv_est) + } + fn split_pri(&mut self, cell: Indeo3Cell, pplane: &Plane, mv_est: &MotionEstimator) -> Box { + let width = cell.get_width(); + let height = cell.get_height(); + if width * height > MAX_CELL_SIZE { + let (hsplit, vsplit) = if width != height { + (width > (self.stripw as usize) * 4 || width > height, height > width) + } else { + let (hdiff, vdiff) = self.calculate_diffs(cell); + (vdiff > THRESHOLD && vdiff > hdiff, + hdiff > THRESHOLD && hdiff > vdiff) + }; + match (hsplit, vsplit) { + (true, _) => { + let (cell1, cell2) = cell.split_v(self.stripw); + let tree1 = self.split_pri(cell1, pplane, mv_est); + let tree2 = self.split_pri(cell2, pplane, mv_est); + Box::new(Indeo3PrimaryTree::VSplit(tree1, tree2)) + }, + (_, true) => { + let (cell1, cell2) = cell.split_h(); + let tree1 = self.split_pri(cell1, pplane, mv_est); + let tree2 = self.split_pri(cell2, pplane, mv_est); + Box::new(Indeo3PrimaryTree::HSplit(tree1, tree2)) + }, + (false, false) => { + let sec = self.split_sec(cell); + Box::new(Indeo3PrimaryTree::AbsFill(sec)) + }, + } + } else { + if !cell.intra { + if let Some((mv, flat)) = mv_est.mv_search(self, pplane, cell) { + return self.add_mv_tree(mv, flat); + } + + // try splitting once to see if it improves situation + if width >= 16 && height >= 16 { + let vsplit = width > height; + let (cell1, cell2) = if vsplit { + cell.split_v(self.stripw) + } else { + cell.split_h() + }; + let search1 = mv_est.mv_search(self, pplane, cell1); + let search2 = mv_est.mv_search(self, pplane, cell2); + if search1.is_some() || search2.is_some() { + let tree1 = if let Some((mv, flat)) = search1 { + self.add_mv_tree(mv, flat) + } else { + let sec = self.split_sec(cell1); + Box::new(Indeo3PrimaryTree::AbsFill(sec)) + }; + let tree2 = if let Some((mv, flat)) = search2 { + self.add_mv_tree(mv, flat) + } else { + let sec = self.split_sec(cell2); + Box::new(Indeo3PrimaryTree::AbsFill(sec)) + }; + return if vsplit { + Box::new(Indeo3PrimaryTree::VSplit(tree1, tree2)) + } else { + Box::new(Indeo3PrimaryTree::HSplit(tree1, tree2)) + } + } + } + } + let sec = self.split_sec(cell); + Box::new(Indeo3PrimaryTree::AbsFill(sec)) + } + } + fn add_mv_tree(&mut self, mv: MV, flat: bool) -> Box { + let sec = if flat { + Box::new(Indeo3SecondaryTree::VQNull(0)) + } else { + Box::new(Indeo3SecondaryTree::VQData(0)) + }; + + let mut found = false; + for (ref cmv, ref mut count) in self.mvs.iter_mut() { + if cmv == &mv { + *count += 1; + found = true; + break; + } + } + if !found { + self.mvs.push((mv, 1)); + } + + Box::new(Indeo3PrimaryTree::RelFill(mv, sec)) + } + fn split_sec(&mut self, cell: Indeo3Cell) -> Box { + let (hdiff, vdiff) = self.calculate_diffs(cell); + if hdiff == 0 && vdiff == 0 { + if !cell.intra { + return Box::new(Indeo3SecondaryTree::VQNull(0)); + } else { + return Box::new(Indeo3SecondaryTree::VQData(0)); + } + } + if cell.get_width() > 16 && cell.get_height() > 16 { + let hsplit = vdiff > THRESHOLD && vdiff > hdiff * 2; + let vsplit = hdiff > THRESHOLD && hdiff > vdiff * 2; + match (vsplit, hsplit) { + (true, _) => { + let (cell1, cell2) = cell.split_v(self.stripw); + let tree1 = self.split_sec(cell1); + let tree2 = self.split_sec(cell2); + Box::new(Indeo3SecondaryTree::VSplit(tree1, tree2)) + }, + (_, true) => { + let (cell1, cell2) = cell.split_h(); + let tree1 = self.split_sec(cell1); + let tree2 = self.split_sec(cell2); + Box::new(Indeo3SecondaryTree::HSplit(tree1, tree2)) + }, + _ => { + Box::new(Indeo3SecondaryTree::VQData(0)) + }, + } + } else { + let is_w8 = (cell.get_width() & 7) == 0; + let is_h8 = (cell.get_height() & 7) == 0; + let mode = match (hdiff > THRESHOLD, vdiff > THRESHOLD) { + (false, false) if is_w8 && is_h8 => 10, + (_, true) if is_h8 => 3, + _ => 0, + }; + Box::new(Indeo3SecondaryTree::VQData(mode)) + } + } + fn calculate_diffs(&self, cell: Indeo3Cell) -> (u32, u32) { + let offset = cell.get_x() + cell.get_y() * self.width; + let mut w = cell.get_width(); + if cell.get_x() + w == self.width { w -= 1; } + let mut h = cell.get_height(); + if cell.get_y() + h == self.height { h -= 1; } + + let mut vdiff = 0; + let mut hdiff = 0; + let src0 = &self.data[offset..]; + let src1 = &self.data[offset + self.width..]; + for (line0, line1) in src0.chunks(self.width).zip(src1.chunks(self.width)).take(h) { + for ((&cur, &right), &bottom) in line0.iter().zip(line0[1..].iter()).zip(line1.iter()).take(w) { + hdiff += ssd(cur, right); + vdiff += ssd(cur, bottom); + } + } + let area = (w * h) as u32; + (hdiff * 16 / area, vdiff * 16 / area) + } + pub fn encode_tree(&mut self, iw: &mut Indeo3Writer, tree: &Indeo3PrimaryTree, cenc: &mut CellEncoder, is_intra: bool, refp: &Plane) { + let cell = Indeo3Cell::new(self.width, self.height, is_intra); + self.encode_pri(iw, cell, tree, cenc, refp); + } + fn encode_pri(&mut self, iw: &mut Indeo3Writer, mut cell: Indeo3Cell, tree: &Indeo3PrimaryTree, cenc: &mut CellEncoder, refp: &Plane) { + match tree { + Indeo3PrimaryTree::HSplit(t1, t2) => { + iw.put_2bits(0); + let (cell1, cell2) = cell.split_h(); + self.encode_pri(iw, cell1, t1, cenc, refp); + self.encode_pri(iw, cell2, t2, cenc, refp); + }, + Indeo3PrimaryTree::VSplit(t1, t2) => { + iw.put_2bits(1); + let (cell1, cell2) = cell.split_v(self.stripw); + self.encode_pri(iw, cell1, t1, cenc, refp); + self.encode_pri(iw, cell2, t2, cenc, refp); + }, + Indeo3PrimaryTree::AbsFill(sec) => { + iw.put_2bits(2); + cell.intra = true; + self.encode_sec(iw, cell, sec, cenc); + } + Indeo3PrimaryTree::RelFill(mv, sec) => { + if let Some(mv_idx) = find_mv(*mv, &self.mvs) { + iw.put_2bits(3); + iw.put_byte(mv_idx); + cell.intra = false; + let real_mv = self.mvs[usize::from(mv_idx)].0; + self.encode_sec_inter(iw, cell, sec, cenc, real_mv, refp); + } else { + iw.put_2bits(2); + cell.intra = true; + self.encode_sec(iw, cell, sec, cenc); + } + }, + } + } + fn encode_sec(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, tree: &Indeo3SecondaryTree, cenc: &mut CellEncoder) { + match tree { + Indeo3SecondaryTree::HSplit(t1, t2) => { + iw.put_2bits(0); + let (cell1, cell2) = cell.split_h(); + self.encode_sec(iw, cell1, t1, cenc); + self.encode_sec(iw, cell2, t2, cenc); + }, + Indeo3SecondaryTree::VSplit(t1, t2) => { + iw.put_2bits(1); + let (cell1, cell2) = cell.split_v(self.stripw); + self.encode_sec(iw, cell1, t1, cenc); + self.encode_sec(iw, cell2, t2, cenc); + }, + Indeo3SecondaryTree::VQNull(mode) => { + iw.put_2bits(2); + iw.put_2bits(*mode); + }, + Indeo3SecondaryTree::VQData(mode) => { + iw.put_2bits(3); + self.encode_cell_data_intra(iw, cell, cenc, *mode); + }, + } + } + fn encode_sec_inter(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, tree: &Indeo3SecondaryTree, cenc: &mut CellEncoder, mv: MV, refp: &Plane) { + match tree { + Indeo3SecondaryTree::HSplit(_t1, _t2) => { + unimplemented!(); + }, + Indeo3SecondaryTree::VSplit(_t1, _t2) => { + unimplemented!(); + }, + Indeo3SecondaryTree::VQNull(mode) => { + iw.put_2bits(2); + iw.put_2bits(*mode); + cenc.read_mv_buffer(refp, cell, mv); + cenc.null_mv(); + cenc.put_buffer(self); + }, + Indeo3SecondaryTree::VQData(_mode) => { + iw.put_2bits(3); + self.encode_cell_data_inter(iw, cell, cenc, mv, refp); + }, + } + } + fn encode_cell_data_intra(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, cenc: &mut CellEncoder, mode: u8) { + cenc.read_buffer(self, cell); + cenc.gen_diffs_intra(); + cenc.compress_intra(mode); + cenc.put_buffer(self); + for &b in cenc.out[..cenc.osize].iter() { + iw.put_byte(b); + } + } + fn encode_cell_data_inter(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, cenc: &mut CellEncoder, mv: MV, refp: &Plane) { + cenc.read_buffer(self, cell); + cenc.read_mv_buffer(refp, cell, mv); + cenc.gen_diffs_inter(); + cenc.compress_inter(); + cenc.put_buffer(self); + for &b in cenc.out[..cenc.osize].iter() { + iw.put_byte(b); + } + } +} diff --git a/nihav-indeo/src/codecs/mod.rs b/nihav-indeo/src/codecs/mod.rs index 9525d7c..85523d6 100644 --- a/nihav-indeo/src/codecs/mod.rs +++ b/nihav-indeo/src/codecs/mod.rs @@ -15,7 +15,7 @@ mod intel263; mod indeo2; #[cfg(feature="decoder_indeo3")] mod indeo3; -#[cfg(feature="decoder_indeo3")] +#[cfg(any(feature="decoder_indeo3", feature="encoder_indeo3"))] mod indeo3data; #[cfg(feature="decoder_indeo4")] mod indeo4; @@ -61,3 +61,19 @@ pub fn indeo_register_all_decoders(rd: &mut RegisteredDecoders) { rd.add_decoder(*decoder); } } + +#[cfg(feature="encoder_indeo3")] +mod indeo3enc; + +const INDEO_ENCODERS: &[EncoderInfo] = &[ +#[cfg(feature="encoder_indeo3")] + EncoderInfo { name: "indeo3", get_encoder: indeo3enc::get_encoder }, +]; + +/// Registers all available encoders provided by this crate. +pub fn indeo_register_all_encoders(re: &mut RegisteredEncoders) { + for encoder in INDEO_ENCODERS.iter() { + re.add_encoder(*encoder); + } +} + diff --git a/nihav-indeo/src/lib.rs b/nihav-indeo/src/lib.rs index c709b23..22ef878 100644 --- a/nihav-indeo/src/lib.rs +++ b/nihav-indeo/src/lib.rs @@ -11,6 +11,7 @@ extern crate nihav_codec_support; mod codecs; pub use crate::codecs::indeo_register_all_decoders; +pub use crate::codecs::indeo_register_all_encoders; mod demuxers; -- 2.30.2