From: Kostya Shishkov Date: Mon, 27 Apr 2026 16:07:21 +0000 (+0200) Subject: QuickTime RLE encoding support X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=b7803ce558fae5907ceef384e3598199fb7bd277;p=nihav.git QuickTime RLE encoding support --- diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index f30b16d..25ee0da 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -13,7 +13,7 @@ path = "../nihav-codec-support" features = ["blockdsp", "fft", "qmf", "qt_pal"] [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_mov"] } [features] default = ["all_decoders", "all_demuxers", "all_encoders"] @@ -45,7 +45,8 @@ demuxer_warhol = ["demuxers"] all_encoders = ["all_video_encoders", "all_audio_encoders"] encoders = [] -all_video_encoders = ["encoder_rawvid"] +all_video_encoders = ["encoder_rawvid", "encoder_rle"] encoder_rawvid = ["encoders"] +encoder_rle = ["encoders"] all_audio_encoders = [] diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index 4685d2b..8b0fecf 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -113,12 +113,16 @@ pub fn qt_register_all_decoders(rd: &mut RegisteredDecoders) { #[cfg(feature="encoder_rawvid")] mod rawvidenc; +#[cfg(feature="encoder_rle")] +mod rleenc; const QT_ENCODERS: &[EncoderInfo] = &[ #[cfg(feature="encoder_rawvid")] EncoderInfo { name: "qt-yuv2", get_encoder: rawvidenc::get_encoder_yuv2 }, #[cfg(feature="encoder_rawvid")] EncoderInfo { name: "qt-yuv4", get_encoder: rawvidenc::get_encoder_yuv4 }, +#[cfg(feature="encoder_rle")] + EncoderInfo { name: "qt-rle", get_encoder: rleenc::get_encoder }, ]; /// Registers all available encoders provided by this crate. diff --git a/nihav-qt/src/codecs/rleenc.rs b/nihav-qt/src/codecs/rleenc.rs new file mode 100644 index 0000000..3f3c2bc --- /dev/null +++ b/nihav-qt/src/codecs/rleenc.rs @@ -0,0 +1,692 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +const RGB555BE_FORMAT: NAPixelFormaton = NAPixelFormaton::make_rgb16_fmt(5, 5, 5, true, true); +const ARGB_FORMAT: NAPixelFormaton = NAPixelFormaton { + model: ColorModel::RGB(RGBSubmodel::RGB), components: 4, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }), + None ], + elem_size: 4, be: false, alpha: true, palette: false }; + +#[derive(Clone,Copy,Default,PartialEq)] +enum Strategy { + Dumb, + #[default] + Greedy, + Slow, +} + +#[derive(Clone,Copy,PartialEq)] +enum EncState { + Skip(u8), + Run(usize, u8), + Copy(usize, u8), +} + +impl EncState { + fn is_skip(&self) -> bool { matches!(*self, EncState::Skip(_)) } + fn write_token(&self, dst: &mut Vec, line: &[u8], pix_size: usize) { + match *self { + EncState::Skip(skip) => { dst.push(skip + 1); }, + EncState::Run(_, 0) => {}, + EncState::Run(pos, 1) => { + dst.push(1); + dst.extend_from_slice(&line[pos * pix_size..][..pix_size]); + }, + EncState::Run(pos, len) => { + dst.push(0u8.wrapping_sub(len)); + dst.extend_from_slice(&line[pos * pix_size..][..pix_size]); + }, + EncState::Copy(pos, len) => { + dst.push(len); + dst.extend_from_slice(&line[pos * pix_size..][..pix_size * usize::from(len)]); + }, + } + } +} + +#[derive(Clone,Copy)] +struct Trellis { + state: EncState, + prev: usize, + cost: u32, +} + +const MAX_COST: u32 = u32::MAX; +const INVALID_NODE: Trellis = Trellis { state: EncState::Skip(0), prev: 0, cost: MAX_COST }; + +#[derive(Default)] +struct TrellisHelper { + skips: Vec, + runs: Vec, + trellis: Vec<[Trellis; 3]>, + stack: Vec, +} + +struct RunLengthEncoder { + stream: Option, + pkt: Option, + key_int: u8, + frm_no: u8, + lpal: Arc<[u8; 1024]>, + last_frm: Vec, + cur_frm: Vec, + pstride: usize, + vinfo: NAVideoInfo, + mode: Strategy, + helper: TrellisHelper, +} + +impl RunLengthEncoder { + 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(), + pstride: 0, + vinfo: NAVideoInfo::new(0, 0, false, RGB24_FORMAT), + mode: Strategy::default(), + helper: TrellisHelper::default(), + } + } + fn pack_frame(&mut self, src: &[u8], stride: usize) { + self.cur_frm.clear(); + let mut gw = GrowableMemoryWriter::new_write(&mut self.cur_frm); + match self.vinfo.bits { + 1 => { + self.pstride = self.vinfo.width / 8; + for line in src.chunks_exact(stride).take(self.vinfo.height) { + for p16 in line.chunks_exact(16).take(self.vinfo.width / 16) { + let mut pp = 0u16; + for &pix in p16.iter() { + pp = pp * 2 + u16::from(pix); + } + let _ = gw.write_u16be(pp); + } + } + }, + 2 => { + self.pstride = self.vinfo.width / 4; + for line in src.chunks_exact(stride).take(self.vinfo.height) { + for p16 in line.chunks_exact(16).take(self.vinfo.width / 16) { + let mut pp = 0u32; + for &pix in p16.iter() { + pp = (pp << 2) + u32::from(pix); + } + let _ = gw.write_u32be(pp); + } + } + }, + 4 => { + self.pstride = self.vinfo.width / 2; + for line in src.chunks_exact(stride).take(self.vinfo.height) { + for p8 in line.chunks_exact(8).take(self.vinfo.width / 8) { + let mut pp = 0u32; + for &pix in p8.iter() { + pp = (pp << 4) + u32::from(pix); + } + let _ = gw.write_u32be(pp); + } + } + }, + 8 | 15 | 16 | 24 | 32 => { + let bpp = usize::from(self.vinfo.bits + 1) / 8; + self.pstride = self.vinfo.width * bpp; + for line in src.chunks_exact(stride).take(self.vinfo.height) { + let _ = gw.write_buf(&line[..self.pstride]); + } + }, + _ => unreachable!(), + } + let _ = gw.flush(); + } + fn pack_frame16(&mut self, src: &[u16], stride: usize) { + self.cur_frm.clear(); + let mut gw = GrowableMemoryWriter::new_write(&mut self.cur_frm); + self.pstride = self.vinfo.width * 2; + for line in src.chunks_exact(stride).take(self.vinfo.height) { + let _ = gw.write_u16be_arr(&line[..self.vinfo.width]); + } + let _ = gw.flush(); + } + fn encode(&mut self, dst: &mut Vec, force_intra: bool) -> bool { + match self.vinfo.bits { + 1 => unimplemented!(), + 2 | 4 | 8 => self.encode_generic::<4>(dst, force_intra), + 15 | 16 => self.encode_generic::<2>(dst, force_intra), + 24 => self.encode_generic::<3>(dst, force_intra), + 32 => self.encode_generic::<4>(dst, force_intra), + _ => unreachable!(), + } + } + fn encode_generic(&mut self, dst: &mut Vec, force_intra: bool) -> bool { + let mut is_intra = true; + let mut start = 0; + let mut height = self.vinfo.height; + if !force_intra && self.mode != Strategy::Dumb { + for (line, pline) in self.cur_frm.chunks_exact(self.pstride) + .zip(self.last_frm.chunks_exact(self.pstride)) { + if line == pline { + start += 1; + } else { + break; + } + } + if start == height { + Self::encode_skip_frame(dst); + return false; + } + height -= start; + for (line, pline) in self.cur_frm.chunks_exact(self.pstride) + .zip(self.last_frm.chunks_exact(self.pstride)).skip(start).rev() { + if line == pline { + height -= 1; + } else { + break; + } + } + } + if start != 0 || height != self.vinfo.height { + dst[5] |= 0x8; + + let mut hdr = [0; 8]; + let _ = write_u16be(&mut hdr[0..], start as u16); + let _ = write_u16be(&mut hdr[4..], height as u16); + dst.extend_from_slice(&hdr); + } + for (line, pline) in self.cur_frm.chunks_exact(self.pstride) + .zip(self.last_frm.chunks_exact(self.pstride)).skip(start).take(height) { + if !force_intra && line == pline { + dst.push(0x01); + dst.push(0xFF); + dst.push(0x00); + is_intra = false; + continue; + } + is_intra &= match self.mode { + Strategy::Dumb => Self::encode_line_dumb::(dst, line, pline, force_intra), + Strategy::Greedy => Self::encode_line_greedy::(dst, line, pline, force_intra), + Strategy::Slow => Self::encode_line_slow::(dst, line, pline, force_intra, &mut self.helper), + }; + } + is_intra + } + fn encode_line_dumb(dst: &mut Vec, line: &[u8], _pline: &[u8], _force_intra: bool) -> bool { + dst.push(1); // skip mode off + for chunk in line.chunks(PIX_SIZE * 127) { + let len = (chunk.len() / PIX_SIZE) as u8; + dst.push(len); + dst.extend_from_slice(chunk); + } + dst.push(0xFF); // run code at the end of line + dst.push(0x00); // skip code at the end of line + true + } + fn encode_line_greedy(dst: &mut Vec, line: &[u8], pline: &[u8], force_intra: bool) -> bool { + let mut is_intra = true; + let mut state = EncState::Skip(0); + + for (i, (cpix, ppix)) in line.chunks_exact(PIX_SIZE) + .zip(pline.chunks_exact(PIX_SIZE)).enumerate() { + let cur_skip = !force_intra && cpix == ppix; + + state = match (state, cur_skip) { + (EncState::Skip(254), _) | (EncState::Skip(_), false) => { + state.write_token(dst, line, PIX_SIZE); + EncState::Run(i, 1) + }, + (EncState::Skip(skip), true) => { + is_intra = false; + EncState::Skip(skip + 1) + }, + (EncState::Run(_, 128), _) | (EncState::Copy(_, 127), _) => { + state.write_token(dst, line, PIX_SIZE); + EncState::Run(i, 1) + }, + (EncState::Run(_, _), true) | (EncState::Copy(_, _), true) => { + state.write_token(dst, line, PIX_SIZE); + dst.push(0); + is_intra = false; + EncState::Skip(1) + }, + (EncState::Run(pos, len), false) => { + if cpix == &line[pos * PIX_SIZE..][..PIX_SIZE] { + EncState::Run(pos, len + 1) + } else if len > 1 { + state.write_token(dst, line, PIX_SIZE); + EncState::Run(i, 1) + } else { + EncState::Copy(pos, 2) + } + }, + (EncState::Copy(pos, len), false) => { + if len > 2 && cpix == &line[(pos + usize::from(len - 1)) * PIX_SIZE..][..PIX_SIZE] { + let tmp = EncState::Copy(pos, len - 1); + tmp.write_token(dst, line, PIX_SIZE); + EncState::Run(i - 1, 2) + } else { + EncState::Copy(pos, len + 1) + } + }, + }; + } + if state.is_skip() { + is_intra = false; + } + state.write_token(dst, line, PIX_SIZE); + dst.push(0xFF); // run code at the end of line + dst.push(0x00); // skip code at the end of line + + is_intra + } + fn encode_line_slow(dst: &mut Vec, line: &[u8], pline: &[u8], force_intra: bool, helper: &mut TrellisHelper) -> bool { + helper.skips.clear(); + if force_intra { + helper.skips.resize(line.len() / PIX_SIZE, false); + } else { + for (cpix, ppix) in line.chunks_exact(PIX_SIZE).zip(pline.chunks_exact(PIX_SIZE)) { + helper.skips.push(cpix == ppix); + } + } + helper.runs.clear(); + helper.runs.push(true); + for (cpix, ppix) in line.chunks_exact(PIX_SIZE).skip(1).zip(line.chunks_exact(PIX_SIZE)) { + helper.runs.push(cpix == ppix); + } + let trellis = &mut helper.trellis; + trellis.clear(); + trellis.resize(helper.runs.len() + 1, [INVALID_NODE; 3]); + trellis[0][0] = Trellis{ state: EncState::Skip(0), prev: 0, cost: 1 }; + + let pix_cost = PIX_SIZE as u32; + for pos in 0..trellis.len() - 1 { + for i in 0..3 { + if trellis[pos][i].cost == MAX_COST { continue; } + for skip in 1..=254 { + if pos + skip > helper.skips.len() || !helper.skips[pos + skip - 1] { + break; + } + let cost = if pos != 0 { 2 } else { 0 }; + if trellis[pos + skip][0].cost > trellis[pos][i].cost + cost { + trellis[pos + skip][0].cost = trellis[pos][i].cost + cost; + trellis[pos + skip][0].prev = pos * 4 + i; + trellis[pos + skip][0].state = EncState::Skip(skip as u8); + } + } + for run in 2..=128 { + if pos + run > helper.runs.len() || !helper.runs[pos + run - 1] { + break; + } + let cost = 1 + pix_cost; + if trellis[pos + run][1].cost > trellis[pos][i].cost + cost { + trellis[pos + run][1].cost = trellis[pos][i].cost + cost; + trellis[pos + run][1].prev = pos * 4 + i; + trellis[pos + run][1].state = EncState::Run(pos, run as u8); + } + } + for copy in 1..=127 { + if pos + copy > helper.runs.len() { + break; + } + let cost = 1 + pix_cost * (copy as u32); + if trellis[pos + copy][2].cost > trellis[pos][i].cost + cost { + trellis[pos + copy][2].cost = trellis[pos][i].cost + cost; + trellis[pos + copy][2].prev = pos * 4 + i; + trellis[pos + copy][2].state = EncState::Copy(pos, copy as u8); + } + } + } + } + + helper.stack.clear(); + let mut pos = (helper.trellis.len() - 1) * 4; + let mut best_cost = MAX_COST; + for (i, nn) in helper.trellis[helper.trellis.len() - 1].iter().enumerate() { + if nn.cost < best_cost { + pos = (pos & !3) | i; + best_cost = nn.cost; + } + } + while pos > 0 { + let node = helper.trellis[pos >> 2][pos & 3]; + helper.stack.push(node.state); + pos = node.prev; + } + if !helper.stack[helper.stack.len() - 1].is_skip() { + helper.stack.push(EncState::Skip(0)); + } + + let mut skip_mode = true; + let mut is_intra = true; + //xxx: skip last codes if they are skips? + for state in helper.stack.iter().rev() { + if state.is_skip() { + if *state != EncState::Skip(0) { + is_intra = false; + } + if !skip_mode { + dst.push(0); + } + skip_mode = false; + } + state.write_token(dst, line, PIX_SIZE); + } + dst.push(0xFF); // run code at the end of line + dst.push(0x00); // skip code at the end of line + is_intra + } + + fn encode_skip_frame(dst: &mut Vec) { + dst[5] |= 0x8; + for _ in 0..8 { // zero bounding rect + dst.push(0); + } + } +} + +fn get_fmt_and_align(bits: u8) -> (NAPixelFormaton, usize) { + match bits { + 1 => (PAL8_FORMAT, 16), + 2 => (PAL8_FORMAT, 16), + 4 => (PAL8_FORMAT, 8), + 8 => (PAL8_FORMAT, 4), + 15 | 16 => (RGB555BE_FORMAT, 1), + 24 => (RGB24_FORMAT, 1), + 32 => (ARGB_FORMAT, 1), + _ => (RGB24_FORMAT, 1), + } +} + +impl NAEncoder for RunLengthEncoder { + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => { + Ok(EncodeParameters { + format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, false, RGB24_FORMAT)), + ..Default::default() + }) + }, + NACodecTypeInfo::Video(vinfo) => { + let bits = if vinfo.format.is_paletted() { + vinfo.bits.min(8) + } else if vinfo.format.model.is_rgb() { + vinfo.format.get_total_depth() + } else if vinfo.format.has_alpha() { + 32 + } else { + 24 + }; + let (fmt, align) = get_fmt_and_align(bits); + let mut info = *encinfo; + if let NACodecTypeInfo::Video(ref mut vinfo) = info.format { + vinfo.format = fmt; + vinfo.width = (vinfo.width + align - 1) & !(align - 1); + vinfo.bits = bits; + 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; + let bits = if vinfo.format.is_paletted() { + vinfo.bits.min(8) + } else if vinfo.format.model.is_rgb() { + vinfo.format.get_total_depth() + } else if vinfo.format.has_alpha() { + 32 + } else { + 24 + }; + self.vinfo.bits = bits; + self.vinfo.flipped = false; + let (fmt, align) = get_fmt_and_align(bits); + if vinfo.format != fmt || (vinfo.width & (align - 1)) != 0 { + return Err(EncoderError::FormatError); + } + let info = NACodecInfo::new("qt-rle", 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 mut dbuf = vec![0x00, 0, 0, 0, 0, 0]; // ID, 24-bit frame size and 16-bit flags placeholder + let is_intra = if let Some(vbuf) = buf.get_vbuf() { + let src = vbuf.get_data(); + let stride = vbuf.get_stride(0); + + self.pack_frame(src, stride); + if self.last_frm.is_empty() { + self.last_frm.extend_from_slice(&self.cur_frm); + } + + self.encode(&mut dbuf, force_intra) + } else if let Some(vbuf16) = buf.get_vbuf16() { + let src = vbuf16.get_data(); + let stride = vbuf16.get_stride(0); + + self.pack_frame16(src, stride); + if self.last_frm.is_empty() { + self.last_frm.extend_from_slice(&self.cur_frm); + } + + self.encode(&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(&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; + dbuf[0] = 0x40; + } + + 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(vbuf) = buf.get_vbuf() { + if self.vinfo.bits <= 8 { + 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); + } + pkt.add_side_data(NASideData::Palette(new_pal, Arc::clone(&self.lpal))); + } + } + + 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(&["dumb", "greedy", "slow"])) }, +]; + +impl NAOptionHandler for RunLengthEncoder { + 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() { + "dumb" => { self.mode = Strategy::Dumb; }, + "greedy" => { self.mode = Strategy::Greedy; }, + "slow" => { self.mode = Strategy::Slow; }, + _ => {}, + } + } + }, + _ => {}, + } + } + } + } + } + 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::Dumb => Some(NAValue::String("dumb".to_string())), + Strategy::Greedy => Some(NAValue::String("greedy".to_string())), + Strategy::Slow => Some(NAValue::String("slow".to_string())), + } + }, + _ => None, + } + } +} + +pub fn get_encoder() -> Box { + Box::new(RunLengthEncoder::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(in_name: &'static str, format: NAPixelFormaton, 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, + stream_type: StreamType::Video, + limit: Some(4), + dmx_reg, dec_reg, + }; + let enc_config = EncoderTestParams { + muxer: "mov", + enc_name: "qt-rle", + out_name: "qt_rle.mov", + mux_reg, enc_reg, + }; + let dst_vinfo = NAVideoInfo { + width: 0, + height: 0, + format, + flipped: false, + bits: format.get_total_depth(), + }; + 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_qt_rle_16bit_modes() { + test_core("assets/QT/Animation-Highcolour.mov", super::RGB555BE_FORMAT, + &[NAOption{ name: "mode", value: NAValue::String("dumb".to_string()) }], + &[0x518474ac, 0x654ebbef, 0xcc79db98, 0xcf1cd1cf]); + test_core("assets/QT/Animation-Highcolour.mov", super::RGB555BE_FORMAT, + &[NAOption{ name: "mode", value: NAValue::String("greedy".to_string()) }], + &[0x61aefc0d, 0x8f7b7d6a, 0x599b89d2, 0x6c61b188]); + test_core("assets/QT/Animation-Highcolour.mov", super::RGB555BE_FORMAT, + &[NAOption{ name: "mode", value: NAValue::String("slow".to_string()) }], + &[0x16d9854a, 0x2f109d72, 0xf564c32c, 0xb3b51312]); + } + #[test] + fn test_qt_rle_24bit() { + test_core("assets/QT/Animation-Truecolour.mov", RGB24_FORMAT, &[], + &[0x9b25e03d, 0x3b878d5e, 0xfd268170, 0x18ff6b6a]); + } + #[test] + fn test_qt_rle_32bit() { + test_core("assets/QT/Jag-finder-renaming.mov", super::ARGB_FORMAT, &[], + &[0xf9c3a0df, 0xecfc4b55, 0xb8dd8fb0, 0xb8ebb93b]); + } +}