From: Kostya Shishkov Date: Wed, 8 Feb 2023 16:36:49 +0000 (+0100) Subject: msvideo1enc: add paletted input support X-Git-Url: https://git.nihav.org/?p=nihav.git;a=commitdiff_plain;h=608a923fdacd068d33f904a5181691bbae221b94 msvideo1enc: add paletted input support --- diff --git a/nihav-ms/src/codecs/msvideo1enc.rs b/nihav-ms/src/codecs/msvideo1enc.rs index 0733215..3c89f7a 100644 --- a/nihav-ms/src/codecs/msvideo1enc.rs +++ b/nihav-ms/src/codecs/msvideo1enc.rs @@ -3,7 +3,17 @@ use nihav_core::io::byteio::*; type UnpackedPixel = [u16; 4]; -fn map_quality(quality: u8) -> (u32, u32) { +fn map_quality_pal(quality: u8) -> (u32, u32) { + if quality == 0 { + (0, 0) + } else { + let skip_threshold = (10 - (u32::from(quality) / 10).min(10)) * (8 << 6); + let fill_threshold = (10 - (u32::from(quality) / 10).min(10)) * (16 << 6); + (skip_threshold, fill_threshold) + } +} + +fn map_quality_15bit(quality: u8) -> (u32, u32) { if quality == 0 { (0, 0) } else { @@ -39,6 +49,84 @@ fn dist_core(val: UnpackedPixel, other: &UnpackedPixel) -> u32 { sum as u32 } +fn find_nearest(pix: UnpackedPixel, pal: &[UnpackedPixel; 256]) -> usize { + let mut bestidx = 0; + let mut bestdist = std::u32::MAX; + + for (idx, entry) in pal.iter().enumerate() { + let dist = dist_core(pix, entry); + if dist == 0 { + return idx; + } + if bestdist > dist { + bestdist = dist; + bestidx = idx; + } + } + bestidx +} + +struct LocalSearch { + pal: [UnpackedPixel; 256], + db: Vec>, +} + +impl LocalSearch { + fn quant(key: UnpackedPixel) -> usize { + (((key[0] >> 3) as usize) << 10) | + (((key[1] >> 3) as usize) << 5) | + ((key[2] >> 3) as usize) + } + fn new() -> Self { + let mut db = Vec::with_capacity(1 << 15); + for _ in 0..(1 << 15) { + db.push(Vec::new()); + } + Self { + pal: [UnpackedPixel::default(); 256], + db + } + } + fn recalculate(&mut self, pal: &[UnpackedPixel; 256]) { + self.pal = *pal; + for vec in self.db.iter_mut() { + vec.clear(); + } + for (i, palentry) in pal.iter().enumerate() { + let r0 = (palentry[0] >> 3) as usize; + let g0 = (palentry[1] >> 3) as usize; + let b0 = (palentry[2] >> 3) as usize; + for r in r0.saturating_sub(1)..=(r0 + 1).min(31) { + for g in g0.saturating_sub(1)..=(g0 + 1).min(31) { + for b in b0.saturating_sub(1)..=(b0 + 1).min(31) { + let idx = (r << 10) | (g << 5) | b; + self.db[idx].push([palentry[0], palentry[1], palentry[2], i as u16]); + } + } + } + } + } + fn search(&self, pix: UnpackedPixel) -> usize { + let idx = Self::quant(pix); + let mut best_dist = std::u32::MAX; + let mut best_idx = 0; + let mut count = 0; + for clr in self.db[idx].iter() { + let dist = dist_core(pix, clr); + count += 1; + if best_dist > dist { + best_dist = dist; + best_idx = clr[3] as usize; + if dist == 0 { break; } + } + } + if count > 0 { + best_idx + } else { + find_nearest(pix, &self.pal) + } + } +} fn rgb2y(r: u16, g: u16, b: u16) -> u16 { (r * 77 + g * 150 + b * 29) >> 8 @@ -150,6 +238,7 @@ struct BlockState { } impl BlockState { + fn new_pal() -> Self { Self { pal_mode: true, ..Default::default() } } fn set_fill_val(&mut self, val: UnpackedPixel) { self.fill_val = val; if !self.pal_mode { @@ -257,6 +346,99 @@ impl BlockState { } } +struct BlockPainterPal<'a> { + ls: &'a LocalSearch, +} +impl<'a> BlockPainterPal<'a> { + fn new(ls: &'a LocalSearch) -> Self { Self{ ls } } + fn find_index(&self, pix: UnpackedPixel) -> u8 { self.ls.search(pix) as u8 } + fn put_fill(&self, bstate: &BlockState, dst: &mut [u8], dstride: usize) -> u8 { + let fill_val = self.find_index(bstate.fill_val); + for line in dst.chunks_mut(dstride) { + for i in 0..4 { + line[i] = fill_val; + } + } + fill_val + } + fn put_clr2(&self, bstate: &BlockState, dst: &mut [u8], dstride: usize) -> [u8; 2] { + let clr2 = [self.find_index(bstate.clr2[0]), self.find_index(bstate.clr2[1])]; + for j in 0..4 { + for i in 0..4 { + if (bstate.clr2_flags & (1 << (i + j * 4))) == 0 { + dst[i + j * dstride] = clr2[0]; + } else { + dst[i + j * dstride] = clr2[1]; + } + } + } + clr2 + } + fn put_clr8(&self, bstate: &BlockState, dst: &mut [u8], dstride: usize) -> [[u8; 4]; 4] { + let mut clr8 = [[0; 4]; 4]; + for (dst, src) in clr8.iter_mut().zip(bstate.clr8.iter()) { + for (dst, &src) in dst.iter_mut().zip(src.iter()) { + *dst = self.find_index(src); + } + } + let mut clr8_flags = bstate.clr8_flags; + let swap = (clr8_flags & 0x8000) == 0; + if swap { + clr8_flags ^= 0xFF00; + } + if clr8_flags < 0x9000 { + clr8_flags |= 0x1000; + } + if swap { + clr8_flags ^= 0xFF00; + } + for (j, line) in dst.chunks_mut(dstride).take(4).enumerate() { + for (i, el) in line.iter_mut().take(4).enumerate() { + let blk_no = (i >> 1) + (j & 2); + *el = clr8[blk_no][(!clr8_flags & 1) as usize]; + clr8_flags >>= 1; + } + } + clr8 + } +} + +struct BlockWriterPal {} +impl BlockWriterPal { + fn write_fill(bw: &mut ByteWriter, fill_val: u8) -> EncoderResult<()> { + bw.write_byte(fill_val)?; + bw.write_byte(0x80)?; + Ok(()) + } + fn write_clr2(bw: &mut ByteWriter, clr2_flags: u16, clr2: [u8; 2]) -> EncoderResult<()> { + bw.write_u16le(clr2_flags)?; + bw.write_byte(clr2[0])?; + bw.write_byte(clr2[1])?; + Ok(()) + } + fn write_clr8(bw: &mut ByteWriter, mut clr8_flags: u16, mut clr8: [[u8; 4]; 4]) -> EncoderResult<()> { + if (clr8_flags & 0x8000) == 0 { + clr8_flags ^= 0xFF00; + clr8[2].swap(0, 1); + clr8[3].swap(0, 1); + } + if clr8_flags < 0x9000 { + clr8_flags |= 0x1000; + } + + bw.write_u16le(clr8_flags)?; + bw.write_byte(clr8[0][0])?; + bw.write_byte(clr8[0][1])?; + bw.write_byte(clr8[1][0])?; + bw.write_byte(clr8[1][1])?; + bw.write_byte(clr8[2][0])?; + bw.write_byte(clr8[2][1])?; + bw.write_byte(clr8[3][0])?; + bw.write_byte(clr8[3][1])?; + Ok(()) + } +} + struct BlockPainter15 {} impl BlockPainter15 { fn new() -> Self { Self{} } @@ -335,11 +517,17 @@ impl BlockWriter15 { struct MSVideo1Encoder { stream: Option, pkt: Option, - pool: NAVideoBufferPool, - lastfrm: Option>, + pool8: NAVideoBufferPool, + pool15: NAVideoBufferPool, + lastfrm8: Option>, + lastfrm15: Option>, quality: u8, frmcount: u8, key_int: u8, + + pal_mode: bool, + pal: [UnpackedPixel; 256], + ls: LocalSearch, } impl MSVideo1Encoder { @@ -347,11 +535,17 @@ impl MSVideo1Encoder { Self { stream: None, pkt: None, - pool: NAVideoBufferPool::new(2), - lastfrm: None, + pool8: NAVideoBufferPool::new(2), + pool15: NAVideoBufferPool::new(2), + lastfrm8: None, + lastfrm15: None, quality: 0, frmcount: 0, key_int: 25, + + pal_mode: false, + pal: [UnpackedPixel::default(); 256], + ls: LocalSearch::new(), } } fn get_block(src: &[u16], sstride: usize, buf: &mut [UnpackedPixel; 16]) { @@ -361,12 +555,19 @@ impl MSVideo1Encoder { } } } + fn get_block8(src: &[u8], sstride: usize, buf: &mut [UnpackedPixel; 16], pal: &[UnpackedPixel; 256]) { + for (line, dst) in src.chunks(sstride).zip(buf.chunks_mut(4)) { + for (dst, src) in dst.iter_mut().zip(line.iter()) { + *dst = pal[usize::from(*src)]; + } + } + } fn write_skips(bw: &mut ByteWriter, skips: usize) -> EncoderResult<()> { bw.write_u16le((skips as u16) | 0x8400)?; Ok(()) } - fn encode_inter(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, prev_frm: &NAVideoBuffer, quality: u8) -> EncoderResult { - let (skip_threshold, fill_threshold) = map_quality(quality); + fn encode_inter_rgb555(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, prev_frm: &NAVideoBuffer, quality: u8) -> EncoderResult { + let (skip_threshold, fill_threshold) = map_quality_15bit(quality); let mut is_intra = true; let src = in_frm.get_data(); let sstride = in_frm.get_stride(0); @@ -451,8 +652,8 @@ impl MSVideo1Encoder { } //xxx: something for inter? Ok(is_intra) } - fn encode_intra(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, quality: u8) -> EncoderResult { - let (_, fill_threshold) = map_quality(quality); + fn encode_intra_rgb555(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, quality: u8) -> EncoderResult { + let (_, fill_threshold) = map_quality_15bit(quality); let src = in_frm.get_data(); let sstride = in_frm.get_stride(0); let soff = in_frm.get_offset(0); @@ -485,6 +686,126 @@ impl MSVideo1Encoder { bw.write_u16le(0)?; Ok(true) } + fn encode_inter_pal(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, prev_frm: &NAVideoBuffer, quality: u8, pal: &[UnpackedPixel; 256], ls: &LocalSearch) -> EncoderResult { + let (skip_threshold, fill_threshold) = map_quality_pal(quality); + let mut is_intra = true; + let src = in_frm.get_data(); + let sstride = in_frm.get_stride(0); + let soff = in_frm.get_offset(0); + let (w, h) = in_frm.get_dimensions(0); + let rsrc = prev_frm.get_data(); + let rstride = prev_frm.get_stride(0); + let roff = prev_frm.get_offset(0); + let dstride = cur_frm.get_stride(0); + let doff = cur_frm.get_offset(0); + let dst = cur_frm.get_data_mut().unwrap(); + let mut skip_run = 0; + let bpainter = BlockPainterPal::new(ls); + for ((sstrip, rstrip), dstrip) in (&src[soff..]).chunks(sstride * 4).take(h / 4).zip((&rsrc[roff..]).chunks(rstride * 4)).zip((&mut dst[doff..]).chunks_mut(dstride * 4)) { + for x in (0..w).step_by(4) { + let mut buf = [UnpackedPixel::default(); 16]; + let mut refbuf = [UnpackedPixel::default(); 16]; + Self::get_block8(&sstrip[x..], sstride, &mut buf, pal); + Self::get_block8(&rstrip[x..], rstride, &mut refbuf, pal); + + let mut skip_dist = 0; + for (pix, rpix) in buf.iter().zip(refbuf.iter()) { + skip_dist += dist_core(*rpix, pix); + } + if skip_dist <= skip_threshold { + skip_run += 1; + is_intra = false; + for (dst, src) in dstrip[x..].chunks_mut(dstride).zip(rstrip[x..].chunks(rstride)).take(4) { + dst[..4].copy_from_slice(&src[..4]); + } + if skip_run == 1023 { + Self::write_skips(bw, skip_run)?; + skip_run = 0; + } + continue; + } + + let mut bstate = BlockState::new_pal(); + bstate.calc_stats(&buf); + + let dst = &mut dstrip[x..]; + if skip_dist <= bstate.fill_dist && skip_dist * 2 <= bstate.clr2_dist { + skip_run += 1; + is_intra = false; + for (dst, src) in dst.chunks_mut(dstride).zip(rstrip[x..].chunks(rstride)).take(4) { + dst[..4].copy_from_slice(&src[..4]); + } + if skip_run == 1023 { + Self::write_skips(bw, skip_run)?; + skip_run = 0; + } + } else if bstate.fill_dist <= fill_threshold || + bstate.fill_dist <= bstate.clr2_dist { + let fill_val = bpainter.put_fill(&bstate, dst, dstride); + if skip_run != 0 { + Self::write_skips(bw, skip_run)?; + skip_run = 0; + } + BlockWriterPal::write_fill(bw, fill_val)?; + } else if bstate.clr8_dist < bstate.clr2_dist { + let clr8 = bpainter.put_clr8(&bstate, dst, dstride); + if skip_run != 0 { + Self::write_skips(bw, skip_run)?; + skip_run = 0; + } + BlockWriterPal::write_clr8(bw, bstate.clr8_flags, clr8)?; + } else { + let clr2 = bpainter.put_clr2(&bstate, dst, dstride); + if skip_run != 0 { + Self::write_skips(bw, skip_run)?; + skip_run = 0; + } + BlockWriterPal::write_clr2(bw, bstate.clr2_flags, clr2)?; + } + } + } + if skip_run != 0 { + Self::write_skips(bw, skip_run)?; + } + if is_intra { + bw.write_u16le(0)?; + } //xxx: something for inter? + Ok(is_intra) + } + fn encode_intra_pal(bw: &mut ByteWriter, cur_frm: &mut NAVideoBuffer, in_frm: &NAVideoBuffer, quality: u8, pal: &[UnpackedPixel; 256], ls: &LocalSearch) -> EncoderResult { + let (_, fill_threshold) = map_quality_pal(quality); + let src = in_frm.get_data(); + let sstride = in_frm.get_stride(0); + let soff = in_frm.get_offset(0); + let (w, h) = in_frm.get_dimensions(0); + let dstride = cur_frm.get_stride(0); + let doff = cur_frm.get_offset(0); + let dst = cur_frm.get_data_mut().unwrap(); + let bpainter = BlockPainterPal::new(ls); + for (sstrip, dstrip) in (&src[soff..]).chunks(sstride * 4).take(h / 4).zip((&mut dst[doff..]).chunks_mut(dstride * 4)) { + for x in (0..w).step_by(4) { + let mut buf = [UnpackedPixel::default(); 16]; + Self::get_block8(&sstrip[x..], sstride, &mut buf, pal); + let mut bstate = BlockState::new_pal(); + bstate.calc_stats(&buf); + + let dst = &mut dstrip[x..]; + if bstate.fill_dist <= fill_threshold || + bstate.fill_dist <= bstate.clr2_dist { + let fill_val = bpainter.put_fill(&bstate, dst, dstride); + BlockWriterPal::write_fill(bw, fill_val)?; + } else if bstate.clr8_dist < bstate.clr2_dist { + let clr8 = bpainter.put_clr8(&bstate, dst, dstride); + BlockWriterPal::write_clr8(bw, bstate.clr8_flags, clr8)?; + } else { + let clr2 = bpainter.put_clr2(&bstate, dst, dstride); + BlockWriterPal::write_clr2(bw, bstate.clr2_flags, clr2)?; + } + } + } + bw.write_u16le(0)?; + Ok(true) + } } const RGB555_FORMAT: NAPixelFormaton = NAPixelFormaton { @@ -506,7 +827,8 @@ impl NAEncoder for MSVideo1Encoder { }, NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), NACodecTypeInfo::Video(vinfo) => { - let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, true, RGB555_FORMAT); + let oformat = if vinfo.format == PAL8_FORMAT { PAL8_FORMAT } else { RGB555_FORMAT }; + let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, true, oformat); let mut ofmt = *encinfo; ofmt.format = NACodecTypeInfo::Video(outinfo); Ok(ofmt) @@ -518,20 +840,28 @@ impl NAEncoder for MSVideo1Encoder { NACodecTypeInfo::None => Err(EncoderError::FormatError), NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), NACodecTypeInfo::Video(vinfo) => { - if vinfo.format != RGB555_FORMAT { + if vinfo.format != RGB555_FORMAT && vinfo.format != PAL8_FORMAT { return Err(EncoderError::FormatError); } if ((vinfo.width | vinfo.height) & 3) != 0 { return Err(EncoderError::FormatError); } - let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, true, RGB555_FORMAT); + let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, true, vinfo.format); let info = NACodecInfo::new("msvideo1", 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(); - if self.pool.prealloc_video(out_info, 2).is_err() { - return Err(EncoderError::AllocError); + self.pal_mode = vinfo.format.is_paletted(); + + if !self.pal_mode { + if self.pool15.prealloc_video(out_info, 2).is_err() { + return Err(EncoderError::AllocError); + } + } else { + if self.pool8.prealloc_video(out_info, 2).is_err() { + return Err(EncoderError::AllocError); + } } self.stream = Some(stream.clone()); @@ -544,25 +874,78 @@ impl NAEncoder for MSVideo1Encoder { fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { let buf = frm.get_buffer(); if let Some(ref vbuf) = buf.get_vbuf16() { - let mut cur_frm = self.pool.get_free().unwrap(); + if self.pal_mode { + return Err(EncoderError::InvalidParameters); + } + let mut cur_frm = self.pool15.get_free().unwrap(); let mut dbuf = Vec::with_capacity(4); let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); let mut bw = ByteWriter::new(&mut gw); if self.frmcount == 0 { - self.lastfrm = None; + self.lastfrm15 = None; } - let is_intra = if let Some(ref prev_buf) = self.lastfrm { - Self::encode_inter(&mut bw, &mut cur_frm, vbuf, prev_buf, self.quality)? + let is_intra = if let Some(ref prev_buf) = self.lastfrm15 { + Self::encode_inter_rgb555(&mut bw, &mut cur_frm, vbuf, prev_buf, self.quality)? } else { - Self::encode_intra(&mut bw, &mut cur_frm, vbuf, self.quality)? + Self::encode_intra_rgb555(&mut bw, &mut cur_frm, vbuf, self.quality)? }; - self.lastfrm = Some(cur_frm); + self.lastfrm15 = Some(cur_frm); self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf)); self.frmcount += 1; if self.frmcount == self.key_int { self.frmcount = 0; } Ok(()) + } else if let Some(ref vbuf) = buf.get_vbuf() { + if !self.pal_mode { + return Err(EncoderError::InvalidParameters); + } + let src = vbuf.get_data(); + let pal = &src[vbuf.get_offset(1)..]; + let mut pal_changed = false; + for (cur_pal, new_pal) in self.pal.iter_mut().zip(pal.chunks_exact(3)) { + let (cur_clr, luma) = cur_pal.split_at_mut(3); + let new_clr = [u16::from(new_pal[0]), u16::from(new_pal[1]), u16::from(new_pal[2])]; + if cur_clr != &new_clr { + pal_changed = true; + cur_clr.copy_from_slice(&new_clr); + luma[0] = rgb2y(cur_clr[0], cur_clr[1], cur_clr[2]); + } + } + + if pal_changed { + self.ls.recalculate(&self.pal); + } + + let mut cur_frm = self.pool8.get_free().unwrap(); + let mut dbuf = Vec::with_capacity(4); + let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); + let mut bw = ByteWriter::new(&mut gw); + if self.frmcount == 0 { + self.lastfrm8 = None; + } + let is_intra = if let Some(ref prev_buf) = self.lastfrm8 { + Self::encode_inter_pal(&mut bw, &mut cur_frm, vbuf, prev_buf, self.quality, &self.pal, &self.ls)? + } else { + Self::encode_intra_pal(&mut bw, &mut cur_frm, vbuf, self.quality, &self.pal, &self.ls)? + }; + self.lastfrm8 = Some(cur_frm); + let mut pkt = NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf); + if pal_changed { + let mut epal = [0; 1024]; + for (dst, src) in epal.chunks_mut(4).zip(self.pal.iter()) { + dst[0] = src[0] as u8; + dst[1] = src[1] as u8; + dst[2] = src[2] as u8; + } + pkt.add_side_data(NASideData::Palette(true, Arc::new(epal))); + } + self.pkt = Some(pkt); + self.frmcount += 1; + if self.frmcount == self.key_int { + self.frmcount = 0; + } + Ok(()) } else { Err(EncoderError::InvalidParameters) } @@ -625,7 +1008,53 @@ mod test { use super::RGB555_FORMAT; #[test] - fn test_ms_video1_encoder() { + fn test_ms_video1_encoder_pal() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + generic_register_all_decoders(&mut dec_reg); + ms_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(); + ms_register_all_encoders(&mut enc_reg); + + // sample: https://samples.mplayerhq.hu/V-codecs/RLE/mplayer-msrle-4bit.avi + let dec_config = DecoderTestParams { + demuxer: "avi", + in_name: "assets/MS/mplayer-msrle-4bit.avi", + stream_type: StreamType::Video, + limit: Some(3), + dmx_reg, dec_reg, + }; + let enc_config = EncoderTestParams { + muxer: "avi", + enc_name: "msvideo1", + out_name: "msvideo1pal.avi", + mux_reg, enc_reg, + }; + let dst_vinfo = NAVideoInfo { + width: 0, + height: 0, + format: PAL8_FORMAT, + flipped: true, + 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, &[]); + test_encoding_md5(&dec_config, &enc_config, enc_params, &[], + &[0x27a9db38, 0x74f1000a, 0x38818c05, 0x99d692ba]); + } + + #[test] + fn test_ms_video1_encoder_rgb555() { let mut dmx_reg = RegisteredDemuxers::new(); generic_register_all_demuxers(&mut dmx_reg); let mut dec_reg = RegisteredDecoders::new();