Home | History | Annotate | Download | only in metrics
      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/interface/i420_video_frame.h"
     19 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
     20 
     21 namespace webrtc {
     22 namespace test {
     23 
     24 // Copy here so our callers won't need to include libyuv for this constant.
     25 double kMetricsPerfectPSNR = kPerfectPSNR;
     26 
     27 // Used for calculating min and max values.
     28 static bool LessForFrameResultValue(const FrameResult& s1,
     29                                     const FrameResult& s2) {
     30   return s1.value < s2.value;
     31 }
     32 
     33 enum VideoMetricsType { kPSNR, kSSIM, kBoth };
     34 
     35 // Calculates metrics for a frame and adds statistics to the result for it.
     36 void CalculateFrame(VideoMetricsType video_metrics_type,
     37                     const I420VideoFrame* ref,
     38                     const I420VideoFrame* 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.
     57 void 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.
     83 int 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   I420VideoFrame ref_frame;
    113   I420VideoFrame test_frame;
    114   scoped_ptr<uint8_t[]> ref_buffer(new uint8_t[frame_length]);
    115   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                   kRotateNone, &ref_frame);
    128     ConvertToI420(kI420, test_buffer.get(), 0, 0, width, height, 0,
    129                   kRotateNone, &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 
    165 int 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 
    177 int 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 
    187 int 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