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