blob: 2aa72d407772ca38dc76a17cde757211a8ef9327 [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.orgf0a84642011-09-12 13:45:39 +000011#include "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);
46 return -2;
47 }
48
49 double mse = 0.0;
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000050 double mseSum = 0.0;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000051 WebRtc_Word32 frames = 0;
52
53 // Allocating size for one I420 frame.
54 WebRtc_Word32 frameBytes = 3 * width * height >> 1;
55 WebRtc_UWord8 *ref = new WebRtc_UWord8[frameBytes];
56 WebRtc_UWord8 *test = new WebRtc_UWord8[frameBytes];
57
58 WebRtc_Word32 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
59 WebRtc_Word32 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
60
61 while (refBytes == frameBytes && testBytes == frameBytes)
62 {
63 mse = 0.0;
64
65 WebRtc_Word32 sh = 8; //boundary offset
66 for (WebRtc_Word32 k2 = sh; k2 < height - sh; k2++)
67 for (WebRtc_Word32 k = sh; k < width - sh; k++)
68 {
69 WebRtc_Word32 kk = k2 * width + k;
70 mse += (test[kk] - ref[kk]) * (test[kk] - ref[kk]);
71 }
72
73 // divide by number of pixels
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000074 mse /= (double) (width * height);
75
76 // Save statistics for this specific frame
77 FrameResult frame_result;
78 frame_result.value = CalcPsnr(mse);
79 frame_result.frame_number = frames;
80 result->frames.push_back(frame_result);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000081
82 // accumulate for total average
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000083 mseSum += mse;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000084 frames++;
85
86 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
87 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
88 }
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000089
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +000090 if (mseSum == 0)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000091 {
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000092 // The PSNR value is undefined in this case.
93 // This value effectively means that the files are equal.
94 result->average = std::numeric_limits<double>::max();
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000095 }
96 else
97 {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000098 result->average = CalcPsnr(mseSum / frames);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000099 }
100
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000101 if (result->frames.size() == 0)
102 {
103 fprintf(stderr, "Tried to measure SSIM from empty files (reference "
104 "file: %s test file: %s\n", refFileName, testFileName);
105 return -3;
106 }
107 else
108 {
109 // Calculate min/max statistics
110 std::vector<FrameResult>::iterator element;
111 element = min_element(result->frames.begin(),
112 result->frames.end(), LessForFrameResultValue);
113 result->min = element->value;
114 result->min_frame_number = element->frame_number;
115 element = max_element(result->frames.begin(),
116 result->frames.end(), LessForFrameResultValue);
117 result->max = element->value;
118 result->max_frame_number = element->frame_number;
119 }
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000120 delete [] ref;
121 delete [] test;
122
123 fclose(refFp);
124 fclose(testFp);
125
126 return 0;
127}
128
129static double
130Similarity(WebRtc_UWord64 sum_s, WebRtc_UWord64 sum_r, WebRtc_UWord64 sum_sq_s,
131 WebRtc_UWord64 sum_sq_r, WebRtc_UWord64 sum_sxr, WebRtc_Word32 count)
132{
133 WebRtc_Word64 ssim_n, ssim_d;
134 WebRtc_Word64 c1, c2;
135 const WebRtc_Word64 cc1 = 26634; // (64^2*(.01*255)^2
136 const WebRtc_Word64 cc2 = 239708; // (64^2*(.03*255)^2
137
138 // Scale the constants by number of pixels
139 c1 = (cc1 * count * count) >> 12;
140 c2 = (cc2 * count * count) >> 12;
141
142 ssim_n = (2 * sum_s * sum_r + c1) * ((WebRtc_Word64) 2 * count * sum_sxr-
143 (WebRtc_Word64) 2 * sum_s * sum_r + c2);
144
145 ssim_d = (sum_s * sum_s + sum_r * sum_r + c1)*
146 ((WebRtc_Word64)count * sum_sq_s - (WebRtc_Word64)sum_s * sum_s +
147 (WebRtc_Word64)count * sum_sq_r - (WebRtc_Word64) sum_r * sum_r + c2);
148
149 return ssim_n * 1.0 / ssim_d;
150}
151
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000152#if !defined(WEBRTC_USE_SSE2)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000153static double
154Ssim8x8C(WebRtc_UWord8 *s, WebRtc_Word32 sp,
155 WebRtc_UWord8 *r, WebRtc_Word32 rp)
156{
157 WebRtc_UWord64 sum_s = 0;
158 WebRtc_UWord64 sum_r = 0;
159 WebRtc_UWord64 sum_sq_s = 0;
160 WebRtc_UWord64 sum_sq_r = 0;
161 WebRtc_UWord64 sum_sxr = 0;
162
163 WebRtc_Word32 i, j;
164 for (i = 0; i < 8; i++, s += sp,r += rp)
165 {
166 for (j = 0; j < 8; j++)
167 {
168 sum_s += s[j];
169 sum_r += r[j];
170 sum_sq_s += s[j] * s[j];
171 sum_sq_r += r[j] * r[j];
172 sum_sxr += s[j] * r[j];
173 }
174 }
175 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
176}
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000177#endif
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000178
179#if defined(WEBRTC_USE_SSE2)
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000180#include <emmintrin.h>
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000181#include <xmmintrin.h>
182static double
183Ssim8x8Sse2(WebRtc_UWord8 *s, WebRtc_Word32 sp,
184 WebRtc_UWord8 *r, WebRtc_Word32 rp)
185{
186 WebRtc_Word32 i;
187 const __m128i z = _mm_setzero_si128();
188 __m128i sum_s_16 = _mm_setzero_si128();
189 __m128i sum_r_16 = _mm_setzero_si128();
190 __m128i sum_sq_s_32 = _mm_setzero_si128();
191 __m128i sum_sq_r_32 = _mm_setzero_si128();
192 __m128i sum_sxr_32 = _mm_setzero_si128();
193
194 for (i = 0; i < 8; i++, s += sp,r += rp)
195 {
196 const __m128i s_8 = _mm_loadl_epi64((__m128i*)(s));
197 const __m128i r_8 = _mm_loadl_epi64((__m128i*)(r));
198
199 const __m128i s_16 = _mm_unpacklo_epi8(s_8,z);
200 const __m128i r_16 = _mm_unpacklo_epi8(r_8,z);
201
202 sum_s_16 = _mm_adds_epu16(sum_s_16, s_16);
203 sum_r_16 = _mm_adds_epu16(sum_r_16, r_16);
204 const __m128i sq_s_32 = _mm_madd_epi16(s_16, s_16);
205 sum_sq_s_32 = _mm_add_epi32(sum_sq_s_32, sq_s_32);
206 const __m128i sq_r_32 = _mm_madd_epi16(r_16, r_16);
207 sum_sq_r_32 = _mm_add_epi32(sum_sq_r_32, sq_r_32);
208 const __m128i sxr_32 = _mm_madd_epi16(s_16, r_16);
209 sum_sxr_32 = _mm_add_epi32(sum_sxr_32, sxr_32);
210 }
211
212 const __m128i sum_s_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_s_16, z),
213 _mm_unpacklo_epi16(sum_s_16, z));
214 const __m128i sum_r_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_r_16, z),
215 _mm_unpacklo_epi16(sum_r_16, z));
216
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000217 __m128i sum_s_128;
218 __m128i sum_r_128;
219 __m128i sum_sq_s_128;
220 __m128i sum_sq_r_128;
221 __m128i sum_sxr_128;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000222
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000223 _mm_store_si128 (&sum_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000224 _mm_add_epi64(_mm_unpackhi_epi32(sum_s_32, z),
225 _mm_unpacklo_epi32(sum_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000226 _mm_store_si128 (&sum_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000227 _mm_add_epi64(_mm_unpackhi_epi32(sum_r_32, z),
228 _mm_unpacklo_epi32(sum_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000229 _mm_store_si128 (&sum_sq_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000230 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_s_32, z),
231 _mm_unpacklo_epi32(sum_sq_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000232 _mm_store_si128 (&sum_sq_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000233 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_r_32, z),
234 _mm_unpacklo_epi32(sum_sq_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000235 _mm_store_si128 (&sum_sxr_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000236 _mm_add_epi64(_mm_unpackhi_epi32(sum_sxr_32, z),
237 _mm_unpacklo_epi32(sum_sxr_32, z)));
238
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000239 const WebRtc_UWord64 *sum_s_64 =
240 reinterpret_cast<WebRtc_UWord64*>(&sum_s_128);
241 const WebRtc_UWord64 *sum_r_64 =
242 reinterpret_cast<WebRtc_UWord64*>(&sum_r_128);
243 const WebRtc_UWord64 *sum_sq_s_64 =
244 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_s_128);
245 const WebRtc_UWord64 *sum_sq_r_64 =
246 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_r_128);
247 const WebRtc_UWord64 *sum_sxr_64 =
248 reinterpret_cast<WebRtc_UWord64*>(&sum_sxr_128);
249
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000250 const WebRtc_UWord64 sum_s = sum_s_64[0] + sum_s_64[1];
251 const WebRtc_UWord64 sum_r = sum_r_64[0] + sum_r_64[1];
252 const WebRtc_UWord64 sum_sq_s = sum_sq_s_64[0] + sum_sq_s_64[1];
253 const WebRtc_UWord64 sum_sq_r = sum_sq_r_64[0] + sum_sq_r_64[1];
254 const WebRtc_UWord64 sum_sxr = sum_sxr_64[0] + sum_sxr_64[1];
255
256 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
257}
258#endif
259
260double
261SsimFrame(WebRtc_UWord8 *img1, WebRtc_UWord8 *img2, WebRtc_Word32 stride_img1,
262 WebRtc_Word32 stride_img2, WebRtc_Word32 width, WebRtc_Word32 height)
263{
264 WebRtc_Word32 i,j;
265 WebRtc_UWord32 samples = 0;
266 double ssim_total = 0;
267 double (*ssim_8x8)(WebRtc_UWord8*, WebRtc_Word32,
268 WebRtc_UWord8*, WebRtc_Word32 rp);
269
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000270#if defined(WEBRTC_USE_SSE2)
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000271 ssim_8x8 = Ssim8x8Sse2;
272#else
273 ssim_8x8 = Ssim8x8C;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000274#endif
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000275
276 // Sample point start with each 4x4 location
277 for (i = 0; i < height - 8; i += 4, img1 += stride_img1 * 4,
278 img2 += stride_img2 * 4)
279 {
280 for (j = 0; j < width - 8; j += 4 )
281 {
282 double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
283 ssim_total += v;
284 samples++;
285 }
286 }
287 ssim_total /= samples;
288 return ssim_total;
289}
290
kjellander@webrtc.org82d91ae2011-12-05 13:03:38 +0000291int
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000292SsimFromFiles(const WebRtc_Word8 *refFileName, const WebRtc_Word8 *testFileName,
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000293 WebRtc_Word32 width, WebRtc_Word32 height, QualityMetricsResult *result)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000294{
295 FILE *refFp = fopen(refFileName, "rb");
296 if (refFp == NULL)
297 {
298 // cannot open reference file
299 fprintf(stderr, "Cannot open file %s\n", refFileName);
300 return -1;
301 }
302
303 FILE *testFp = fopen(testFileName, "rb");
304 if (testFp == NULL)
305 {
306 // cannot open test file
307 fprintf(stderr, "Cannot open file %s\n", testFileName);
308 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 }
338
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);
343 return -3;
344 }
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;
364
365 fclose(refFp);
366 fclose(testFp);
367
368 return 0;
369}