1/*
2 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "testsupport/metrics/video_metrics.h"
12
13#include <algorithm> // min_element, max_element
14#include <cassert>
15#include <cstdio>
16
17#include "common_video/libyuv/include/libyuv.h"
18
19namespace webrtc {
20namespace test {
21
22// Used for calculating min and max values
23static bool LessForFrameResultValue (const FrameResult& s1,
24                                     const FrameResult& s2) {
25    return s1.value < s2.value;
26}
27
28enum VideoMetricsType { kPSNR, kSSIM, kBoth };
29
30// Calculates metrics for a frame and adds statistics to the result for it.
31void CalculateFrame(VideoMetricsType video_metrics_type,
32                    uint8_t* ref,
33                    uint8_t* test,
34                    int width,
35                    int height,
36                    int frame_number,
37                    QualityMetricsResult* result) {
38  FrameResult frame_result;
39  frame_result.frame_number = frame_number;
40  switch (video_metrics_type) {
41    case kPSNR:
42      frame_result.value = I420PSNR(ref, test, width, height);
43      break;
44    case kSSIM:
45      frame_result.value = I420SSIM(ref, test, width, height);
46      break;
47    default:
48      assert(false);
49  }
50  result->frames.push_back(frame_result);
51}
52
53// Calculates average, min and max values for the supplied struct, if non-NULL.
54void CalculateStats(QualityMetricsResult* result) {
55  if (result == NULL || result->frames.size() == 0) {
56    return;
57  }
58  // Calculate average
59  std::vector<FrameResult>::iterator iter;
60  double metrics_values_sum = 0.0;
61  for (iter = result->frames.begin(); iter != result->frames.end(); ++iter) {
62    metrics_values_sum += iter->value;
63  }
64  result->average = metrics_values_sum / result->frames.size();
65
66  // Calculate min/max statistics
67  iter = min_element(result->frames.begin(), result->frames.end(),
68                     LessForFrameResultValue);
69  result->min = iter->value;
70  result->min_frame_number = iter->frame_number;
71  iter = max_element(result->frames.begin(), result->frames.end(),
72                     LessForFrameResultValue);
73  result->max = iter->value;
74  result->max_frame_number = iter->frame_number;
75}
76
77// Single method that handles all combinations of video metrics calculation, to
78// minimize code duplication. Either psnr_result or ssim_result may be NULL,
79// depending on which VideoMetricsType is targeted.
80int CalculateMetrics(VideoMetricsType video_metrics_type,
81                     const char* ref_filename,
82                     const char* test_filename,
83                     int width,
84                     int height,
85                     QualityMetricsResult* psnr_result,
86                     QualityMetricsResult* ssim_result) {
87  assert(ref_filename != NULL);
88  assert(test_filename != NULL);
89  assert(width > 0);
90  assert(height > 0);
91
92  FILE* ref_fp = fopen(ref_filename, "rb");
93  if (ref_fp == NULL) {
94    // cannot open reference file
95    fprintf(stderr, "Cannot open file %s\n", ref_filename);
96    return -1;
97  }
98  FILE* test_fp = fopen(test_filename, "rb");
99  if (test_fp == NULL) {
100    // cannot open test file
101    fprintf(stderr, "Cannot open file %s\n", test_filename);
102    fclose(ref_fp);
103    return -2;
104  }
105  int frame_number = 0;
106
107  // Allocating size for one I420 frame.
108  const int frame_length = 3 * width * height >> 1;
109  uint8_t* ref = new uint8_t[frame_length];
110  uint8_t* test = new uint8_t[frame_length];
111
112  int ref_bytes = fread(ref, 1, frame_length, ref_fp);
113  int test_bytes = fread(test, 1, frame_length, test_fp);
114  while (ref_bytes == frame_length && test_bytes == frame_length) {
115    switch (video_metrics_type) {
116      case kPSNR:
117        CalculateFrame(kPSNR, ref, test, width, height, frame_number,
118                       psnr_result);
119        break;
120      case kSSIM:
121        CalculateFrame(kSSIM, ref, test, width, height, frame_number,
122                       ssim_result);
123        break;
124      case kBoth:
125        CalculateFrame(kPSNR, ref, test, width, height, frame_number,
126                       psnr_result);
127        CalculateFrame(kSSIM, ref, test, width, height, frame_number,
128                       ssim_result);
129        break;
130      default:
131        assert(false);
132    }
133    frame_number++;
134    ref_bytes = fread(ref, 1, frame_length, ref_fp);
135    test_bytes = fread(test, 1, frame_length, test_fp);
136  }
137  int return_code = 0;
138  if (frame_number == 0) {
139    fprintf(stderr, "Tried to measure video metrics from empty files "
140            "(reference file: %s  test file: %s)\n", ref_filename,
141            test_filename);
142    return_code = -3;
143  } else {
144    CalculateStats(psnr_result);
145    CalculateStats(ssim_result);
146  }
147  delete [] ref;
148  delete [] test;
149  fclose(ref_fp);
150  fclose(test_fp);
151  return return_code;
152}
153
154int I420MetricsFromFiles(const char* ref_filename,
155                         const char* test_filename,
156                         int width,
157                         int height,
158                         QualityMetricsResult* psnr_result,
159                         QualityMetricsResult* ssim_result) {
160  assert(psnr_result != NULL);
161  assert(ssim_result != NULL);
162  return CalculateMetrics(kBoth, ref_filename, test_filename, width, height,
163                          psnr_result, ssim_result);
164}
165
166int I420PSNRFromFiles(const char* ref_filename,
167                      const char* test_filename,
168                      int width,
169                      int height,
170                      QualityMetricsResult* result) {
171  assert(result != NULL);
172  return CalculateMetrics(kPSNR, ref_filename, test_filename, width, height,
173                          result, NULL);
174}
175
176int I420SSIMFromFiles(const char* ref_filename,
177                      const char* test_filename,
178                      int width,
179                      int height,
180                      QualityMetricsResult* result) {
181  assert(result != NULL);
182  return CalculateMetrics(kSSIM, ref_filename, test_filename, width, height,
183                          NULL, result);
184}
185
186}  // namespace test
187}  // namespace webrtc
188