video_metrics.cc revision 4765070b8d6f024509c717c04d9b708750666927
1/*
2 *  Copyright (c) 2012 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 "webrtc/test/testsupport/metrics/video_metrics.h"
12
13#include <assert.h>
14#include <stdio.h>
15
16#include <algorithm>  // min_element, max_element
17
18#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
19#include "webrtc/video_frame.h"
20
21namespace webrtc {
22namespace test {
23
24// Copy here so our callers won't need to include libyuv for this constant.
25double kMetricsPerfectPSNR = kPerfectPSNR;
26
27// Used for calculating min and max values.
28static bool LessForFrameResultValue(const FrameResult& s1,
29                                    const FrameResult& s2) {
30  return s1.value < s2.value;
31}
32
33enum VideoMetricsType { kPSNR, kSSIM, kBoth };
34
35// Calculates metrics for a frame and adds statistics to the result for it.
36void CalculateFrame(VideoMetricsType video_metrics_type,
37                    const VideoFrame* ref,
38                    const VideoFrame* test,
39                    int frame_number,
40                    QualityMetricsResult* result) {
41  FrameResult frame_result = {0, 0};
42  frame_result.frame_number = frame_number;
43  switch (video_metrics_type) {
44    case kPSNR:
45      frame_result.value = I420PSNR(ref, test);
46      break;
47    case kSSIM:
48      frame_result.value = I420SSIM(ref, test);
49      break;
50    default:
51      assert(false);
52  }
53  result->frames.push_back(frame_result);
54}
55
56// Calculates average, min and max values for the supplied struct, if non-NULL.
57void CalculateStats(QualityMetricsResult* result) {
58  if (result == NULL || result->frames.size() == 0) {
59    return;
60  }
61  // Calculate average.
62  std::vector<FrameResult>::iterator iter;
63  double metrics_values_sum = 0.0;
64  for (iter = result->frames.begin(); iter != result->frames.end(); ++iter) {
65    metrics_values_sum += iter->value;
66  }
67  result->average = metrics_values_sum / result->frames.size();
68
69  // Calculate min/max statistics.
70  iter = std::min_element(result->frames.begin(), result->frames.end(),
71                     LessForFrameResultValue);
72  result->min = iter->value;
73  result->min_frame_number = iter->frame_number;
74  iter = std::max_element(result->frames.begin(), result->frames.end(),
75                     LessForFrameResultValue);
76  result->max = iter->value;
77  result->max_frame_number = iter->frame_number;
78}
79
80// Single method that handles all combinations of video metrics calculation, to
81// minimize code duplication. Either psnr_result or ssim_result may be NULL,
82// depending on which VideoMetricsType is targeted.
83int CalculateMetrics(VideoMetricsType video_metrics_type,
84                     const char* ref_filename,
85                     const char* test_filename,
86                     int width,
87                     int height,
88                     QualityMetricsResult* psnr_result,
89                     QualityMetricsResult* ssim_result) {
90  assert(ref_filename != NULL);
91  assert(test_filename != NULL);
92  assert(width > 0);
93  assert(height > 0);
94
95  FILE* ref_fp = fopen(ref_filename, "rb");
96  if (ref_fp == NULL) {
97    // Cannot open reference file.
98    fprintf(stderr, "Cannot open file %s\n", ref_filename);
99    return -1;
100  }
101  FILE* test_fp = fopen(test_filename, "rb");
102  if (test_fp == NULL) {
103    // Cannot open test file.
104    fprintf(stderr, "Cannot open file %s\n", test_filename);
105    fclose(ref_fp);
106    return -2;
107  }
108  int frame_number = 0;
109
110  // Read reference and test frames.
111  const size_t frame_length = 3 * width * height >> 1;
112  VideoFrame ref_frame;
113  VideoFrame test_frame;
114  rtc::scoped_ptr<uint8_t[]> ref_buffer(new uint8_t[frame_length]);
115  rtc::scoped_ptr<uint8_t[]> test_buffer(new uint8_t[frame_length]);
116
117  // Set decoded image parameters.
118  int half_width = (width + 1) / 2;
119  ref_frame.CreateEmptyFrame(width, height, width, half_width, half_width);
120  test_frame.CreateEmptyFrame(width, height, width, half_width, half_width);
121
122  size_t ref_bytes = fread(ref_buffer.get(), 1, frame_length, ref_fp);
123  size_t test_bytes = fread(test_buffer.get(), 1, frame_length, test_fp);
124  while (ref_bytes == frame_length && test_bytes == frame_length) {
125    // Converting from buffer to plane representation.
126    ConvertToI420(kI420, ref_buffer.get(), 0, 0, width, height, 0,
127                  kVideoRotation_0, &ref_frame);
128    ConvertToI420(kI420, test_buffer.get(), 0, 0, width, height, 0,
129                  kVideoRotation_0, &test_frame);
130    switch (video_metrics_type) {
131      case kPSNR:
132        CalculateFrame(kPSNR, &ref_frame, &test_frame, frame_number,
133                       psnr_result);
134        break;
135      case kSSIM:
136        CalculateFrame(kSSIM, &ref_frame, &test_frame, frame_number,
137                       ssim_result);
138        break;
139      case kBoth:
140        CalculateFrame(kPSNR, &ref_frame, &test_frame, frame_number,
141                       psnr_result);
142        CalculateFrame(kSSIM, &ref_frame, &test_frame, frame_number,
143                       ssim_result);
144        break;
145    }
146    frame_number++;
147    ref_bytes = fread(ref_buffer.get(), 1, frame_length, ref_fp);
148    test_bytes = fread(test_buffer.get(), 1, frame_length, test_fp);
149  }
150  int return_code = 0;
151  if (frame_number == 0) {
152    fprintf(stderr, "Tried to measure video metrics from empty files "
153            "(reference file: %s  test file: %s)\n", ref_filename,
154            test_filename);
155    return_code = -3;
156  } else {
157    CalculateStats(psnr_result);
158    CalculateStats(ssim_result);
159  }
160  fclose(ref_fp);
161  fclose(test_fp);
162  return return_code;
163}
164
165int I420MetricsFromFiles(const char* ref_filename,
166                         const char* test_filename,
167                         int width,
168                         int height,
169                         QualityMetricsResult* psnr_result,
170                         QualityMetricsResult* ssim_result) {
171  assert(psnr_result != NULL);
172  assert(ssim_result != NULL);
173  return CalculateMetrics(kBoth, ref_filename, test_filename, width, height,
174                          psnr_result, ssim_result);
175}
176
177int I420PSNRFromFiles(const char* ref_filename,
178                      const char* test_filename,
179                      int width,
180                      int height,
181                      QualityMetricsResult* result) {
182  assert(result != NULL);
183  return CalculateMetrics(kPSNR, ref_filename, test_filename, width, height,
184                          result, NULL);
185}
186
187int I420SSIMFromFiles(const char* ref_filename,
188                      const char* test_filename,
189                      int width,
190                      int height,
191                      QualityMetricsResult* result) {
192  assert(result != NULL);
193  return CalculateMetrics(kSSIM, ref_filename, test_filename, width, height,
194                          NULL, result);
195}
196
197}  // namespace test
198}  // namespace webrtc
199