From: Kostya Shishkov Date: Sat, 16 May 2026 14:32:28 +0000 (+0200) Subject: SMC encoder X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=b2b34419115db286f5856d2023237d4026ab9175;p=nihav.git SMC encoder --- diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index 9564a2c..586347e 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -10,7 +10,7 @@ features = [] [dependencies.nihav_codec_support] path = "../nihav-codec-support" -features = ["blockdsp", "fft", "qmf", "qt_pal"] +features = ["blockdsp", "fft", "qmf", "qt_pal", "vq"] [dev-dependencies] nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers", "muxer_mov", "decoder_pcm"] } @@ -46,10 +46,11 @@ demuxer_warhol = ["demuxers"] all_encoders = ["all_video_encoders", "all_audio_encoders"] encoders = [] -all_video_encoders = ["encoder_rawvid", "encoder_rle", "encoder_rpza"] +all_video_encoders = ["encoder_rawvid", "encoder_rle", "encoder_rpza", "encoder_smc"] encoder_rawvid = ["encoders"] encoder_rle = ["encoders"] encoder_rpza = ["encoders"] +encoder_smc = ["encoders"] all_audio_encoders = ["encoder_ima_adpcm_qt", "encoder_mace"] encoder_ima_adpcm_qt = ["encoders"] diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index a058a3b..7e2f485 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -124,6 +124,8 @@ mod rawvidenc; mod rleenc; #[cfg(feature="encoder_rpza")] mod rpzaenc; +#[cfg(feature="encoder_smc")] +mod smcenc; #[cfg(feature="encoder_ima_adpcm_qt")] mod imaadpcmenc; @@ -140,6 +142,8 @@ const QT_ENCODERS: &[EncoderInfo] = &[ EncoderInfo { name: "qt-rle", get_encoder: rleenc::get_encoder }, #[cfg(feature="encoder_rpza")] EncoderInfo { name: "apple-video", get_encoder: rpzaenc::get_encoder }, +#[cfg(feature="encoder_smc")] + EncoderInfo { name: "qt-smc", get_encoder: smcenc::get_encoder }, #[cfg(feature="encoder_ima_adpcm_qt")] EncoderInfo { name: "ima-adpcm-qt", get_encoder: imaadpcmenc::get_encoder }, diff --git a/nihav-qt/src/codecs/smcenc.rs b/nihav-qt/src/codecs/smcenc.rs new file mode 100644 index 0000000..80c2ea5 --- /dev/null +++ b/nihav-qt/src/codecs/smcenc.rs @@ -0,0 +1,908 @@ +use std::convert::TryInto; +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use nihav_codec_support::vq::*; + +#[derive(Clone,Copy,Default,PartialEq)] +enum Strategy { + #[default] + Lossless, + Max2Clr, + Max4Clr, + Max8Clr, +} + +#[derive(Clone,PartialEq)] +enum Opcode { + None, + Skip(usize), + Repeat(usize), + RepeatTwo(usize), + Fill(usize, u8), + TwoColours(usize, [u8; 2], [u16; 16]), + FourColours(usize, [u8; 4], [u32; 16]), + EightColours(usize, [u8; 8], [[u16; 3]; 16]), + Raw(usize), +} + +impl Opcode { + fn is_skip(&self) -> bool { matches!(*self, Opcode::Skip(_)) } +} + +struct EncoderHelper { + pairs: [[u8; 2]; 256], + ppos: usize, + pcount: usize, + quads: [[u8; 4]; 256], + qpos: usize, + qcount: usize, + octets: [[u8; 8]; 256], + opos: usize, + ocount: usize, +} + +impl Default for EncoderHelper { + fn default() -> Self { + let tmp = std::mem::MaybeUninit::::zeroed(); + unsafe { tmp.assume_init() } + } +} + +impl EncoderHelper { + fn reset(&mut self) { + *self = EncoderHelper::default(); + } + fn write_token(&mut self, dbuf: &mut Vec, token: &Opcode, blocks: &[[u8; 16]], pos: usize) { + match *token { + Opcode::None => {}, + Opcode::Skip(skip_len) => { + let mut to_write = skip_len; + while to_write > 16 { + let len = to_write.min(256); + dbuf.push(0x10); + dbuf.push((len - 1) as u8); + to_write -= len; + } + if to_write > 0 { + dbuf.push((to_write - 1) as u8); + } + }, + Opcode::Repeat(repeat_len) => { + let mut to_write = repeat_len; + while to_write > 16 { + let len = to_write.min(256); + dbuf.push(0x30); + dbuf.push((len - 1) as u8); + to_write -= len; + } + if to_write > 0 { + dbuf.push(0x20 | ((to_write - 1) as u8)); + } + }, + Opcode::RepeatTwo(repeat_len) => { + let mut to_write = repeat_len; + while to_write > 16 { + let len = to_write.min(256); + dbuf.push(0x50); + dbuf.push((len - 1) as u8); + to_write -= len; + } + if to_write > 0 { + dbuf.push(0x40 | ((to_write - 1) as u8)); + } + }, + Opcode::Fill(run_len, clr) => { + let mut to_write = run_len; + while to_write > 16 { + let len = to_write.min(256); + dbuf.push(0x70); + dbuf.push((len - 1) as u8); + dbuf.push(clr); + to_write -= len; + } + if to_write > 0 { + dbuf.push(0x60 | ((to_write - 1) as u8)); + dbuf.push(clr); + } + }, + Opcode::TwoColours(count, pair, flags) => { + if let Some(idx) = self.pairs.iter().take(self.pcount).position(|pp| pp == &pair) { + dbuf.push(0x90 | ((count - 1) as u8)); + dbuf.push(idx as u8); + } else { + self.pairs[self.ppos] = pair; + self.ppos = (self.ppos + 1) & 0xFF; + if self.pcount < 256 { + self.pcount += 1; + } + dbuf.push(0x80 | ((count - 1) as u8)); + dbuf.extend_from_slice(&pair); + } + let mut tmp = [0; 2]; + for &flg in flags[..count].iter() { + let _ = write_u16be(&mut tmp, flg); + dbuf.extend_from_slice(&tmp); + } + }, + Opcode::FourColours(count, quad, flags) => { + if let Some(idx) = self.quads.iter().take(self.qcount).position(|qq| qq == &quad) { + dbuf.push(0xB0 | ((count - 1) as u8)); + dbuf.push(idx as u8); + } else { + self.quads[self.qpos] = quad; + self.qpos = (self.qpos + 1) & 0xFF; + if self.qcount < 256 { + self.qcount += 1; + } + dbuf.push(0xA0 | ((count - 1) as u8)); + dbuf.extend_from_slice(&quad); + } + let mut tmp = [0; 4]; + for &flg in flags[..count].iter() { + let _ = write_u32be(&mut tmp, flg); + dbuf.extend_from_slice(&tmp); + } + }, + Opcode::EightColours(count, oct, flags) => { + if let Some(idx) = self.octets.iter().take(self.ocount).position(|oo| oo == &oct) { + dbuf.push(0xD0 | ((count - 1) as u8)); + dbuf.push(idx as u8); + } else { + self.octets[self.opos] = oct; + self.opos = (self.opos + 1) & 0xFF; + if self.ocount < 256 { + self.ocount += 1; + } + dbuf.push(0xC0 | ((count - 1) as u8)); + dbuf.extend_from_slice(&oct); + } + let mut tmp = [0; 6]; + for flg in flags[..count].iter() { + let _ = write_u16be(&mut tmp[..2], flg[0]); + let _ = write_u16be(&mut tmp[2..], flg[1]); + let _ = write_u16be(&mut tmp[4..], flg[2]); + dbuf.extend_from_slice(&tmp); + } + }, + Opcode::Raw(count) => { + let mut to_write = count; + let mut src_pos = pos - count; + while to_write > 0 { + let len = to_write.min(16); + dbuf.push(0xE0 | ((len - 1) as u8)); + for blk in blocks[src_pos..][..len].iter() { + dbuf.extend_from_slice(blk); + } + to_write -= len; + src_pos += len; + } + }, + } + } +} + +fn blk_2clr(block: &[u8; 16], clrs: &[u8; 2]) -> u16 { + let mut flags = 0; + for &pix in block.iter() { + flags <<= 1; + if pix == clrs[1] { + flags |= 1; + } + } + flags +} + +fn blk_4clr(block: &[u8; 16], clrs: &[u8; 4]) -> u32 { + let mut flags = 0; + for pix in block.iter() { + flags <<= 2; + flags |= clrs.iter().position(|c| c == pix).unwrap_or(0) as u32; + } + flags +} + +fn blk_8clr(block: &[u8; 16], clrs: &[u8; 8]) -> [u16; 3] { + let mut flags = [0; 4]; + for (flag, row) in flags.iter_mut().zip(block.chunks_exact(4)) { + for pix in row.iter() { + *flag <<= 3; + *flag |= clrs.iter().position(|c| c == pix).unwrap_or(0) as u16; + } + } + [(flags[0] << 4) | (flags[3] >> 8), + (flags[1] << 4) | ((flags[3] >> 4) & 0xF), + (flags[2] << 4) | (flags[3] & 0xF)] +} + +fn calc_clrs(clrs: &mut [u8; 24], mut ccount: usize, blk: &[u8; 16]) -> usize { + for clr in blk.iter() { + if !clrs[..ccount].contains(clr) { + clrs[ccount] = *clr; + ccount += 1; + } + } + if (2..=8).contains(&ccount) { + clrs[..ccount].sort(); + } + ccount +} + +#[derive(Clone,Copy,Default,PartialEq)] +struct Colour([u8; 3]); + +impl VQElement for Colour { + fn dist(&self, rval: Self) -> u32 { + let rd = u32::from(self.0[0].abs_diff(rval.0[0])); + let gd = u32::from(self.0[1].abs_diff(rval.0[1])); + let bd = u32::from(self.0[2].abs_diff(rval.0[2])); + rd * rd + gd * gd + bd * bd + } + fn min_cw() -> Self { Colour([0x00; 3]) } + fn max_cw() -> Self { Colour([0xFF; 3]) } + fn min(&self, rval: Self) -> Self { + Colour([self.0[0].min(rval.0[0]), + self.0[1].min(rval.0[1]), + self.0[2].min(rval.0[2])]) + } + fn max(&self, rval: Self) -> Self { + Colour([self.0[0].max(rval.0[0]), + self.0[1].max(rval.0[1]), + self.0[2].max(rval.0[2])]) + } + fn num_components() -> usize { 3 } + fn sort_by_component(arr: &mut [Self], component: usize) { + arr.sort_unstable_by(|a, b| a.0[component].cmp(&b.0[component])); + } + fn max_dist_component(min: &Self, max: &Self) -> usize { + let rd = u32::from(min.0[0].abs_diff(max.0[0])); + let gd = u32::from(min.0[1].abs_diff(max.0[1])); + let bd = u32::from(min.0[2].abs_diff(max.0[2])); + if gd >= rd && gd >= bd { + 1 + } else if rd >= gd && rd >= bd { + 0 + } else if bd >= rd && bd >= gd { + 2 + } else { + 1 + } + } +} + +#[derive(Default)] +struct ColourSum { + clr: [u16; 3], + tot: u16, +} + +impl VQElementSum for ColourSum { + fn zero() -> Self { Self::default() } + fn add(&mut self, rval: Colour, count: u64) { + for (dst, &src) in self.clr.iter_mut().zip(rval.0.iter()) { + *dst += u16::from(src) * (count as u16); + } + self.tot += count as u16; + } + fn get_centroid(&self) -> Colour { + if self.tot > 0 { + Colour([(self.clr[0] / self.tot) as u8, + (self.clr[1] / self.tot) as u8, + (self.clr[2] / self.tot) as u8]) + } else { + Colour::default() + } + } +} + +fn pixel_dist(pix: &Colour, pal: &[u8]) -> u32 { + pal.iter().zip(pix.0.iter()).fold(0u32, + |acc, (&a, &b)| acc + u32::from(a.abs_diff(b)) * u32::from(a.abs_diff(b))) +} + +fn lossy_block(blk: &[u8; 16], pal: &[u8; 1024], nclrs: usize, dst_clrs: &mut [u8; 8], dst_idx: &mut [u8; 16]) { + let mut pixels = [Colour::default(); 16]; + for (dst, &src) in pixels.iter_mut().zip(blk.iter()) { + let clr = &pal[usize::from(src) * 4..][..3]; + *dst = Colour([clr[0], clr[1], clr[2]]); + } + let mut ppal = [Colour::default(); 8]; + quantise_median_cut::(&pixels, &mut ppal[..nclrs]); + for (dst, src) in dst_clrs.iter_mut().zip(ppal.iter_mut()) { + let mut best_dist = u32::MAX; + let mut best_idx = 0; + for (i, palclr) in pal.chunks_exact(4).enumerate() { + let dist = pixel_dist(src, palclr); + if dist < best_dist { + best_dist = dist; + best_idx = i; + if dist == 0 { + break; + } + } + } + *dst = best_idx as u8; + *src = Colour([pal[best_idx * 4], pal[best_idx * 4 + 1], pal[best_idx * 4 + 2]]); + } + for i in 0..nclrs { + for j in i + 1..nclrs { + if dst_clrs[i] > dst_clrs[j] { + dst_clrs.swap(i, j); + ppal.swap(i, j); + } + } + } + + for (dst, pix) in dst_idx.iter_mut().zip(pixels.iter()) { + let mut best_dist = u32::MAX; + let mut best_idx = 0; + for (i, refclr) in ppal.iter().take(nclrs).enumerate() { + let dist = pixel_dist(pix, &refclr.0); + if dist < best_dist { + best_dist = dist; + best_idx = i; + if dist == 0 { + break; + } + } + } + *dst = best_idx as u8; + } +} + +struct SeanEncoder { + stream: Option, + pkt: Option, + key_int: u8, + frm_no: u8, + lpal: Arc<[u8; 1024]>, + last_frm: Vec<[u8; 16]>, + cur_frm: Vec<[u8; 16]>, + vinfo: NAVideoInfo, + mode: Strategy, + helper: EncoderHelper, +} + +impl SeanEncoder { + fn new() -> Self { + Self { + stream: None, + pkt: None, + key_int: 25, + frm_no: 254, + lpal: Arc::new([0; 1024]), + last_frm: Vec::new(), + cur_frm: Vec::new(), + vinfo: NAVideoInfo::new(0, 0, false, PAL8_FORMAT), + mode: Strategy::default(), + helper: EncoderHelper::default(), + } + } + fn load_frame(&mut self, buf: NAVideoBufferRef) { + let (w, h) = buf.get_dimensions(0); + let stride = buf.get_stride(0); + let src = buf.get_data(); + + self.cur_frm.clear(); + let mut blk = [0; 16]; + for strip in src.chunks_exact(stride * 4).take(h / 4) { + for x in (0..w).step_by(4) { + for (row, line) in blk.chunks_exact_mut(4).zip(strip.chunks_exact(stride)) { + row.copy_from_slice(&line[x..][..4]); + } + self.cur_frm.push(blk); + } + } + } + fn encode_frame(&mut self, dbuf: &mut Vec, force_intra: bool) -> bool { + let mut is_intra = true; + + let mut opcode = Opcode::None; + self.helper.reset(); + let mut clrs = [0; 24]; + let mut clrs2 = [0; 24]; + let mut lookahead = false; + let mut lossy_clrs = [0; 8]; + let mut lossy_idx = [0; 16]; + for (blk_no, (block, pblock)) in self.cur_frm.iter().zip(self.last_frm.iter_mut()).enumerate() { + if lookahead { + lookahead = false; + pblock.copy_from_slice(block); + continue; + } + + if !force_intra && block == pblock { + if let Opcode::Skip(ref mut scount) = opcode { + *scount += 1; + } else { + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + opcode = Opcode::Skip(1); + } + is_intra = false; + continue; + } + + pblock.copy_from_slice(block); + + let has_next_block = blk_no < self.cur_frm.len() - 1; + + if let Opcode::Fill(ref mut count, fillclr) = opcode { + if block == &[fillclr; 16] { + *count += 1; + continue; + } + } + + if !opcode.is_skip() && blk_no > 0 && block == &self.cur_frm[blk_no - 1] { + if let Opcode::Repeat(ref mut count) = opcode { + *count += 1; + continue; + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + opcode = Opcode::Repeat(1); + continue; + } + + if !opcode.is_skip() && blk_no > 1 && has_next_block && block == &self.cur_frm[blk_no - 2] && self.cur_frm[blk_no - 1] == self.cur_frm[blk_no + 1] { + if let Opcode::RepeatTwo(ref mut count) = opcode { + lookahead = true; + *count += 1; + continue; + } + lookahead = true; + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + opcode = Opcode::RepeatTwo(1); + continue; + } + + let ccount = calc_clrs(&mut clrs, 0, block); + match ccount { + 1 => { + if let Opcode::Fill(ref mut count, ref clr) = opcode { + if *clr == clrs[0] { + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + opcode = Opcode::Fill(1, clrs[0]); + }, + 2 => { + if let Opcode::TwoColours(ref mut count, ref twoclrs, ref mut flags) = opcode { + if *count < 16 && twoclrs == &clrs[..2] { + flags[*count] = blk_2clr(block, twoclrs); + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [0; 16]; + let twoclrs = [clrs[0], clrs[1]]; + flags[0] = blk_2clr(block, &twoclrs); + opcode = Opcode::TwoColours(1, twoclrs, flags); + }, + _ if self.mode == Strategy::Max2Clr => { + lossy_block(block, &self.lpal, 2, &mut lossy_clrs, &mut lossy_idx); + let mut blk_flags = 0; + for (dst, &idx) in pblock.iter_mut().zip(lossy_idx.iter()) { + *dst = lossy_clrs[usize::from(idx)]; + blk_flags = (blk_flags << 1) | u16::from(idx); + } + if let Opcode::TwoColours(ref mut count, ref twoclrs, ref mut flags) = opcode { + if *count < 16 && twoclrs == &lossy_clrs[..2] { + flags[*count] = blk_flags; + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [0; 16]; + let twoclrs = [lossy_clrs[0], lossy_clrs[1]]; + flags[0] = blk_flags; + opcode = Opcode::TwoColours(1, twoclrs, flags); + }, + 3 => { + if let Opcode::FourColours(ref mut count, ref fourclrs, ref mut flags) = opcode { + if *count < 16 && fourclrs.contains(&clrs[0]) && fourclrs.contains(&clrs[1]) && fourclrs.contains(&clrs[2]) { + flags[*count] = blk_4clr(block, fourclrs); + *count += 1; + continue; + } + } + // check next blocks if possible to see if we can use merged 4-colour set + let mut fourclrs = clrs[..4].try_into().unwrap(); + clrs2[..ccount].copy_from_slice(&clrs[..ccount]); + let mut ccount2 = ccount; + for next_blk in self.cur_frm.iter().skip(blk_no + 1) { + ccount2 = calc_clrs(&mut clrs2, ccount2, next_blk); + if ccount2 <= 4 { + fourclrs = clrs2[..4].try_into().unwrap(); + } else { + break; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [0; 16]; + flags[0] = blk_4clr(block, &fourclrs); + opcode = Opcode::FourColours(1, fourclrs, flags); + }, + 4 => { + if let Opcode::FourColours(ref mut count, ref fourclrs, ref mut flags) = opcode { + if *count < 16 && fourclrs == &clrs[..4] { + flags[*count] = blk_4clr(block, fourclrs); + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [0; 16]; + let fourclrs = [clrs[0], clrs[1], clrs[2], clrs[3]]; + flags[0] = blk_4clr(block, &fourclrs); + opcode = Opcode::FourColours(1, fourclrs, flags); + }, + _ if self.mode == Strategy::Max4Clr => { + lossy_block(block, &self.lpal, 4, &mut lossy_clrs, &mut lossy_idx); + let mut blk_flags = 0; + for (dst, &idx) in pblock.iter_mut().zip(lossy_idx.iter()) { + *dst = lossy_clrs[usize::from(idx)]; + blk_flags = (blk_flags << 2) | u32::from(idx); + } + if let Opcode::FourColours(ref mut count, ref fourclrs, ref mut flags) = opcode { + if *count < 16 && fourclrs == &lossy_clrs[..4] { + flags[*count] = blk_flags; + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [0; 16]; + let fourclrs = lossy_clrs[..4].try_into().unwrap(); + flags[0] = blk_flags; + opcode = Opcode::FourColours(1, fourclrs, flags); + }, + 5..=7 => { + if let Opcode::EightColours(ref mut count, ref eightclrs, ref mut flags) = opcode { + let mut all_in = true; + for clr in clrs[..ccount].iter() { + if !eightclrs.contains(clr) { + all_in = false; + break; + } + } + if *count < 16 && all_in { + flags[*count] = blk_8clr(block, eightclrs); + *count += 1; + continue; + } + } + // check next block if possible to see if we can use merged 8-colour set + let mut eightclrs = clrs[..8].try_into().unwrap(); + clrs2[..ccount].copy_from_slice(&clrs[..ccount]); + let mut ccount2 = ccount; + for next_blk in self.cur_frm.iter().skip(blk_no + 1) { + ccount2 = calc_clrs(&mut clrs2, ccount2, next_blk); + if ccount2 <= 8 { + eightclrs = clrs2[..8].try_into().unwrap(); + } else { + break; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [[0; 3]; 16]; + flags[0] = blk_8clr(block, &eightclrs); + opcode = Opcode::EightColours(1, eightclrs, flags); + }, + 8 => { + if let Opcode::EightColours(ref mut count, ref eightclrs, ref mut flags) = opcode { + if *count < 16 && eightclrs == &clrs[..8] { + flags[*count] = blk_8clr(block, eightclrs); + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [[0; 3]; 16]; + let eightclrs = clrs[..8].try_into().unwrap(); + flags[0] = blk_8clr(block, &eightclrs); + opcode = Opcode::EightColours(1, eightclrs, flags); + }, + _ if self.mode == Strategy::Max8Clr => { + lossy_block(block, &self.lpal, 8, &mut lossy_clrs, &mut lossy_idx); + let mut row_flags = [0; 4]; + for (dst, &idx) in pblock.iter_mut().zip(lossy_idx.iter()) { + *dst = lossy_clrs[usize::from(idx)]; + } + for (flg, row) in row_flags.iter_mut().zip(lossy_idx.chunks_exact(4)) { + for &idx in row.iter() { + *flg = (*flg << 3) | u16::from(idx); + } + } + let blk_flags = [(row_flags[0] << 4) | (row_flags[3] >> 8), + (row_flags[1] << 4) | ((row_flags[3] >> 4) & 0xF), + (row_flags[2] << 4) | (row_flags[3] & 0xF)]; + + if let Opcode::EightColours(ref mut count, ref eightclrs, ref mut flags) = opcode { + if *count < 16 && eightclrs == &lossy_clrs { + flags[*count] = blk_flags; + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + let mut flags = [[0; 3]; 16]; + let eightclrs = lossy_clrs; + flags[0] = blk_flags; + opcode = Opcode::EightColours(1, eightclrs, flags); + }, + _ => { + if let Opcode::Raw(ref mut count) = opcode { + if *count < 16 { + *count += 1; + continue; + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, blk_no); + opcode = Opcode::Raw(1); + }, + } + } + self.helper.write_token(dbuf, &opcode, &self.cur_frm, self.cur_frm.len()); + + is_intra + } + fn encode_skip_frame(&mut self, dbuf: &mut Vec) { + self.helper.write_token(dbuf, &Opcode::Skip(self.cur_frm.len()), &[], 0); + } +} + +impl NAEncoder for SeanEncoder { + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => { + Ok(EncodeParameters { + format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, false, PAL8_FORMAT)), + ..Default::default() + }) + }, + NACodecTypeInfo::Video(_vinfo) => { + let mut info = *encinfo; + if let NACodecTypeInfo::Video(ref mut vinfo) = info.format { + vinfo.format = PAL8_FORMAT; + vinfo.width = (vinfo.width + 3) & !3; + vinfo.height = (vinfo.height + 3) & !3; + vinfo.bits = 8; + vinfo.flipped = false; + } else { + return Err(EncoderError::FormatError); + } + Ok(info) + }, + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + } + } + fn get_capabilities(&self) -> u64 { ENC_CAPS_SKIPFRAME } + 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) => { + self.vinfo = vinfo; + if vinfo.format != PAL8_FORMAT || ((vinfo.width | vinfo.height) & 3) != 0 { + return Err(EncoderError::FormatError); + } + let info = NACodecInfo::new("qt-smc", encinfo.format, 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()); + Ok(stream) + } + } + } + fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { + let buf = frm.get_buffer(); + if let Some(vinfo) = buf.get_video_info() { + if vinfo != self.vinfo { + println!("Input format differs from the initial one"); + return Err(EncoderError::FormatError); + } + } + self.frm_no += 1; + let force_intra = self.frm_no >= self.key_int; + + let side_data = if let Some(vbuf) = buf.get_vbuf() { + let mut npal = [0; 1024]; + let src = vbuf.get_data(); + for (dst, src) in npal.chunks_exact_mut(4).zip(src[vbuf.get_offset(1)..].chunks_exact(3)) { + dst[..3].copy_from_slice(src); + } + let new_pal = npal != *self.lpal; + if new_pal { + self.lpal = Arc::new(npal); + } + Some(NASideData::Palette(new_pal, Arc::clone(&self.lpal))) + } else { None }; + + let mut dbuf = vec![0x80, 0, 0, 0]; // ID and 24-bit frame size + let is_intra = if let Some(vbuf) = buf.get_vbuf() { + self.load_frame(vbuf); + if self.last_frm.is_empty() { + self.last_frm.extend_from_slice(&self.cur_frm); + } + + self.encode_frame(&mut dbuf, force_intra) + } else if matches!(buf, NABufferType::None) { + self.cur_frm.clear(); + self.cur_frm.extend_from_slice(&self.last_frm); + if force_intra { + if self.cur_frm.is_empty() { + return Err(EncoderError::InvalidParameters); + } + self.encode_frame(&mut dbuf, force_intra); + true + } else { + self.encode_skip_frame(&mut dbuf); + false + } + } else { + return Err(EncoderError::FormatError); + }; + std::mem::swap(&mut self.last_frm, &mut self.cur_frm); + + if is_intra { + self.frm_no = 0; + } + + let size = dbuf.len() as u32; + let _ = write_u24be(&mut dbuf[1..], size); + + let mut pkt = NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf); + if let Some(sdata) = side_data { + pkt.add_side_data(sdata); + } + + self.pkt = Some(pkt); + Ok(()) + } + 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 MODE_OPTION: &str = "mode"; + +const ENCODER_OPTS: &[NAOptionDefinition] = &[ + NAOptionDefinition { + name: KEYFRAME_OPTION, description: KEYFRAME_OPTION_DESC, + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, + NAOptionDefinition { + name: MODE_OPTION, description: "encoding strategy", + opt_type: NAOptionDefinitionType::String(Some(&["lossless", "2clr", "4clr", "8clr"])) }, +]; + +impl NAOptionHandler for SeanEncoder { + 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(intval) = option.value { + self.key_int = intval as u8; + } + }, + MODE_OPTION => { + if let NAValue::String(ref strval) = option.value { + match strval.as_str() { + "lossless" => { self.mode = Strategy::Lossless; }, + "2clr" => { self.mode = Strategy::Max2Clr; }, + "4clr" => { self.mode = Strategy::Max4Clr; }, + "8clr" => { self.mode = Strategy::Max8Clr; }, + _ => {}, + } + } + }, + _ => {}, + } + } + } + } + } + fn query_option_value(&self, name: &str) -> Option { + match name { + KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.key_int))), + MODE_OPTION => { + match self.mode { + Strategy::Lossless => Some(NAValue::String("lossless".to_string())), + Strategy::Max2Clr => Some(NAValue::String("2clr".to_string())), + Strategy::Max4Clr => Some(NAValue::String("4clr".to_string())), + Strategy::Max8Clr => Some(NAValue::String("8clr".to_string())), + } + }, + _ => None, + } + } +} + +pub fn get_encoder() -> Box { + Box::new(SeanEncoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::*; + use nihav_core::demuxers::*; + use nihav_core::muxers::*; + use nihav_commonfmt::*; + use nihav_codec_support::test::enc_video::*; + use crate::*; + + // samples from https://samples.mplayerhq.hu/V-codecs/QTRLE + fn test_core(enc_options: &[NAOption], hash: &[u32; 4]) { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + qt_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(); + qt_register_all_encoders(&mut enc_reg); + + let dec_config = DecoderTestParams { + demuxer: "mov", + in_name: "assets/QT/Animation-256Greys.mov", + stream_type: StreamType::Video, + limit: Some(2), + dmx_reg, dec_reg, + }; + let enc_config = EncoderTestParams { + muxer: "mov", + enc_name: "qt-smc", + out_name: "qt_smc.mov", + mux_reg, enc_reg, + }; + let dst_vinfo = NAVideoInfo { + width: 0, + height: 0, + format: PAL8_FORMAT, + flipped: false, + bits: 8, + }; + let enc_params = EncodeParameters { + format: NACodecTypeInfo::Video(dst_vinfo), + quality: 0, + bitrate: 0, + 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_smc() { + test_core(&[], + &[0xf53a5201, 0x7549e986, 0x680f929a, 0x92467b9e]); + } + #[test] + fn test_smc_2clrs() { + test_core(&[NAOption{ name: "mode", value: NAValue::String("2clr".to_string())}], + &[0xbf7be071, 0x62d1e567, 0x3ae99dc3, 0xac0bbc82]); + } + #[test] + fn test_smc_4clrs() { + test_core(&[NAOption{ name: "mode", value: NAValue::String("4clr".to_string())}], + &[0xc4b244ca, 0x1a4eebfd, 0x37b04213, 0x352a0105]); + } + #[test] + fn test_smc_8clrs() { + test_core(&[NAOption{ name: "mode", value: NAValue::String("8clr".to_string())}], + &[0x2f9a1663, 0x09601374, 0x1a014ab8, 0xfc341a92]); + } +}