1 // Copyright (c) 2018-2020, The rav1e contributors. All rights reserved
2 //
3 // This source code is subject to the terms of the BSD 2 Clause License and
4 // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
5 // was not distributed with this source code in the LICENSE file, you can
6 // obtain it at www.aomedia.org/license/software. If the Alliance for Open
7 // Media Patent License 1.0 was not distributed with this source code in the
8 // PATENTS file, you can obtain it at www.aomedia.org/license/patent.
9 
10 use av_metrics::video::*;
11 use rav1e::data::EncoderStats;
12 use rav1e::prelude::Rational;
13 use rav1e::prelude::*;
14 use rav1e::{Packet, Pixel};
15 use rust_hawktracer::*;
16 use std::fmt;
17 use std::time::Instant;
18 
19 #[derive(Debug, Clone)]
20 pub struct FrameSummary {
21   /// Frame size in bytes
22   pub size: usize,
23   pub input_frameno: u64,
24   pub frame_type: FrameType,
25   /// Contains metrics such as PSNR, SSIM, etc.
26   pub metrics: QualityMetrics,
27   /// QP selected for the frame.
28   pub qp: u8,
29   /// Block-level encoding stats for the frame
30   pub enc_stats: EncoderStats,
31 }
32 
33 #[hawktracer(build_frame_summary)]
build_frame_summary<T: Pixel>( packets: Packet<T>, bit_depth: usize, chroma_sampling: ChromaSampling, metrics_cli: MetricsEnabled, ) -> FrameSummary34 pub fn build_frame_summary<T: Pixel>(
35   packets: Packet<T>, bit_depth: usize, chroma_sampling: ChromaSampling,
36   metrics_cli: MetricsEnabled,
37 ) -> FrameSummary {
38   let metrics_input_frame: &Frame<T> = packets.source.as_ref().unwrap();
39   let metrics_output_frame: &Frame<T> = packets.rec.as_ref().unwrap();
40   let encode_metrics: QualityMetrics = calculate_frame_metrics(
41     metrics_input_frame,
42     metrics_output_frame,
43     bit_depth,
44     chroma_sampling,
45     metrics_cli,
46   );
47   FrameSummary {
48     size: packets.data.len(),
49     input_frameno: packets.input_frameno,
50     frame_type: packets.frame_type,
51     metrics: encode_metrics,
52     qp: packets.qp,
53     enc_stats: packets.enc_stats,
54   }
55 }
56 
57 impl fmt::Display for FrameSummary {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result58   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59     write!(
60       f,
61       "Input Frame {} - {} - {} bytes{}",
62       self.input_frameno,
63       self.frame_type,
64       self.size,
65       if let Some(psnr) = self.metrics.psnr {
66         format!(
67           " - PSNR: Y: {:.4}  Cb: {:.4}  Cr: {:.4}",
68           psnr.y, psnr.u, psnr.v
69         )
70       } else {
71         String::new()
72       }
73     )
74   }
75 }
76 
77 #[derive(Debug, Clone)]
78 pub struct ProgressInfo {
79   // Frame rate of the video
80   frame_rate: Rational,
81   // The length of the whole video, in frames, if known
82   total_frames: Option<usize>,
83   // The time the encode was started
84   time_started: Instant,
85   // List of frames encoded so far
86   frame_info: Vec<FrameSummary>,
87   // Video size so far in bytes.
88   //
89   // This value will be updated in the CLI very frequently, so we cache the previous value
90   // to reduce the overall complexity.
91   encoded_size: usize,
92   // Which Metrics to display during and at end of encode
93   metrics_enabled: MetricsEnabled,
94 }
95 
96 impl ProgressInfo {
new( frame_rate: Rational, total_frames: Option<usize>, metrics_enabled: MetricsEnabled, ) -> Self97   pub fn new(
98     frame_rate: Rational, total_frames: Option<usize>,
99     metrics_enabled: MetricsEnabled,
100   ) -> Self {
101     Self {
102       frame_rate,
103       total_frames,
104       time_started: Instant::now(),
105       frame_info: Vec::with_capacity(total_frames.unwrap_or_default()),
106       encoded_size: 0,
107       metrics_enabled,
108     }
109   }
110 
add_frame(&mut self, frame: FrameSummary)111   pub fn add_frame(&mut self, frame: FrameSummary) {
112     self.encoded_size += frame.size;
113     self.frame_info.push(frame);
114   }
115 
frames_encoded(&self) -> usize116   pub fn frames_encoded(&self) -> usize {
117     self.frame_info.len()
118   }
119 
encoding_fps(&self) -> f64120   pub fn encoding_fps(&self) -> f64 {
121     let duration = Instant::now().duration_since(self.time_started);
122     self.frame_info.len() as f64
123       / (duration.as_secs() as f64 + duration.subsec_millis() as f64 / 1000f64)
124   }
125 
video_fps(&self) -> f64126   pub fn video_fps(&self) -> f64 {
127     self.frame_rate.num as f64 / self.frame_rate.den as f64
128   }
129 
130   // Returns the bitrate of the frames so far, in bits/second
bitrate(&self) -> usize131   pub fn bitrate(&self) -> usize {
132     let bits = self.encoded_size * 8;
133     let seconds = self.frame_info.len() as f64 / self.video_fps();
134     (bits as f64 / seconds) as usize
135   }
136 
137   // Estimates the final filesize in bytes, if the number of frames is known
estimated_size(&self) -> usize138   pub fn estimated_size(&self) -> usize {
139     self
140       .total_frames
141       .map(|frames| self.encoded_size * frames / self.frames_encoded())
142       .unwrap_or_default()
143   }
144 
145   // Estimates the remaining encoding time in seconds, if the number of frames is known
estimated_time(&self) -> u64146   pub fn estimated_time(&self) -> u64 {
147     self
148       .total_frames
149       .map(|frames| {
150         (frames - self.frames_encoded()) as f64 / self.encoding_fps()
151       })
152       .unwrap_or_default() as u64
153   }
154 
155   // Elapsed time in seconds
elapsed_time(&self) -> u64156   pub fn elapsed_time(&self) -> u64 {
157     Instant::now().duration_since(self.time_started).as_secs() as u64
158   }
159 
160   // Number of frames of given type which appear in the video
get_frame_type_count(&self, frame_type: FrameType) -> usize161   fn get_frame_type_count(&self, frame_type: FrameType) -> usize {
162     self
163       .frame_info
164       .iter()
165       .filter(|frame| frame.frame_type == frame_type)
166       .count()
167   }
168 
get_frame_type_avg_size(&self, frame_type: FrameType) -> usize169   fn get_frame_type_avg_size(&self, frame_type: FrameType) -> usize {
170     let count = self.get_frame_type_count(frame_type);
171     if count == 0 {
172       return 0;
173     }
174     self
175       .frame_info
176       .iter()
177       .filter(|frame| frame.frame_type == frame_type)
178       .map(|frame| frame.size)
179       .sum::<usize>()
180       / count
181   }
182 
get_frame_type_avg_qp(&self, frame_type: FrameType) -> f32183   fn get_frame_type_avg_qp(&self, frame_type: FrameType) -> f32 {
184     let count = self.get_frame_type_count(frame_type);
185     if count == 0 {
186       return 0.;
187     }
188     self
189       .frame_info
190       .iter()
191       .filter(|frame| frame.frame_type == frame_type)
192       .map(|frame| frame.qp as f32)
193       .sum::<f32>()
194       / count as f32
195   }
196 
get_block_count_by_frame_type(&self, frame_type: FrameType) -> usize197   fn get_block_count_by_frame_type(&self, frame_type: FrameType) -> usize {
198     self
199       .frame_info
200       .iter()
201       .filter(|frame| frame.frame_type == frame_type)
202       .map(|frame| frame.enc_stats.block_size_counts.iter().sum::<usize>())
203       .sum()
204   }
205 
get_tx_count_by_frame_type(&self, frame_type: FrameType) -> usize206   fn get_tx_count_by_frame_type(&self, frame_type: FrameType) -> usize {
207     self
208       .frame_info
209       .iter()
210       .filter(|frame| frame.frame_type == frame_type)
211       .map(|frame| frame.enc_stats.tx_type_counts.iter().sum::<usize>())
212       .sum()
213   }
214 
get_bsize_pct_by_frame_type( &self, bsize: BlockSize, frame_type: FrameType, ) -> f32215   fn get_bsize_pct_by_frame_type(
216     &self, bsize: BlockSize, frame_type: FrameType,
217   ) -> f32 {
218     let count = self.get_block_count_by_frame_type(frame_type);
219     if count == 0 {
220       return 0.;
221     }
222     self
223       .frame_info
224       .iter()
225       .filter(|frame| frame.frame_type == frame_type)
226       .map(|frame| frame.enc_stats.block_size_counts[bsize as usize])
227       .sum::<usize>() as f32
228       / count as f32
229       * 100.
230   }
231 
get_skip_pct_by_frame_type(&self, frame_type: FrameType) -> f32232   fn get_skip_pct_by_frame_type(&self, frame_type: FrameType) -> f32 {
233     let count = self.get_block_count_by_frame_type(frame_type);
234     if count == 0 {
235       return 0.;
236     }
237     self
238       .frame_info
239       .iter()
240       .filter(|frame| frame.frame_type == frame_type)
241       .map(|frame| frame.enc_stats.skip_block_count)
242       .sum::<usize>() as f32
243       / count as f32
244       * 100.
245   }
246 
get_txtype_pct_by_frame_type( &self, txtype: TxType, frame_type: FrameType, ) -> f32247   fn get_txtype_pct_by_frame_type(
248     &self, txtype: TxType, frame_type: FrameType,
249   ) -> f32 {
250     let count = self.get_tx_count_by_frame_type(frame_type);
251     if count == 0 {
252       return 0.;
253     }
254     self
255       .frame_info
256       .iter()
257       .filter(|frame| frame.frame_type == frame_type)
258       .map(|frame| frame.enc_stats.tx_type_counts[txtype as usize])
259       .sum::<usize>() as f32
260       / count as f32
261       * 100.
262   }
263 
get_luma_pred_count_by_frame_type(&self, frame_type: FrameType) -> usize264   fn get_luma_pred_count_by_frame_type(&self, frame_type: FrameType) -> usize {
265     self
266       .frame_info
267       .iter()
268       .filter(|frame| frame.frame_type == frame_type)
269       .map(|frame| frame.enc_stats.luma_pred_mode_counts.iter().sum::<usize>())
270       .sum()
271   }
272 
get_chroma_pred_count_by_frame_type( &self, frame_type: FrameType, ) -> usize273   fn get_chroma_pred_count_by_frame_type(
274     &self, frame_type: FrameType,
275   ) -> usize {
276     self
277       .frame_info
278       .iter()
279       .filter(|frame| frame.frame_type == frame_type)
280       .map(|frame| {
281         frame.enc_stats.chroma_pred_mode_counts.iter().sum::<usize>()
282       })
283       .sum()
284   }
285 
get_luma_pred_mode_pct_by_frame_type( &self, pred_mode: PredictionMode, frame_type: FrameType, ) -> f32286   fn get_luma_pred_mode_pct_by_frame_type(
287     &self, pred_mode: PredictionMode, frame_type: FrameType,
288   ) -> f32 {
289     let count = self.get_luma_pred_count_by_frame_type(frame_type);
290     if count == 0 {
291       return 0.;
292     }
293     self
294       .frame_info
295       .iter()
296       .filter(|frame| frame.frame_type == frame_type)
297       .map(|frame| frame.enc_stats.luma_pred_mode_counts[pred_mode as usize])
298       .sum::<usize>() as f32
299       / count as f32
300       * 100.
301   }
302 
get_chroma_pred_mode_pct_by_frame_type( &self, pred_mode: PredictionMode, frame_type: FrameType, ) -> f32303   fn get_chroma_pred_mode_pct_by_frame_type(
304     &self, pred_mode: PredictionMode, frame_type: FrameType,
305   ) -> f32 {
306     let count = self.get_chroma_pred_count_by_frame_type(frame_type);
307     if count == 0 {
308       return 0.;
309     }
310     self
311       .frame_info
312       .iter()
313       .filter(|frame| frame.frame_type == frame_type)
314       .map(|frame| frame.enc_stats.chroma_pred_mode_counts[pred_mode as usize])
315       .sum::<usize>() as f32
316       / count as f32
317       * 100.
318   }
319 
print_summary(&self, verbose: bool)320   pub fn print_summary(&self, verbose: bool) {
321     eprint!("\r");
322     info!("{}", self);
323     info!("----------");
324     self.print_frame_type_summary(FrameType::KEY);
325     self.print_frame_type_summary(FrameType::INTER);
326     self.print_frame_type_summary(FrameType::INTRA_ONLY);
327     self.print_frame_type_summary(FrameType::SWITCH);
328     if verbose {
329       self.print_block_type_summary();
330       self.print_transform_type_summary();
331       self.print_prediction_modes_summary();
332     }
333     match self.metrics_enabled {
334       MetricsEnabled::None => info!("----"),
335       MetricsEnabled::Psnr => self.print_video_psnr(),
336       MetricsEnabled::All => {
337         self.print_video_psnr();
338         self.print_video_all();
339       }
340     }
341   }
342 
print_frame_type_summary(&self, frame_type: FrameType)343   fn print_frame_type_summary(&self, frame_type: FrameType) {
344     let count = self.get_frame_type_count(frame_type);
345     let size = self.get_frame_type_avg_size(frame_type);
346     let avg_qp = self.get_frame_type_avg_qp(frame_type);
347     info!(
348       "{:17} {:>6} | avg QP: {:6.2} | avg size: {:>7} B",
349       format!("{}:", frame_type),
350       count,
351       avg_qp,
352       size
353     );
354   }
355 
print_video_psnr(&self)356   fn print_video_psnr(&self) {
357     info!("----------");
358     let psnr_y = sum_metric(&self.frame_info, |fi| fi.metrics.psnr.unwrap().y);
359     let psnr_u = sum_metric(&self.frame_info, |fi| fi.metrics.psnr.unwrap().u);
360     let psnr_v = sum_metric(&self.frame_info, |fi| fi.metrics.psnr.unwrap().v);
361     let psnr_avg =
362       sum_metric(&self.frame_info, |fi| fi.metrics.psnr.unwrap().avg);
363     info!(
364       "Mean PSNR: Avg: {:.4}  Y: {:.4}  Cb: {:.4}  Cr: {:.4}",
365       psnr_avg, psnr_y, psnr_u, psnr_v
366     );
367   }
print_video_all(&self)368   fn print_video_all(&self) {
369     info!("----------");
370     let psnr_hvs =
371       sum_metric(&self.frame_info, |fi| fi.metrics.psnr_hvs.unwrap().avg);
372     let ssim = sum_metric(&self.frame_info, |fi| fi.metrics.ssim.unwrap().avg);
373     let ms_ssim =
374       sum_metric(&self.frame_info, |fi| fi.metrics.ms_ssim.unwrap().avg);
375     let ciede = sum_metric(&self.frame_info, |fi| fi.metrics.ciede.unwrap());
376     info!("PSNR HVS: {:.4}", psnr_hvs);
377     info!("SSIM: {:.4}  MS SSIM: {:.4}", ssim, ms_ssim);
378     info!("CIEDE2000: {:.4}", ciede);
379     info!("----------");
380   }
381 
print_block_type_summary(&self)382   fn print_block_type_summary(&self) {
383     self.print_block_type_summary_for_frame_type(FrameType::KEY, 'I');
384     self.print_block_type_summary_for_frame_type(FrameType::INTER, 'P');
385   }
386 
print_block_type_summary_for_frame_type( &self, frame_type: FrameType, type_label: char, )387   fn print_block_type_summary_for_frame_type(
388     &self, frame_type: FrameType, type_label: char,
389   ) {
390     info!("----------");
391     info!(
392       "bsize {}: {:>6} {:>6} {:>6} {:>6} {:>6} {:>6}",
393       type_label, "x128", "x64", "x32", "x16", "x8", "x4"
394     );
395     info!(
396       "   128x: {:>5.1}% {:>5.1}%                              {}",
397       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_128X128, frame_type),
398       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_128X64, frame_type),
399       if frame_type == FrameType::INTER {
400         format!("skip: {:>5.1}%", self.get_skip_pct_by_frame_type(frame_type))
401       } else {
402         String::new()
403       }
404     );
405     info!(
406       "    64x: {:>5.1}% {:>5.1}% {:>5.1}% {:>5.1}%",
407       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_64X128, frame_type),
408       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_64X64, frame_type),
409       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_64X32, frame_type),
410       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_64X16, frame_type),
411     );
412     info!(
413       "    32x:        {:>5.1}% {:>5.1}% {:>5.1}% {:>5.1}%",
414       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_32X64, frame_type),
415       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_32X32, frame_type),
416       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_32X16, frame_type),
417       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_32X8, frame_type),
418     );
419     info!(
420       "    16x:        {:>5.1}% {:>5.1}% {:>5.1}% {:>5.1}% {:>5.1}%",
421       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_16X64, frame_type),
422       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_16X32, frame_type),
423       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_16X16, frame_type),
424       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_16X8, frame_type),
425       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_16X4, frame_type),
426     );
427     info!(
428       "     8x:               {:>5.1}% {:>5.1}% {:>5.1}% {:>5.1}%",
429       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_8X32, frame_type),
430       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_8X16, frame_type),
431       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_8X8, frame_type),
432       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_8X4, frame_type),
433     );
434     info!(
435       "     4x:                      {:>5.1}% {:>5.1}% {:>5.1}%",
436       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_4X16, frame_type),
437       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_4X8, frame_type),
438       self.get_bsize_pct_by_frame_type(BlockSize::BLOCK_4X4, frame_type),
439     );
440   }
441 
print_transform_type_summary(&self)442   fn print_transform_type_summary(&self) {
443     info!("----------");
444     self.print_transform_type_summary_by_frame_type(FrameType::KEY, 'I');
445     self.print_transform_type_summary_by_frame_type(FrameType::INTER, 'P');
446   }
447 
print_transform_type_summary_by_frame_type( &self, frame_type: FrameType, type_label: char, )448   fn print_transform_type_summary_by_frame_type(
449     &self, frame_type: FrameType, type_label: char,
450   ) {
451     info!(
452       "txtypes {}: DCT_DCT: {:.1}% | ADST_DCT: {:.1}% | DCT_ADST: {:.1}% | ADST_ADST: {:.1}%",
453       type_label,
454       self.get_txtype_pct_by_frame_type(TxType::DCT_DCT, frame_type),
455       self.get_txtype_pct_by_frame_type(TxType::ADST_DCT, frame_type),
456       self.get_txtype_pct_by_frame_type(TxType::DCT_ADST, frame_type),
457       self.get_txtype_pct_by_frame_type(TxType::ADST_ADST, frame_type)
458     );
459     info!(
460       "           IDTX: {:.1}% | V_DCT: {:.1}% | H_DCT: {:.1}%",
461       self.get_txtype_pct_by_frame_type(TxType::IDTX, frame_type),
462       self.get_txtype_pct_by_frame_type(TxType::V_DCT, frame_type),
463       self.get_txtype_pct_by_frame_type(TxType::H_DCT, frame_type),
464     )
465   }
466 
print_prediction_modes_summary(&self)467   fn print_prediction_modes_summary(&self) {
468     info!("----------");
469     self.print_luma_prediction_mode_summary_by_frame_type(FrameType::KEY, 'I');
470     self
471       .print_chroma_prediction_mode_summary_by_frame_type(FrameType::KEY, 'I');
472     info!("----------");
473     self
474       .print_luma_prediction_mode_summary_by_frame_type(FrameType::INTER, 'P');
475     self.print_chroma_prediction_mode_summary_by_frame_type(
476       FrameType::INTER,
477       'P',
478     );
479   }
480 
print_luma_prediction_mode_summary_by_frame_type( &self, frame_type: FrameType, type_label: char, )481   fn print_luma_prediction_mode_summary_by_frame_type(
482     &self, frame_type: FrameType, type_label: char,
483   ) {
484     if frame_type == FrameType::KEY {
485       info!(
486         "y modes {}: DC: {:.1}% | V: {:.1}% | H: {:.1}% | Paeth: {:.1}%",
487         type_label,
488         self.get_luma_pred_mode_pct_by_frame_type(
489           PredictionMode::DC_PRED,
490           frame_type
491         ),
492         self.get_luma_pred_mode_pct_by_frame_type(
493           PredictionMode::V_PRED,
494           frame_type
495         ),
496         self.get_luma_pred_mode_pct_by_frame_type(
497           PredictionMode::H_PRED,
498           frame_type
499         ),
500         self.get_luma_pred_mode_pct_by_frame_type(
501           PredictionMode::PAETH_PRED,
502           frame_type
503         ),
504       );
505       info!(
506         "           Smooth: {:.1}% | Smooth V: {:.1}% | Smooth H: {:.1}%",
507         self.get_luma_pred_mode_pct_by_frame_type(
508           PredictionMode::SMOOTH_PRED,
509           frame_type
510         ),
511         self.get_luma_pred_mode_pct_by_frame_type(
512           PredictionMode::SMOOTH_V_PRED,
513           frame_type
514         ),
515         self.get_luma_pred_mode_pct_by_frame_type(
516           PredictionMode::SMOOTH_H_PRED,
517           frame_type
518         ),
519       );
520       // Keep angular order for presentation here, rather than enum order.
521       info!(
522       "        D: 45: {:.1}% | 67: {:.1}% | 113: {:.1}% | 135: {:.1}% | 157: {:.1}% | 203: {:.1}%",
523       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D45_PRED, frame_type),
524       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D67_PRED, frame_type),
525       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D113_PRED, frame_type),
526       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D135_PRED, frame_type),
527       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D157_PRED, frame_type),
528       self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::D203_PRED, frame_type),
529       );
530     } else if frame_type == FrameType::INTER {
531       info!(
532         "y modes {}: Nearest: {:.1}% | Near0: {:.1}% | Near1: {:.1}% | NearNear0: {:.1}% | NearNear1: {:.1}% | NearNear2: {:.1}%",
533         type_label,
534         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEARESTMV, frame_type),
535         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEAR0MV, frame_type),
536         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEAR1MV, frame_type),
537         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR0MV, frame_type),
538         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR1MV, frame_type),
539         self.get_luma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR2MV, frame_type),
540       );
541       info!(
542         "y modes {}: NearNew0: {:.1}% | NearNew1: {:.1}% | NearNew2: {:.1}%",
543         type_label,
544         self.get_luma_pred_mode_pct_by_frame_type(
545           PredictionMode::NEAR_NEW0MV,
546           frame_type
547         ),
548         self.get_luma_pred_mode_pct_by_frame_type(
549           PredictionMode::NEAR_NEW1MV,
550           frame_type
551         ),
552         self.get_luma_pred_mode_pct_by_frame_type(
553           PredictionMode::NEAR_NEW2MV,
554           frame_type
555         ),
556       );
557       info!(
558         "y modes {}: NewNear0: {:.1}% | NewNear1: {:.1}% | NewNear2: {:.1}%",
559         type_label,
560         self.get_luma_pred_mode_pct_by_frame_type(
561           PredictionMode::NEW_NEAR0MV,
562           frame_type
563         ),
564         self.get_luma_pred_mode_pct_by_frame_type(
565           PredictionMode::NEW_NEAR1MV,
566           frame_type
567         ),
568         self.get_luma_pred_mode_pct_by_frame_type(
569           PredictionMode::NEW_NEAR2MV,
570           frame_type
571         ),
572       );
573     }
574   }
575 
print_chroma_prediction_mode_summary_by_frame_type( &self, frame_type: FrameType, type_label: char, )576   fn print_chroma_prediction_mode_summary_by_frame_type(
577     &self, frame_type: FrameType, type_label: char,
578   ) {
579     if frame_type == FrameType::KEY {
580       info!(
581         "uv modes {}: DC: {:.1}% | V: {:.1}% | H: {:.1}% | Paeth: {:.1}%",
582         type_label,
583         self.get_chroma_pred_mode_pct_by_frame_type(
584           PredictionMode::DC_PRED,
585           frame_type
586         ),
587         self.get_chroma_pred_mode_pct_by_frame_type(
588           PredictionMode::V_PRED,
589           frame_type
590         ),
591         self.get_chroma_pred_mode_pct_by_frame_type(
592           PredictionMode::H_PRED,
593           frame_type
594         ),
595         self.get_chroma_pred_mode_pct_by_frame_type(
596           PredictionMode::PAETH_PRED,
597           frame_type
598         ),
599       );
600       info!(
601         "            Smooth: {:.1}% | Smooth V: {:.1}% | Smooth H: {:.1}% | UV CFL: {:.1}%",
602         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::SMOOTH_PRED, frame_type),
603         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::SMOOTH_V_PRED, frame_type),
604         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::SMOOTH_H_PRED, frame_type),
605         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::UV_CFL_PRED, frame_type),
606       );
607       // Keep angular order for presentation here, rather than enum order.
608       info!(
609         "         D: 45: {:.1}% | 67: {:.1}% | 113: {:.1}% | 135: {:.1}% | 157: {:.1}% | 203: {:.1}%",
610         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D45_PRED, frame_type),
611         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D67_PRED, frame_type),
612         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D113_PRED, frame_type),
613         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D135_PRED, frame_type),
614         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D157_PRED, frame_type),
615         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::D203_PRED, frame_type),
616       );
617     } else if frame_type == FrameType::INTER {
618       info!(
619         "uv modes {}: Nearest: {:.1}% | Near0: {:.1}% | Near1: {:.1}% | NearNear0: {:.1}% | NearNear1: {:.1}% | NearNear2: {:.1}%",
620         type_label,
621         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEARESTMV, frame_type),
622         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAR0MV, frame_type),
623         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAR1MV, frame_type),
624         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR0MV, frame_type),
625         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR1MV, frame_type),
626         self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAR_NEAR2MV, frame_type),
627       );
628       info!(
629         "uv modes {}: NearNew0: {:.1}% | NearNew1: {:.1}% | NearNew2: {:.1}%",
630         type_label,
631         self.get_chroma_pred_mode_pct_by_frame_type(
632           PredictionMode::NEAR_NEW0MV,
633           frame_type
634         ),
635         self.get_chroma_pred_mode_pct_by_frame_type(
636           PredictionMode::NEAR_NEW1MV,
637           frame_type
638         ),
639         self.get_chroma_pred_mode_pct_by_frame_type(
640           PredictionMode::NEAR_NEW2MV,
641           frame_type
642         ),
643       );
644       info!(
645         "uv modes {}: NewNear0: {:.1}% | NewNear1: {:.1}% | NewNear2: {:.1}%",
646         type_label,
647         self.get_chroma_pred_mode_pct_by_frame_type(
648           PredictionMode::NEW_NEAR0MV,
649           frame_type
650         ),
651         self.get_chroma_pred_mode_pct_by_frame_type(
652           PredictionMode::NEW_NEAR1MV,
653           frame_type
654         ),
655         self.get_chroma_pred_mode_pct_by_frame_type(
656           PredictionMode::NEW_NEAR2MV,
657           frame_type
658         ),
659       );
660       info!("            New: {:.1}% | NewNew: {:.1}% | NearestNearest: {:.1}% | GlobalGlobal: {:.1}%",
661             self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEWMV, frame_type),
662             self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEW_NEWMV, frame_type),
663             self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::NEAREST_NEARESTMV, frame_type),
664             self.get_chroma_pred_mode_pct_by_frame_type(PredictionMode::GLOBAL_GLOBALMV, frame_type),);
665     }
666   }
667 }
668 
sum_metric<F: FnMut(&FrameSummary) -> f64>( frame_info: &[FrameSummary], map_fn: F, ) -> f64669 fn sum_metric<F: FnMut(&FrameSummary) -> f64>(
670   frame_info: &[FrameSummary], map_fn: F,
671 ) -> f64 {
672   frame_info.iter().map(map_fn).sum::<f64>() / frame_info.len() as f64
673 }
674 
675 impl fmt::Display for ProgressInfo {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result676   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677     if let Some(total_frames) = self.total_frames {
678       write!(
679                 f,
680                 "encoded {}/{} frames, {:.3} fps, {:.2} Kb/s, est. size: {:.2} MB, est. time: {},  elap. time: {}",
681                 self.frames_encoded(),
682                 total_frames,
683                 self.encoding_fps(),
684                 self.bitrate() as f64 / 1000f64,
685                 self.estimated_size() as f64 / (1024 * 1024) as f64,
686                 secs_to_human_time(self.estimated_time()),
687                 secs_to_human_time(self.elapsed_time())
688             )
689     } else {
690       write!(
691         f,
692         "encoded {} frames, {:.3} fps, {:.2} Kb/s, elap. time: {}",
693         self.frames_encoded(),
694         self.encoding_fps(),
695         self.bitrate() as f64 / 1000f64,
696         secs_to_human_time(self.elapsed_time())
697       )
698     }
699   }
700 }
701 
secs_to_human_time(mut secs: u64) -> String702 fn secs_to_human_time(mut secs: u64) -> String {
703   let mut mins = secs / 60;
704   secs %= 60;
705   let hours = mins / 60;
706   mins %= 60;
707   if hours > 0 {
708     format!("{}h {}m {}s", hours, mins, secs)
709   } else if mins > 0 {
710     format!("{}m {}s", mins, secs)
711   } else {
712     format!("{}s", secs)
713   }
714 }
715 
716 #[derive(Debug, Clone, Copy, Default, PartialEq)]
717 pub struct QualityMetrics {
718   /// Peak Signal-to-Noise Ratio for Y, U, and V planes
719   pub psnr: Option<PlanarMetrics>,
720   /// Peak Signal-to-Noise Ratio as perceived by the Human Visual System--
721   /// taking into account Contrast Sensitivity Function (CSF)
722   pub psnr_hvs: Option<PlanarMetrics>,
723   /// Structural Similarity
724   pub ssim: Option<PlanarMetrics>,
725   /// Multi-Scale Structural Similarity
726   pub ms_ssim: Option<PlanarMetrics>,
727   /// CIEDE 2000 color difference algorithm: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
728   pub ciede: Option<f64>,
729   /// Aligned Peak Signal-to-Noise Ratio for Y, U, and V planes
730   pub apsnr: Option<PlanarMetrics>,
731   /// Netflix's Video Multimethod Assessment Fusion
732   pub vmaf: Option<f64>,
733 }
734 
735 #[derive(Debug, Clone, Copy, PartialEq)]
736 pub enum MetricsEnabled {
737   /// Don't calculate any metrics.
738   None,
739   /// Calculate the PSNR of each plane, but no other metrics.
740   Psnr,
741   /// Calculate all implemented metrics. Currently implemented metrics match what is available via AWCY.
742   All,
743 }
744 
745 #[hawktracer(calculate_frame_metrics)]
calculate_frame_metrics<T: Pixel>( frame1: &Frame<T>, frame2: &Frame<T>, bit_depth: usize, cs: ChromaSampling, metrics: MetricsEnabled, ) -> QualityMetrics746 pub fn calculate_frame_metrics<T: Pixel>(
747   frame1: &Frame<T>, frame2: &Frame<T>, bit_depth: usize, cs: ChromaSampling,
748   metrics: MetricsEnabled,
749 ) -> QualityMetrics {
750   let frame1_info = FrameInfo {
751     planes: frame1.planes.clone(),
752     bit_depth,
753     chroma_sampling: cs,
754   };
755 
756   let frame2_info = FrameInfo {
757     planes: frame2.planes.clone(),
758     bit_depth,
759     chroma_sampling: cs,
760   };
761 
762   match metrics {
763     MetricsEnabled::None => QualityMetrics::default(),
764     MetricsEnabled::Psnr => QualityMetrics {
765       psnr: Some(
766         psnr::calculate_frame_psnr(&frame1_info, &frame2_info).unwrap(),
767       ),
768       ..Default::default()
769     },
770     MetricsEnabled::All => {
771       let mut metrics = QualityMetrics {
772         psnr: Some(
773           psnr::calculate_frame_psnr(&frame1_info, &frame2_info).unwrap(),
774         ),
775         psnr_hvs: Some(
776           psnr_hvs::calculate_frame_psnr_hvs(&frame1_info, &frame2_info)
777             .unwrap(),
778         ),
779         ..Default::default()
780       };
781       let ssim = ssim::calculate_frame_ssim(&frame1_info, &frame2_info);
782       metrics.ssim = Some(ssim.unwrap());
783       let ms_ssim = ssim::calculate_frame_msssim(&frame1_info, &frame2_info);
784       metrics.ms_ssim = Some(ms_ssim.unwrap());
785       let ciede = ciede::calculate_frame_ciede(&frame1_info, &frame2_info);
786       metrics.ciede = Some(ciede.unwrap());
787       // TODO APSNR
788       // TODO VMAF
789       metrics
790     }
791   }
792 }
793