]>
Commit | Line | Data |
---|---|---|
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 | ]; |