| 1 | use std::ops::*; |
| 2 | use nihav_core::codecs::*; |
| 3 | use nihav_core::io::byteio::*; |
| 4 | use super::truemotion1data::*; |
| 5 | |
| 6 | #[derive(Clone,Copy,PartialEq,Default)] |
| 7 | enum BlockMode { |
| 8 | FourByFour, |
| 9 | #[default] |
| 10 | TwoByTwo, |
| 11 | FourByTwo, |
| 12 | TwoByFour, |
| 13 | } |
| 14 | |
| 15 | impl FromStr for BlockMode { |
| 16 | type Err = (); |
| 17 | fn from_str(s: &str) -> Result<BlockMode, Self::Err> { |
| 18 | match s { |
| 19 | "4x4" => Ok(BlockMode::FourByFour), |
| 20 | "2x4" => Ok(BlockMode::TwoByFour), |
| 21 | "4x2" => Ok(BlockMode::FourByTwo), |
| 22 | "2x2" => Ok(BlockMode::TwoByTwo), |
| 23 | _ => Err(()), |
| 24 | } |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | impl ToString for BlockMode { |
| 29 | fn to_string(&self) -> String { |
| 30 | match *self { |
| 31 | BlockMode::FourByFour => "4x4".to_string(), |
| 32 | BlockMode::FourByTwo => "4x2".to_string(), |
| 33 | BlockMode::TwoByFour => "2x4".to_string(), |
| 34 | BlockMode::TwoByTwo => "2x2".to_string(), |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | #[derive(Clone,Copy,PartialEq,Default)] |
| 40 | enum OptionMode { |
| 41 | Off, |
| 42 | On, |
| 43 | #[default] |
| 44 | Auto, |
| 45 | } |
| 46 | |
| 47 | impl FromStr for OptionMode { |
| 48 | type Err = (); |
| 49 | fn from_str(s: &str) -> Result<OptionMode, Self::Err> { |
| 50 | match s { |
| 51 | "off" => Ok(OptionMode::Off), |
| 52 | "on" => Ok(OptionMode::On), |
| 53 | "auto" => Ok(OptionMode::Auto), |
| 54 | _ => Err(()), |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | impl ToString for OptionMode { |
| 60 | fn to_string(&self) -> String { |
| 61 | match *self { |
| 62 | OptionMode::Off => "off".to_string(), |
| 63 | OptionMode::On => "on".to_string(), |
| 64 | OptionMode::Auto => "auto".to_string(), |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | #[derive(Default,Clone,Copy,PartialEq)] |
| 70 | struct WorkPixel ([i16; 3]); |
| 71 | |
| 72 | impl WorkPixel { |
| 73 | fn decorr(self) -> Self { |
| 74 | WorkPixel([self.0[0] - self.0[1], self.0[1], self.0[2] - self.0[1]]) |
| 75 | } |
| 76 | fn recon(self) -> Self { |
| 77 | WorkPixel([self.0[0] + self.0[1], self.0[1], self.0[2] + self.0[1]]) |
| 78 | } |
| 79 | fn check16(self) -> bool { |
| 80 | let full = self.recon(); |
| 81 | for comp in full.0.iter() { |
| 82 | if !(0i16..32i16).contains(comp) { |
| 83 | return false; |
| 84 | } |
| 85 | } |
| 86 | true |
| 87 | } |
| 88 | fn clear_chroma(&mut self) { |
| 89 | self.0[0] = 0; |
| 90 | self.0[2] = 0; |
| 91 | } |
| 92 | fn avg(mut self, other: Self) -> Self { |
| 93 | self += other; |
| 94 | for el in self.0.iter_mut() { |
| 95 | *el = (*el + 1) >> 1; |
| 96 | } |
| 97 | self |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | impl std::fmt::Display for WorkPixel { |
| 102 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
| 103 | write!(f, "({:3},{:3},{:3})", self.0[0], self.0[1], self.0[2]) |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | impl Add for WorkPixel { |
| 108 | type Output = Self; |
| 109 | fn add(self, rhs: Self) -> Self::Output { |
| 110 | WorkPixel([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1], self.0[2] + rhs.0[2]]) |
| 111 | } |
| 112 | } |
| 113 | impl AddAssign for WorkPixel { |
| 114 | fn add_assign(&mut self, rhs: Self) { |
| 115 | self.0[0] += rhs.0[0]; |
| 116 | self.0[1] += rhs.0[1]; |
| 117 | self.0[2] += rhs.0[2]; |
| 118 | } |
| 119 | } |
| 120 | impl Sub for WorkPixel { |
| 121 | type Output = Self; |
| 122 | fn sub(self, rhs: Self) -> Self::Output { |
| 123 | WorkPixel([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1], self.0[2] - rhs.0[2]]) |
| 124 | } |
| 125 | } |
| 126 | impl SubAssign for WorkPixel { |
| 127 | fn sub_assign(&mut self, rhs: Self) { |
| 128 | self.0[0] -= rhs.0[0]; |
| 129 | self.0[1] -= rhs.0[1]; |
| 130 | self.0[2] -= rhs.0[2]; |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | fn load16(frm: &mut [WorkPixel], width: usize, vbuf: &NAVideoBuffer<u16>) { |
| 135 | let stride = vbuf.get_stride(0); |
| 136 | let src = vbuf.get_data(); |
| 137 | |
| 138 | for (dline, sline) in frm.chunks_exact_mut(width).zip( |
| 139 | src[vbuf.get_offset(0)..].chunks_exact(stride)) { |
| 140 | for (dst, &src) in dline.iter_mut().zip(sline.iter()) { |
| 141 | let el = (src & 0x7FFF) as i16; |
| 142 | *dst = WorkPixel([el >> 10, (el >> 5) & 0x1F, el & 0x1F]).decorr(); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | fn load24(frm: &mut [WorkPixel], width: usize, vbuf: &NAVideoBuffer<u8>) { |
| 148 | let stride = vbuf.get_stride(0); |
| 149 | let src = vbuf.get_data(); |
| 150 | |
| 151 | for (dline, sline) in frm.chunks_exact_mut(width).zip( |
| 152 | src[vbuf.get_offset(0)..].chunks_exact(stride)) { |
| 153 | for (dst, src) in dline.iter_mut().zip(sline.chunks_exact(8)) { |
| 154 | let pix1 = WorkPixel([i16::from(src[0]), i16::from(src[1]), i16::from(src[2])]); |
| 155 | let pix2 = WorkPixel([i16::from(src[4]), i16::from(src[5]), i16::from(src[6])]); |
| 156 | *dst = pix1.avg(pix2); |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | const MAX_DIFF: i32 = std::i32::MAX; |
| 162 | |
| 163 | fn luma15_delta(pix: &mut WorkPixel, pred: WorkPixel, tgt: WorkPixel, deltas: &[i32; 8], fat_deltas: &[i32; 8]) { |
| 164 | let mut tpix = *pix; |
| 165 | let tgt_val = i32::from(tgt.0[1]); |
| 166 | let base_val = i32::from(pred.0[1]); |
| 167 | let mut best_delta = 0; |
| 168 | let mut best_diff = MAX_DIFF; |
| 169 | if i32::from(pix.0[1].abs()) <= deltas[deltas.len() - 2] { |
| 170 | for &delta in deltas.iter() { |
| 171 | let diff = (base_val + delta - tgt_val) * (base_val + delta - tgt_val); |
| 172 | if diff < best_diff { |
| 173 | tpix.0[1] = delta as i16; |
| 174 | if (tpix + pred).check16() { |
| 175 | best_diff = diff; |
| 176 | best_delta = delta as i16; |
| 177 | if diff == 0 { |
| 178 | break; |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | if best_diff == MAX_DIFF { |
| 185 | for &f_delta in fat_deltas.iter().skip(1) { |
| 186 | for &delta1 in deltas.iter() { |
| 187 | let delta = delta1 + f_delta; |
| 188 | let diff = (base_val + delta - tgt_val) * (base_val + delta - tgt_val); |
| 189 | if diff < best_diff { |
| 190 | tpix.0[1] = delta as i16; |
| 191 | if (tpix + pred).check16() { |
| 192 | best_diff = diff; |
| 193 | best_delta = delta as i16; |
| 194 | if diff == 0 { |
| 195 | break; |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | pix.0[1] = best_delta; |
| 203 | } |
| 204 | |
| 205 | fn chroma15_deltas(pix: &mut [WorkPixel; 2], pred: &[WorkPixel; 2], tgt: &[WorkPixel], component: usize, deltas: &[i32; 8], fat_deltas: &[i32; 8]) { |
| 206 | let mut tpix = *pix; |
| 207 | let pred_val = [i32::from(pred[0].0[component]), i32::from(pred[1].0[component])]; |
| 208 | let tgt_val = [i32::from(tgt[0].0[component]), i32::from(tgt[1].0[component])]; |
| 209 | |
| 210 | let mut best_delta = 0; |
| 211 | let mut best_diff = MAX_DIFF; |
| 212 | |
| 213 | let delta_thr = deltas[deltas.len() - 2]; |
| 214 | if i32::from(pix[0].0[component].abs()) <= delta_thr && |
| 215 | i32::from(pix[1].0[component].abs()) <= delta_thr { |
| 216 | for &delta in deltas.iter() { |
| 217 | let diffs = [pred_val[0] + delta - tgt_val[0], pred_val[1] + delta - tgt_val[1]]; |
| 218 | let diff = diffs[0] * diffs[0] + diffs[1] * diffs[1]; |
| 219 | if diff < best_diff { |
| 220 | tpix[0].0[component] = delta as i16; |
| 221 | tpix[1].0[component] = delta as i16; |
| 222 | if (tpix[0] + pred[0]).check16() && (tpix[1] + pred[1]).check16() { |
| 223 | best_diff = diff; |
| 224 | best_delta = delta as i16; |
| 225 | if diff == 0 { |
| 226 | break; |
| 227 | } |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | if best_diff == MAX_DIFF { |
| 233 | for &f_delta in fat_deltas.iter().skip(1) { |
| 234 | for &delta1 in deltas.iter() { |
| 235 | let delta = delta1 + f_delta; |
| 236 | let diffs = [pred_val[0] + delta - tgt_val[0], pred_val[1] + delta - tgt_val[1]]; |
| 237 | let diff = diffs[0] * diffs[0] + diffs[1] * diffs[1]; |
| 238 | if diff < best_diff { |
| 239 | tpix[0].0[component] = delta as i16; |
| 240 | tpix[1].0[component] = delta as i16; |
| 241 | if (tpix[0] + pred[0]).check16() && (tpix[1] + pred[1]).check16() { |
| 242 | best_diff = diff; |
| 243 | best_delta = delta as i16; |
| 244 | if diff == 0 { |
| 245 | break; |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | pix[0].0[component] = best_delta; |
| 253 | pix[1].0[component] = best_delta; |
| 254 | } |
| 255 | |
| 256 | #[derive(Default)] |
| 257 | struct MaskWriter { |
| 258 | data: Vec<u8>, |
| 259 | buf: u8, |
| 260 | pos: u8, |
| 261 | } |
| 262 | |
| 263 | impl MaskWriter { |
| 264 | fn new() -> Self { Self::default() } |
| 265 | fn reset(&mut self) { |
| 266 | self.data.clear(); |
| 267 | self.buf = 0; |
| 268 | self.pos = 0; |
| 269 | } |
| 270 | fn write_bit(&mut self, val: bool) { |
| 271 | if val { |
| 272 | self.buf |= 1 << self.pos; |
| 273 | } |
| 274 | self.pos += 1; |
| 275 | if self.pos == 8 { |
| 276 | self.data.push(self.buf); |
| 277 | self.buf = 0; |
| 278 | self.pos = 0; |
| 279 | } |
| 280 | } |
| 281 | fn flush(&mut self) { |
| 282 | if self.pos != 0 { |
| 283 | self.data.push(self.buf); |
| 284 | } |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | struct IndexWriter { |
| 289 | dst: Vec<u8>, |
| 290 | in_seq: [u8; 4], |
| 291 | in_len: usize, |
| 292 | cand: u8, |
| 293 | table: &'static [u8], |
| 294 | } |
| 295 | |
| 296 | enum SearchResult { |
| 297 | Delta(u8), |
| 298 | Escape(u8, u8), |
| 299 | } |
| 300 | |
| 301 | impl IndexWriter { |
| 302 | fn new() -> Self { |
| 303 | Self { |
| 304 | dst: Vec::new(), |
| 305 | in_seq: [0; 4], |
| 306 | in_len: 0, |
| 307 | cand: 0, |
| 308 | table: DUCK_VECTABLES[0], |
| 309 | } |
| 310 | } |
| 311 | fn reset(&mut self, idx: usize) { |
| 312 | self.dst.clear(); |
| 313 | self.in_len = 0; |
| 314 | self.table = DUCK_VECTABLES[idx]; |
| 315 | } |
| 316 | fn flush(&mut self) { |
| 317 | if self.in_len > 0 { |
| 318 | let idx = self.find(self.in_len).unwrap(); |
| 319 | assert_eq!(idx, self.cand); |
| 320 | self.dst.push(self.cand); |
| 321 | self.in_len = 0; |
| 322 | } |
| 323 | } |
| 324 | fn find(&self, len: usize) -> Option<u8> { |
| 325 | let mut cur_idx = 0u8; |
| 326 | let src = self.table; |
| 327 | let mut cb_pos = 0; |
| 328 | while cb_pos < src.len() { |
| 329 | let entry_len = usize::from(src[cb_pos]) / 2; |
| 330 | cb_pos += 1; |
| 331 | if entry_len < len { |
| 332 | break; |
| 333 | } |
| 334 | if entry_len == len && src[cb_pos..][..len] == self.in_seq[..len] { |
| 335 | return Some(cur_idx.max(1)); |
| 336 | } |
| 337 | cb_pos += entry_len; |
| 338 | cur_idx += 1; |
| 339 | } |
| 340 | None |
| 341 | } |
| 342 | fn find_delta(dval: i16, deltas: &[i32; 8], fat_deltas: &[i32; 8]) -> SearchResult { |
| 343 | if let Some(pos) = deltas.iter().position(|&x| x == i32::from(dval)) { |
| 344 | SearchResult::Delta(pos as u8) |
| 345 | } else { |
| 346 | for (i, &delta1) in deltas.iter().enumerate() { |
| 347 | for (j, &f_delta) in fat_deltas.iter().enumerate().skip(1) { |
| 348 | let delta = delta1 + f_delta; |
| 349 | if delta == i32::from(dval) { |
| 350 | return SearchResult::Escape(i as u8, j as u8); |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | unreachable!() |
| 355 | } |
| 356 | } |
| 357 | fn add_deltas(&mut self, d0: i16, d1: i16, deltas: &[i32; 8], fat_deltas: &[i32; 8]) { |
| 358 | let idx0 = Self::find_delta(d0, deltas, fat_deltas); |
| 359 | let idx1 = Self::find_delta(d1, deltas, fat_deltas); |
| 360 | match (idx0, idx1) { |
| 361 | (SearchResult::Delta(i0), SearchResult::Delta(i1)) => { |
| 362 | self.add(i0, i1); |
| 363 | }, |
| 364 | (SearchResult::Delta(i0), SearchResult::Escape(i10, i11)) => { |
| 365 | self.add(i0, i10); |
| 366 | self.add_byte(0); |
| 367 | self.add(0, i11); |
| 368 | }, |
| 369 | (SearchResult::Escape(i00, i01), SearchResult::Delta(i1)) => { |
| 370 | self.add(i00, i1); |
| 371 | self.add_byte(0); |
| 372 | self.add(i01, 0); |
| 373 | }, |
| 374 | (SearchResult::Escape(i00, i01), SearchResult::Escape(i10, i11)) => { |
| 375 | self.add(i00, i10); |
| 376 | self.add_byte(0); |
| 377 | self.add(i01, i11); |
| 378 | }, |
| 379 | } |
| 380 | } |
| 381 | fn add(&mut self, idx0: u8, idx1: u8) { |
| 382 | let pair = idx0 * 16 + idx1; |
| 383 | self.in_seq[self.in_len] = pair; |
| 384 | self.in_len += 1; |
| 385 | match self.find(self.in_len) { |
| 386 | None => { |
| 387 | self.dst.push(self.cand); |
| 388 | self.in_seq[0] = self.in_seq[self.in_len - 1]; |
| 389 | self.in_len = 1; |
| 390 | self.cand = self.find(self.in_len).unwrap(); |
| 391 | }, |
| 392 | Some(idx) if self.in_len == 4 => { |
| 393 | self.dst.push(idx); |
| 394 | self.in_len = 0; |
| 395 | }, |
| 396 | Some(idx) => { |
| 397 | self.cand = idx; |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | fn add_byte(&mut self, val: u8) { |
| 402 | self.flush(); |
| 403 | self.dst.push(val); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | struct TM1Encoder { |
| 408 | stream: Option<NAStreamRef>, |
| 409 | pkt: Option<NAPacket>, |
| 410 | top_line: Vec<WorkPixel>, |
| 411 | skip_map: Vec<bool>, |
| 412 | frame: Vec<WorkPixel>, |
| 413 | prev_frame: Vec<WorkPixel>, |
| 414 | width: usize, |
| 415 | height: usize, |
| 416 | is16: bool, |
| 417 | mask: MaskWriter, |
| 418 | idx_wr: IndexWriter, |
| 419 | tm1type: usize, |
| 420 | block_mode: BlockMode, |
| 421 | delta_set: usize, |
| 422 | table_idx: usize, |
| 423 | delta_mode: OptionMode, |
| 424 | table_mode: OptionMode, |
| 425 | frameno: usize, |
| 426 | key_int: usize, |
| 427 | } |
| 428 | |
| 429 | impl TM1Encoder { |
| 430 | fn new() -> Self { |
| 431 | Self { |
| 432 | stream: None, |
| 433 | pkt: None, |
| 434 | top_line: Vec::new(), |
| 435 | skip_map: Vec::new(), |
| 436 | frame: Vec::new(), |
| 437 | prev_frame: Vec::new(), |
| 438 | width: 0, |
| 439 | height: 0, |
| 440 | is16: false, |
| 441 | mask: MaskWriter::new(), |
| 442 | idx_wr: IndexWriter::new(), |
| 443 | tm1type: 0, |
| 444 | block_mode: BlockMode::default(), |
| 445 | delta_set: 0, |
| 446 | table_idx: 0, |
| 447 | delta_mode: OptionMode::Off, |
| 448 | table_mode: OptionMode::Off, |
| 449 | frameno: 0, |
| 450 | key_int: 10, |
| 451 | } |
| 452 | } |
| 453 | fn encode_16(&mut self, is_intra: bool) -> EncoderResult<()> { |
| 454 | let (blk_w, blk_h) = if let Some(ref info) = TM1_COMPR_TYPES[self.tm1type] { |
| 455 | (info.block_w, info.block_h) |
| 456 | } else { |
| 457 | unreachable!(); |
| 458 | }; |
| 459 | self.mask.reset(); |
| 460 | self.idx_wr.reset(self.table_idx); |
| 461 | for el in self.top_line.iter_mut() { |
| 462 | *el = WorkPixel::default(); |
| 463 | } |
| 464 | |
| 465 | let y_deltas = &DUCK_Y_DELTAS [self.delta_set]; |
| 466 | let yf_deltas = &DUCK_Y_DELTAS_5[self.delta_set]; |
| 467 | let c_deltas = &DUCK_C_DELTAS [self.delta_set]; |
| 468 | let cf_deltas = &DUCK_C_DELTAS_5[self.delta_set]; |
| 469 | |
| 470 | let mut has_c = [[false; 2]; 4]; |
| 471 | has_c[0][0] = true; |
| 472 | if blk_w == 2 { |
| 473 | has_c[0][1] = true; |
| 474 | } |
| 475 | if blk_h == 2 { |
| 476 | has_c[2] = has_c[0]; |
| 477 | } |
| 478 | |
| 479 | if !is_intra { |
| 480 | self.skip_map.clear(); |
| 481 | for (stripe, pstripe) in self.frame.chunks_exact(self.width * 4).zip( |
| 482 | self.prev_frame.chunks_exact(self.width * 4)) { |
| 483 | for x in (0..self.width).step_by(4) { |
| 484 | let mut skip = true; |
| 485 | 'cmp_loop: for (line, pline) in stripe[x..].chunks(self.width).zip( |
| 486 | pstripe[x..].chunks(self.width)) { |
| 487 | for (&a, &b) in line[..4].iter().zip(pline[..4].iter()) { |
| 488 | if a != b { |
| 489 | skip = false; |
| 490 | break 'cmp_loop; |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | self.mask.write_bit(skip); |
| 495 | self.skip_map.push(skip); |
| 496 | } |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | let mut skip_blk_addr = 0; |
| 501 | for (y, line) in self.frame.chunks_exact_mut(self.width).enumerate() { |
| 502 | let mut hpred = [WorkPixel::default(); 2]; |
| 503 | |
| 504 | for (x, (pair, vpred)) in line.chunks_exact_mut(2).zip( |
| 505 | self.top_line.chunks_exact_mut(2)).enumerate() { |
| 506 | if !is_intra && self.skip_map[skip_blk_addr + x / 2] { |
| 507 | pair[0] = self.prev_frame[x * 2 + y * self.width]; |
| 508 | pair[1] = self.prev_frame[x * 2 + 1 + y * self.width]; |
| 509 | hpred[0] = pair[0] - vpred[0]; |
| 510 | hpred[1] = pair[1] - vpred[1]; |
| 511 | vpred.copy_from_slice(pair); |
| 512 | continue; |
| 513 | } |
| 514 | let pval = [vpred[0] + hpred[0], vpred[1] + hpred[1]]; |
| 515 | let mut deltas = [pair[0] - pval[0], pair[1] - pval[1]]; |
| 516 | if has_c[y & 3][x & 1] { |
| 517 | chroma15_deltas(&mut deltas, &pval, pair, 0, c_deltas, cf_deltas); |
| 518 | chroma15_deltas(&mut deltas, &pval, pair, 2, c_deltas, cf_deltas); |
| 519 | self.idx_wr.add_deltas(deltas[0].0[0], deltas[0].0[2], c_deltas, cf_deltas); |
| 520 | } else { |
| 521 | deltas[0].clear_chroma(); |
| 522 | deltas[1].clear_chroma(); |
| 523 | } |
| 524 | luma15_delta(&mut deltas[0], pval[0], pair[0], y_deltas, yf_deltas); |
| 525 | luma15_delta(&mut deltas[1], pval[1], pair[1], y_deltas, yf_deltas); |
| 526 | self.idx_wr.add_deltas(deltas[0].0[1], deltas[1].0[1], y_deltas, yf_deltas); |
| 527 | |
| 528 | hpred[0] += deltas[0]; |
| 529 | hpred[1] += deltas[1]; |
| 530 | vpred[0] += hpred[0]; |
| 531 | vpred[1] += hpred[1]; |
| 532 | pair[0] = vpred[0]; |
| 533 | pair[1] = vpred[1]; |
| 534 | } |
| 535 | |
| 536 | if (y & 3) == 3 { |
| 537 | skip_blk_addr += self.width / 4; |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | Ok(()) |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | impl NAEncoder for TM1Encoder { |
| 546 | fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> { |
| 547 | match encinfo.format { |
| 548 | NACodecTypeInfo::None => { |
| 549 | Ok(EncodeParameters { |
| 550 | format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, BGR0_FORMAT)), |
| 551 | ..Default::default() |
| 552 | }) |
| 553 | }, |
| 554 | NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), |
| 555 | NACodecTypeInfo::Video(vinfo) => { |
| 556 | /*let in_fmt = vinfo.format; |
| 557 | let pix_fmt = if in_fmt.model.is_rgb() && in_fmt.get_total_depth() <= 16 { |
| 558 | RGB555_FORMAT |
| 559 | } else { |
| 560 | BGR0_FORMAT |
| 561 | };*/ |
| 562 | let pix_fmt = RGB555_FORMAT; |
| 563 | let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, false, pix_fmt); |
| 564 | let mut ofmt = *encinfo; |
| 565 | ofmt.format = NACodecTypeInfo::Video(outinfo); |
| 566 | Ok(ofmt) |
| 567 | } |
| 568 | } |
| 569 | } |
| 570 | fn get_capabilities(&self) -> u64 { ENC_CAPS_PARAMCHANGE } |
| 571 | fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> { |
| 572 | match encinfo.format { |
| 573 | NACodecTypeInfo::None => Err(EncoderError::FormatError), |
| 574 | NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), |
| 575 | NACodecTypeInfo::Video(vinfo) => { |
| 576 | if vinfo.format != BGR0_FORMAT && vinfo.format != RGB555_FORMAT { |
| 577 | return Err(EncoderError::FormatError); |
| 578 | } |
| 579 | if vinfo.format == BGR0_FORMAT { |
| 580 | return Err(EncoderError::NotImplemented); |
| 581 | } |
| 582 | if ((vinfo.width | vinfo.height) & 3) != 0 { |
| 583 | return Err(EncoderError::FormatError); |
| 584 | } |
| 585 | if (vinfo.width | vinfo.height) >= (1 << 10) { |
| 586 | return Err(EncoderError::FormatError); |
| 587 | } |
| 588 | |
| 589 | let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, vinfo.format); |
| 590 | let info = NACodecInfo::new("truemotion1", NACodecTypeInfo::Video(out_info), None); |
| 591 | let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0); |
| 592 | stream.set_num(stream_id as usize); |
| 593 | let stream = stream.into_ref(); |
| 594 | |
| 595 | self.stream = Some(stream.clone()); |
| 596 | |
| 597 | Ok(stream) |
| 598 | }, |
| 599 | } |
| 600 | } |
| 601 | fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { |
| 602 | let buf = frm.get_buffer(); |
| 603 | |
| 604 | let mut meta_type = 2; |
| 605 | let mut is_intra = meta_type == 1 || self.frameno == 0; |
| 606 | |
| 607 | if let Some(vinfo) = buf.get_video_info() { |
| 608 | if vinfo.width != self.width || vinfo.height != self.height { |
| 609 | self.top_line.resize(vinfo.width, WorkPixel::default()); |
| 610 | self.skip_map.resize((vinfo.width / 4) * (vinfo.height / 4), false); |
| 611 | self.frame.resize(vinfo.width * vinfo.height, WorkPixel::default()); |
| 612 | self.prev_frame.resize(vinfo.width * vinfo.height, WorkPixel::default()); |
| 613 | self.width = vinfo.width; |
| 614 | self.height = vinfo.height; |
| 615 | is_intra = true; |
| 616 | if meta_type == 1 && self.width < 213 && self.height >= 176 { |
| 617 | // switch to a newer version in order to avoid being an interpolated frame |
| 618 | meta_type = 2; |
| 619 | } |
| 620 | } |
| 621 | } else { |
| 622 | return Err(EncoderError::InvalidParameters); |
| 623 | } |
| 624 | |
| 625 | let old_is16 = self.is16; |
| 626 | match buf { |
| 627 | NABufferType::Video(ref vbuf) | NABufferType::VideoPacked(ref vbuf) => { |
| 628 | self.is16 = false; |
| 629 | load24(&mut self.frame, self.width / 2, vbuf); // 24-bit video is half-size |
| 630 | }, |
| 631 | NABufferType::Video16(ref vbuf16) => { |
| 632 | self.is16 = true; |
| 633 | load16(&mut self.frame, self.width, vbuf16); |
| 634 | }, |
| 635 | _ => return Err(EncoderError::InvalidParameters), |
| 636 | }; |
| 637 | if old_is16 != self.is16 { |
| 638 | is_intra = true; |
| 639 | } |
| 640 | |
| 641 | let mut dbuf = Vec::with_capacity(4); |
| 642 | let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); |
| 643 | let mut bw = ByteWriter::new(&mut gw); |
| 644 | |
| 645 | self.tm1type = match (self.is16, self.block_mode) { |
| 646 | (true, BlockMode::FourByFour) => 2, |
| 647 | (true, BlockMode::FourByTwo) => 4, |
| 648 | (true, BlockMode::TwoByFour) => 6, |
| 649 | (true, BlockMode::TwoByTwo) => 8, |
| 650 | (false, BlockMode::FourByFour) => 10, |
| 651 | (false, BlockMode::FourByTwo) => 12, |
| 652 | (false, BlockMode::TwoByFour) => 14, |
| 653 | (false, BlockMode::TwoByTwo) => 16, |
| 654 | }; |
| 655 | self.delta_set = if self.is16 { 0 } else { 3 }; |
| 656 | if self.delta_mode == OptionMode::On && self.delta_set < 3 { |
| 657 | self.delta_set += 1; |
| 658 | } |
| 659 | if (self.tm1type & 1) != 0 && meta_type != 0 { |
| 660 | self.table_idx = 0; |
| 661 | } else { |
| 662 | self.table_idx = if self.is16 { |
| 663 | 0 |
| 664 | } else { |
| 665 | match self.table_mode { |
| 666 | OptionMode::Off => 1, |
| 667 | OptionMode::On => 2, |
| 668 | OptionMode::Auto => self.delta_set.saturating_sub(1), |
| 669 | } |
| 670 | }; |
| 671 | } |
| 672 | |
| 673 | bw.write_byte(0)?; // header size |
| 674 | bw.write_byte(self.tm1type as u8)?; |
| 675 | bw.write_byte(self.delta_set as u8)?; |
| 676 | bw.write_byte(self.table_idx as u8 + 1)?; |
| 677 | bw.write_u16le(self.height as u16)?; |
| 678 | bw.write_u16le(self.width as u16)?; |
| 679 | bw.write_u16le(0)?; // checksum |
| 680 | bw.write_byte(2)?; // version |
| 681 | bw.write_byte(meta_type)?; |
| 682 | if meta_type == 2 { |
| 683 | let flags = if is_intra { 0x10 } else { 0x08 }; |
| 684 | bw.write_byte(flags)?; |
| 685 | bw.write_byte(1 << 4)?; // control - PC flavour |
| 686 | } |
| 687 | |
| 688 | if self.is16 { |
| 689 | self.encode_16(is_intra)?; |
| 690 | } else { |
| 691 | unimplemented!(); |
| 692 | } |
| 693 | std::mem::swap(&mut self.frame, &mut self.prev_frame); |
| 694 | self.frameno += 1; |
| 695 | if self.frameno >= self.key_int { |
| 696 | self.frameno = 0; |
| 697 | } |
| 698 | |
| 699 | let hdr_size = bw.tell() as usize; |
| 700 | |
| 701 | if !is_intra { |
| 702 | self.mask.flush(); |
| 703 | dbuf.extend_from_slice(&self.mask.data); |
| 704 | } |
| 705 | self.idx_wr.add_byte(1); // TM1 need a non-zero code at the end to make sure it's not an escape |
| 706 | dbuf.extend_from_slice(&self.idx_wr.dst); |
| 707 | |
| 708 | dbuf[0] = ((hdr_size | 0x80) as u8).rotate_right(3); |
| 709 | for i in (1..hdr_size).rev() { |
| 710 | dbuf[i] ^= dbuf[i + 1]; |
| 711 | } |
| 712 | |
| 713 | self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf)); |
| 714 | Ok(()) |
| 715 | } |
| 716 | fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> { |
| 717 | let mut npkt = None; |
| 718 | std::mem::swap(&mut self.pkt, &mut npkt); |
| 719 | Ok(npkt) |
| 720 | } |
| 721 | fn flush(&mut self) -> EncoderResult<()> { |
| 722 | Ok(()) |
| 723 | } |
| 724 | } |
| 725 | |
| 726 | const ENCODER_OPTS: &[NAOptionDefinition] = &[ |
| 727 | NAOptionDefinition { |
| 728 | name: KEYFRAME_OPTION, description: KEYFRAME_OPTION_DESC, |
| 729 | opt_type: NAOptionDefinitionType::Int(Some(0), Some(64)) }, |
| 730 | NAOptionDefinition { |
| 731 | name: "delta_mode", description: "Alternative delta mode", |
| 732 | opt_type: NAOptionDefinitionType::String(Some(&["off", "on", "auto"])) }, |
| 733 | NAOptionDefinition { |
| 734 | name: "table_mode", description: "Alternative table mode", |
| 735 | opt_type: NAOptionDefinitionType::String(Some(&["off", "on", "auto"])) }, |
| 736 | NAOptionDefinition { |
| 737 | name: "block_mode", description: "Block mode", |
| 738 | opt_type: NAOptionDefinitionType::String(Some(&["2x2", "2x4", "4x2", "4x4"])) }, |
| 739 | ]; |
| 740 | |
| 741 | impl NAOptionHandler for TM1Encoder { |
| 742 | fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS } |
| 743 | fn set_options(&mut self, options: &[NAOption]) { |
| 744 | for option in options.iter() { |
| 745 | for opt_def in ENCODER_OPTS.iter() { |
| 746 | if opt_def.check(option).is_ok() { |
| 747 | match option.name { |
| 748 | KEYFRAME_OPTION => { |
| 749 | if let NAValue::Int(val) = option.value { |
| 750 | self.key_int = val as usize; |
| 751 | } |
| 752 | }, |
| 753 | "delta_mode" => { |
| 754 | if let NAValue::String(ref val) = option.value { |
| 755 | self.delta_mode = val.parse::<OptionMode>().unwrap(); |
| 756 | } |
| 757 | }, |
| 758 | "table_mode" => { |
| 759 | if let NAValue::String(ref val) = option.value { |
| 760 | self.table_mode = val.parse::<OptionMode>().unwrap(); |
| 761 | } |
| 762 | }, |
| 763 | "block_mode" => { |
| 764 | if let NAValue::String(ref val) = option.value { |
| 765 | self.block_mode = val.parse::<BlockMode>().unwrap(); |
| 766 | } |
| 767 | }, |
| 768 | _ => {}, |
| 769 | }; |
| 770 | } |
| 771 | } |
| 772 | } |
| 773 | } |
| 774 | fn query_option_value(&self, name: &str) -> Option<NAValue> { |
| 775 | match name { |
| 776 | KEYFRAME_OPTION => Some(NAValue::Int(self.key_int as i64)), |
| 777 | "delta_mode" => Some(NAValue::String(self.delta_mode.to_string())), |
| 778 | "table_mode" => Some(NAValue::String(self.table_mode.to_string())), |
| 779 | "block_mode" => Some(NAValue::String(self.block_mode.to_string())), |
| 780 | _ => None, |
| 781 | } |
| 782 | } |
| 783 | } |
| 784 | |
| 785 | pub fn get_encoder() -> Box<dyn NAEncoder + Send> { |
| 786 | Box::new(TM1Encoder::new()) |
| 787 | } |
| 788 | |
| 789 | #[cfg(test)] |
| 790 | mod test { |
| 791 | use nihav_core::codecs::*; |
| 792 | use nihav_core::demuxers::*; |
| 793 | use nihav_core::muxers::*; |
| 794 | use crate::*; |
| 795 | use nihav_commonfmt::*; |
| 796 | use nihav_codec_support::test::enc_video::*; |
| 797 | use super::super::truemotion1data::{RGB555_FORMAT, BGR0_FORMAT}; |
| 798 | |
| 799 | #[allow(unused_variables)] |
| 800 | fn encode_test(name: &'static str, enc_options: &[NAOption], hash: &[u32; 4], is16: bool) { |
| 801 | let mut dmx_reg = RegisteredDemuxers::new(); |
| 802 | generic_register_all_demuxers(&mut dmx_reg); |
| 803 | let mut dec_reg = RegisteredDecoders::new(); |
| 804 | duck_register_all_decoders(&mut dec_reg); |
| 805 | let mut mux_reg = RegisteredMuxers::new(); |
| 806 | generic_register_all_muxers(&mut mux_reg); |
| 807 | let mut enc_reg = RegisteredEncoders::new(); |
| 808 | duck_register_all_encoders(&mut enc_reg); |
| 809 | |
| 810 | // sample: https://samples.mplayerhq.hu/V-codecs/TM20/tm20.avi |
| 811 | let dec_config = DecoderTestParams { |
| 812 | demuxer: "avi", |
| 813 | in_name: "assets/Duck/tm20.avi", |
| 814 | stream_type: StreamType::Video, |
| 815 | limit: Some(2), |
| 816 | dmx_reg, dec_reg, |
| 817 | }; |
| 818 | let enc_config = EncoderTestParams { |
| 819 | muxer: "avi", |
| 820 | enc_name: "truemotion1", |
| 821 | out_name: name, |
| 822 | mux_reg, enc_reg, |
| 823 | }; |
| 824 | let dst_vinfo = NAVideoInfo { |
| 825 | width: 0, |
| 826 | height: 0, |
| 827 | format: if is16 { RGB555_FORMAT } else { BGR0_FORMAT }, |
| 828 | flipped: false, |
| 829 | bits: if is16 { 16 } else { 24 }, |
| 830 | }; |
| 831 | let enc_params = EncodeParameters { |
| 832 | format: NACodecTypeInfo::Video(dst_vinfo), |
| 833 | quality: 0, |
| 834 | bitrate: 0, |
| 835 | tb_num: 0, |
| 836 | tb_den: 0, |
| 837 | flags: 0, |
| 838 | }; |
| 839 | //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options); |
| 840 | test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash); |
| 841 | } |
| 842 | #[test] |
| 843 | fn test_truemotion1_encoder_rgb15_4x4() { |
| 844 | let enc_options = &[ |
| 845 | NAOption { name: "key_int", value: NAValue::Int(8) }, |
| 846 | NAOption { name: "block_mode", value: NAValue::String("4x4".to_string()) }, |
| 847 | ]; |
| 848 | encode_test("tm1-15.avi", enc_options, &[0x6b8a5d15, 0xc9c9a391, 0x588c95c5, 0x5568d3b3], true); |
| 849 | } |
| 850 | #[test] |
| 851 | fn test_truemotion1_encoder_rgb15_4x2() { |
| 852 | let enc_options = &[ |
| 853 | NAOption { name: "key_int", value: NAValue::Int(8) }, |
| 854 | NAOption { name: "block_mode", value: NAValue::String("4x2".to_string()) }, |
| 855 | ]; |
| 856 | encode_test("tm1-15.avi", enc_options, &[0x999c2ffd, 0xd637f7a3, 0x4ebc070a, 0xef6fca4b], true); |
| 857 | } |
| 858 | #[test] |
| 859 | fn test_truemotion1_encoder_rgb15_2x4() { |
| 860 | let enc_options = &[ |
| 861 | NAOption { name: "key_int", value: NAValue::Int(8) }, |
| 862 | NAOption { name: "block_mode", value: NAValue::String("2x4".to_string()) }, |
| 863 | ]; |
| 864 | encode_test("tm1-15.avi", enc_options, &[0xfe62d1e6, 0xbdd8f28b, 0xf7fd810e, 0x0a9142f1], true); |
| 865 | } |
| 866 | #[test] |
| 867 | fn test_truemotion1_encoder_rgb15_2x2() { |
| 868 | let enc_options = &[ |
| 869 | NAOption { name: "key_int", value: NAValue::Int(8) }, |
| 870 | NAOption { name: "block_mode", value: NAValue::String("2x2".to_string()) }, |
| 871 | ]; |
| 872 | encode_test("tm1-15.avi", enc_options, &[0x3b445eb8, 0xac7cab31, 0x4c2ce978, 0x9b698658], true); |
| 873 | } |
| 874 | /*#[test] |
| 875 | fn test_truemotion1_encoder_rgb24() { |
| 876 | let enc_options = &[ |
| 877 | NAOption { name: "key_int", value: NAValue::Int(0) }, |
| 878 | ]; |
| 879 | encode_test("tm1-24.avi", enc_options, &[0x36cf8f48, 0x3e8ff2ce, 0x6f3822cf, 0xf7fbf19d], false); |
| 880 | panic!("end"); |
| 881 | }*/ |
| 882 | } |
| 883 | |
| 884 | // fat deltas for 15-bit mode |
| 885 | #[allow(clippy::neg_multiply)] |
| 886 | const DUCK_Y_DELTAS_5: [[i32; 8]; 4] = [ |
| 887 | [ 0, -1 * 5, 1 * 5, -3 * 5, 3 * 5, -6 * 5, 6 * 5, -6 * 5 ], |
| 888 | [ 0, -1 * 5, 2 * 5, -3 * 5, 4 * 5, -6 * 5, 6 * 5, -6 * 5 ], |
| 889 | [ 2 * 5, -3 * 5, 10 * 5, -10 * 5, 23 * 5, -23 * 5, 47 * 5, -47 * 5 ], |
| 890 | [ 0, -2 * 5, 2 * 5, -8 * 5, 8 * 5, -18 * 5, 18 * 5, -40 * 5 ] |
| 891 | ]; |
| 892 | #[allow(clippy::neg_multiply)] |
| 893 | const DUCK_C_DELTAS_5: [[i32; 8]; 4] = [ |
| 894 | [ 0, -1 * 5, 1 * 5, -2 * 5, 3 * 5, -4 * 5, 5 * 5, -4 * 5 ], |
| 895 | [ 0, -1 * 5, 1 * 5, -2 * 5, 3 * 5, -4 * 5, 5 * 5, -4 * 5 ], |
| 896 | [ 0, -4 * 5, 3 * 5, -16 * 5, 20 * 5, -32 * 5, 36 * 5, -32 * 5 ], |
| 897 | [ 0, -2 * 5, 2 * 5, -8 * 5, 8 * 5, -18 * 5, 18 * 5, -40 * 5 ] |
| 898 | ]; |