blob: 6a63e1a1ad35530b13b3ba1f0da44ade934c93a0 [file] [log] [blame]
mikhal@webrtc.org552f1732011-08-26 17:38:09 +00001/*
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
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +000011#include "testsupport/metrics/video_metrics.h"
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +000012
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000013#include <algorithm> // min_element, max_element
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000014#include <cmath>
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000015#include <fstream>
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000016
17// Calculates PSNR from MSE
18static inline double CalcPsnr(double mse) {
19 // Formula: PSNR = 10 log (255^2 / MSE) = 20 log(255) - 10 log(MSE)
20 return 20.0 * std::log10(255.0) - 10.0 * std::log10(mse);
21}
22
23// Used for calculating min and max values
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +000024static bool LessForFrameResultValue (const FrameResult& s1,
25 const FrameResult& s2) {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000026 return s1.value < s2.value;
27}
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000028
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +000029int
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000030PsnrFromFiles(const WebRtc_Word8 *refFileName, const WebRtc_Word8 *testFileName,
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000031 WebRtc_Word32 width, WebRtc_Word32 height, QualityMetricsResult *result)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000032{
33 FILE *refFp = fopen(refFileName, "rb");
34 if (refFp == NULL )
35 {
36 // cannot open reference file
37 fprintf(stderr, "Cannot open file %s\n", refFileName);
38 return -1;
39 }
40
41 FILE *testFp = fopen(testFileName, "rb");
42 if (testFp == NULL )
43 {
44 // cannot open test file
45 fprintf(stderr, "Cannot open file %s\n", testFileName);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +000046 fclose(refFp);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000047 return -2;
48 }
49
50 double mse = 0.0;
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000051 double mseSum = 0.0;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000052 WebRtc_Word32 frames = 0;
53
54 // Allocating size for one I420 frame.
55 WebRtc_Word32 frameBytes = 3 * width * height >> 1;
56 WebRtc_UWord8 *ref = new WebRtc_UWord8[frameBytes];
57 WebRtc_UWord8 *test = new WebRtc_UWord8[frameBytes];
58
59 WebRtc_Word32 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
60 WebRtc_Word32 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
61
62 while (refBytes == frameBytes && testBytes == frameBytes)
63 {
64 mse = 0.0;
65
66 WebRtc_Word32 sh = 8; //boundary offset
67 for (WebRtc_Word32 k2 = sh; k2 < height - sh; k2++)
68 for (WebRtc_Word32 k = sh; k < width - sh; k++)
69 {
70 WebRtc_Word32 kk = k2 * width + k;
71 mse += (test[kk] - ref[kk]) * (test[kk] - ref[kk]);
72 }
73
74 // divide by number of pixels
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000075 mse /= (double) (width * height);
76
77 // Save statistics for this specific frame
78 FrameResult frame_result;
79 frame_result.value = CalcPsnr(mse);
80 frame_result.frame_number = frames;
81 result->frames.push_back(frame_result);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000082
83 // accumulate for total average
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000084 mseSum += mse;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000085 frames++;
86
87 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
88 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
89 }
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000090
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +000091 if (mseSum == 0)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000092 {
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000093 // The PSNR value is undefined in this case.
94 // This value effectively means that the files are equal.
95 result->average = std::numeric_limits<double>::max();
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000096 }
97 else
98 {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000099 result->average = CalcPsnr(mseSum / frames);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000100 }
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000101 int return_code = 0;
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000102 if (result->frames.size() == 0)
103 {
104 fprintf(stderr, "Tried to measure SSIM from empty files (reference "
105 "file: %s test file: %s\n", refFileName, testFileName);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000106 return_code = -3;
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000107 }
108 else
109 {
110 // Calculate min/max statistics
111 std::vector<FrameResult>::iterator element;
112 element = min_element(result->frames.begin(),
113 result->frames.end(), LessForFrameResultValue);
114 result->min = element->value;
115 result->min_frame_number = element->frame_number;
116 element = max_element(result->frames.begin(),
117 result->frames.end(), LessForFrameResultValue);
118 result->max = element->value;
119 result->max_frame_number = element->frame_number;
120 }
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000121 delete [] ref;
122 delete [] test;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000123 fclose(refFp);
124 fclose(testFp);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000125 return return_code;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000126}
127
128static double
129Similarity(WebRtc_UWord64 sum_s, WebRtc_UWord64 sum_r, WebRtc_UWord64 sum_sq_s,
130 WebRtc_UWord64 sum_sq_r, WebRtc_UWord64 sum_sxr, WebRtc_Word32 count)
131{
132 WebRtc_Word64 ssim_n, ssim_d;
133 WebRtc_Word64 c1, c2;
134 const WebRtc_Word64 cc1 = 26634; // (64^2*(.01*255)^2
135 const WebRtc_Word64 cc2 = 239708; // (64^2*(.03*255)^2
136
137 // Scale the constants by number of pixels
138 c1 = (cc1 * count * count) >> 12;
139 c2 = (cc2 * count * count) >> 12;
140
141 ssim_n = (2 * sum_s * sum_r + c1) * ((WebRtc_Word64) 2 * count * sum_sxr-
142 (WebRtc_Word64) 2 * sum_s * sum_r + c2);
143
144 ssim_d = (sum_s * sum_s + sum_r * sum_r + c1)*
145 ((WebRtc_Word64)count * sum_sq_s - (WebRtc_Word64)sum_s * sum_s +
146 (WebRtc_Word64)count * sum_sq_r - (WebRtc_Word64) sum_r * sum_r + c2);
147
148 return ssim_n * 1.0 / ssim_d;
149}
150
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000151#if !defined(WEBRTC_USE_SSE2)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000152static double
153Ssim8x8C(WebRtc_UWord8 *s, WebRtc_Word32 sp,
154 WebRtc_UWord8 *r, WebRtc_Word32 rp)
155{
156 WebRtc_UWord64 sum_s = 0;
157 WebRtc_UWord64 sum_r = 0;
158 WebRtc_UWord64 sum_sq_s = 0;
159 WebRtc_UWord64 sum_sq_r = 0;
160 WebRtc_UWord64 sum_sxr = 0;
161
162 WebRtc_Word32 i, j;
163 for (i = 0; i < 8; i++, s += sp,r += rp)
164 {
165 for (j = 0; j < 8; j++)
166 {
167 sum_s += s[j];
168 sum_r += r[j];
169 sum_sq_s += s[j] * s[j];
170 sum_sq_r += r[j] * r[j];
171 sum_sxr += s[j] * r[j];
172 }
173 }
174 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
175}
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000176#endif
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000177
178#if defined(WEBRTC_USE_SSE2)
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000179#include <emmintrin.h>
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000180#include <xmmintrin.h>
181static double
182Ssim8x8Sse2(WebRtc_UWord8 *s, WebRtc_Word32 sp,
183 WebRtc_UWord8 *r, WebRtc_Word32 rp)
184{
185 WebRtc_Word32 i;
186 const __m128i z = _mm_setzero_si128();
187 __m128i sum_s_16 = _mm_setzero_si128();
188 __m128i sum_r_16 = _mm_setzero_si128();
189 __m128i sum_sq_s_32 = _mm_setzero_si128();
190 __m128i sum_sq_r_32 = _mm_setzero_si128();
191 __m128i sum_sxr_32 = _mm_setzero_si128();
192
193 for (i = 0; i < 8; i++, s += sp,r += rp)
194 {
195 const __m128i s_8 = _mm_loadl_epi64((__m128i*)(s));
196 const __m128i r_8 = _mm_loadl_epi64((__m128i*)(r));
197
198 const __m128i s_16 = _mm_unpacklo_epi8(s_8,z);
199 const __m128i r_16 = _mm_unpacklo_epi8(r_8,z);
200
201 sum_s_16 = _mm_adds_epu16(sum_s_16, s_16);
202 sum_r_16 = _mm_adds_epu16(sum_r_16, r_16);
203 const __m128i sq_s_32 = _mm_madd_epi16(s_16, s_16);
204 sum_sq_s_32 = _mm_add_epi32(sum_sq_s_32, sq_s_32);
205 const __m128i sq_r_32 = _mm_madd_epi16(r_16, r_16);
206 sum_sq_r_32 = _mm_add_epi32(sum_sq_r_32, sq_r_32);
207 const __m128i sxr_32 = _mm_madd_epi16(s_16, r_16);
208 sum_sxr_32 = _mm_add_epi32(sum_sxr_32, sxr_32);
209 }
210
211 const __m128i sum_s_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_s_16, z),
212 _mm_unpacklo_epi16(sum_s_16, z));
213 const __m128i sum_r_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_r_16, z),
214 _mm_unpacklo_epi16(sum_r_16, z));
215
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000216 __m128i sum_s_128;
217 __m128i sum_r_128;
218 __m128i sum_sq_s_128;
219 __m128i sum_sq_r_128;
220 __m128i sum_sxr_128;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000221
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000222 _mm_store_si128 (&sum_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000223 _mm_add_epi64(_mm_unpackhi_epi32(sum_s_32, z),
224 _mm_unpacklo_epi32(sum_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000225 _mm_store_si128 (&sum_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000226 _mm_add_epi64(_mm_unpackhi_epi32(sum_r_32, z),
227 _mm_unpacklo_epi32(sum_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000228 _mm_store_si128 (&sum_sq_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000229 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_s_32, z),
230 _mm_unpacklo_epi32(sum_sq_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000231 _mm_store_si128 (&sum_sq_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000232 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_r_32, z),
233 _mm_unpacklo_epi32(sum_sq_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000234 _mm_store_si128 (&sum_sxr_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000235 _mm_add_epi64(_mm_unpackhi_epi32(sum_sxr_32, z),
236 _mm_unpacklo_epi32(sum_sxr_32, z)));
237
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000238 const WebRtc_UWord64 *sum_s_64 =
239 reinterpret_cast<WebRtc_UWord64*>(&sum_s_128);
240 const WebRtc_UWord64 *sum_r_64 =
241 reinterpret_cast<WebRtc_UWord64*>(&sum_r_128);
242 const WebRtc_UWord64 *sum_sq_s_64 =
243 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_s_128);
244 const WebRtc_UWord64 *sum_sq_r_64 =
245 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_r_128);
246 const WebRtc_UWord64 *sum_sxr_64 =
247 reinterpret_cast<WebRtc_UWord64*>(&sum_sxr_128);
248
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000249 const WebRtc_UWord64 sum_s = sum_s_64[0] + sum_s_64[1];
250 const WebRtc_UWord64 sum_r = sum_r_64[0] + sum_r_64[1];
251 const WebRtc_UWord64 sum_sq_s = sum_sq_s_64[0] + sum_sq_s_64[1];
252 const WebRtc_UWord64 sum_sq_r = sum_sq_r_64[0] + sum_sq_r_64[1];
253 const WebRtc_UWord64 sum_sxr = sum_sxr_64[0] + sum_sxr_64[1];
254
255 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
256}
257#endif
258
259double
260SsimFrame(WebRtc_UWord8 *img1, WebRtc_UWord8 *img2, WebRtc_Word32 stride_img1,
261 WebRtc_Word32 stride_img2, WebRtc_Word32 width, WebRtc_Word32 height)
262{
263 WebRtc_Word32 i,j;
264 WebRtc_UWord32 samples = 0;
265 double ssim_total = 0;
266 double (*ssim_8x8)(WebRtc_UWord8*, WebRtc_Word32,
267 WebRtc_UWord8*, WebRtc_Word32 rp);
268
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000269#if defined(WEBRTC_USE_SSE2)
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000270 ssim_8x8 = Ssim8x8Sse2;
271#else
272 ssim_8x8 = Ssim8x8C;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000273#endif
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000274
275 // Sample point start with each 4x4 location
276 for (i = 0; i < height - 8; i += 4, img1 += stride_img1 * 4,
277 img2 += stride_img2 * 4)
278 {
279 for (j = 0; j < width - 8; j += 4 )
280 {
281 double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
282 ssim_total += v;
283 samples++;
284 }
285 }
286 ssim_total /= samples;
287 return ssim_total;
288}
289
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000290int
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000291SsimFromFiles(const WebRtc_Word8 *refFileName, const WebRtc_Word8 *testFileName,
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000292 WebRtc_Word32 width, WebRtc_Word32 height, QualityMetricsResult *result)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000293{
294 FILE *refFp = fopen(refFileName, "rb");
295 if (refFp == NULL)
296 {
297 // cannot open reference file
298 fprintf(stderr, "Cannot open file %s\n", refFileName);
299 return -1;
300 }
301
302 FILE *testFp = fopen(testFileName, "rb");
303 if (testFp == NULL)
304 {
305 // cannot open test file
306 fprintf(stderr, "Cannot open file %s\n", testFileName);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000307 fclose(refFp);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000308 return -2;
309 }
310
311 WebRtc_Word32 frames = 0;
312
313 // Bytes in one frame I420
314 const WebRtc_Word32 frameBytes = 3 * width * height / 2;
315 WebRtc_UWord8 *ref = new WebRtc_UWord8[frameBytes];
316 WebRtc_UWord8 *test = new WebRtc_UWord8[frameBytes];
317
318 WebRtc_Word32 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
319 WebRtc_Word32 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
320
321 double ssimScene = 0.0; //average SSIM for sequence
322
323 while (refBytes == frameBytes && testBytes == frameBytes )
324 {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000325 double ssimFrameValue = SsimFrame(ref, test, width, width, width, height);
326 // Save statistics for this specific frame
327 FrameResult frame_result;
328 frame_result.value = ssimFrameValue;
329 frame_result.frame_number = frames;
330 result->frames.push_back(frame_result);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000331
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000332 ssimScene += ssimFrameValue;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000333 frames++;
334
335 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
336 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
337 }
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000338 int return_code = 0;
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000339 if (result->frames.size() == 0)
340 {
341 fprintf(stderr, "Tried to measure SSIM from empty files (reference "
342 "file: %s test file: %s\n", refFileName, testFileName);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000343 return_code = -3;
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000344 }
345 else
346 {
347 // SSIM: normalize/average for sequence
348 ssimScene = ssimScene / frames;
349 result->average = ssimScene;
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000350
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000351 // Calculate min/max statistics
352 std::vector<FrameResult>::iterator element;
353 element = min_element(result->frames.begin(),
354 result->frames.end(), LessForFrameResultValue);
355 result->min = element->value;
356 result->min_frame_number = element->frame_number;
357 element = max_element(result->frames.begin(),
358 result->frames.end(), LessForFrameResultValue);
359 result->max = element->value;
360 result->max_frame_number = element->frame_number;
361 }
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000362 delete [] ref;
363 delete [] test;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000364 fclose(refFp);
365 fclose(testFp);
kjellander@webrtc.org5b97b122011-12-08 07:42:18 +0000366 return return_code;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000367}