blob: 568c43ae3a49c49e06d55dd1271a4a18c4899a74 [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"
12#include <algorithm> // min_element, max_element
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000013#include <cmath>
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000014#include <fstream>
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000015#include "system_wrappers/interface/cpu_features_wrapper.h"
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
24static bool lessForFrameResultValue (const FrameResult& s1, const FrameResult& s2) {
25 return s1.value < s2.value;
26}
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000027
28WebRtc_Word32
29PsnrFromFiles(const WebRtc_Word8 *refFileName, const WebRtc_Word8 *testFileName,
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000030 WebRtc_Word32 width, WebRtc_Word32 height, QualityMetricsResult *result)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000031{
32 FILE *refFp = fopen(refFileName, "rb");
33 if (refFp == NULL )
34 {
35 // cannot open reference file
36 fprintf(stderr, "Cannot open file %s\n", refFileName);
37 return -1;
38 }
39
40 FILE *testFp = fopen(testFileName, "rb");
41 if (testFp == NULL )
42 {
43 // cannot open test file
44 fprintf(stderr, "Cannot open file %s\n", testFileName);
45 return -2;
46 }
47
48 double mse = 0.0;
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000049 double mseSum = 0.0;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000050 WebRtc_Word32 frames = 0;
51
52 // Allocating size for one I420 frame.
53 WebRtc_Word32 frameBytes = 3 * width * height >> 1;
54 WebRtc_UWord8 *ref = new WebRtc_UWord8[frameBytes];
55 WebRtc_UWord8 *test = new WebRtc_UWord8[frameBytes];
56
57 WebRtc_Word32 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
58 WebRtc_Word32 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
59
60 while (refBytes == frameBytes && testBytes == frameBytes)
61 {
62 mse = 0.0;
63
64 WebRtc_Word32 sh = 8; //boundary offset
65 for (WebRtc_Word32 k2 = sh; k2 < height - sh; k2++)
66 for (WebRtc_Word32 k = sh; k < width - sh; k++)
67 {
68 WebRtc_Word32 kk = k2 * width + k;
69 mse += (test[kk] - ref[kk]) * (test[kk] - ref[kk]);
70 }
71
72 // divide by number of pixels
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000073 mse /= (double) (width * height);
74
75 // Save statistics for this specific frame
76 FrameResult frame_result;
77 frame_result.value = CalcPsnr(mse);
78 frame_result.frame_number = frames;
79 result->frames.push_back(frame_result);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000080
81 // accumulate for total average
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000082 mseSum += mse;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000083 frames++;
84
85 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
86 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
87 }
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000088
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000089 if (mse == 0)
90 {
phoglund@webrtc.org1144ba22011-11-11 09:01:03 +000091 // The PSNR value is undefined in this case.
92 // This value effectively means that the files are equal.
93 result->average = std::numeric_limits<double>::max();
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000094 }
95 else
96 {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +000097 result->average = CalcPsnr(mseSum / frames);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +000098 }
99
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000100 // Calculate min/max statistics
101 std::vector<FrameResult>::iterator element;
102 element = min_element(result->frames.begin(),
103 result->frames.end(), lessForFrameResultValue);
104 result->min = element->value;
105 result->min_frame_number = element->frame_number;
106 element = max_element(result->frames.begin(),
107 result->frames.end(), lessForFrameResultValue);
108 result->max = element->value;
109 result->max_frame_number = element->frame_number;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000110
111 delete [] ref;
112 delete [] test;
113
114 fclose(refFp);
115 fclose(testFp);
116
117 return 0;
118}
119
120static double
121Similarity(WebRtc_UWord64 sum_s, WebRtc_UWord64 sum_r, WebRtc_UWord64 sum_sq_s,
122 WebRtc_UWord64 sum_sq_r, WebRtc_UWord64 sum_sxr, WebRtc_Word32 count)
123{
124 WebRtc_Word64 ssim_n, ssim_d;
125 WebRtc_Word64 c1, c2;
126 const WebRtc_Word64 cc1 = 26634; // (64^2*(.01*255)^2
127 const WebRtc_Word64 cc2 = 239708; // (64^2*(.03*255)^2
128
129 // Scale the constants by number of pixels
130 c1 = (cc1 * count * count) >> 12;
131 c2 = (cc2 * count * count) >> 12;
132
133 ssim_n = (2 * sum_s * sum_r + c1) * ((WebRtc_Word64) 2 * count * sum_sxr-
134 (WebRtc_Word64) 2 * sum_s * sum_r + c2);
135
136 ssim_d = (sum_s * sum_s + sum_r * sum_r + c1)*
137 ((WebRtc_Word64)count * sum_sq_s - (WebRtc_Word64)sum_s * sum_s +
138 (WebRtc_Word64)count * sum_sq_r - (WebRtc_Word64) sum_r * sum_r + c2);
139
140 return ssim_n * 1.0 / ssim_d;
141}
142
143static double
144Ssim8x8C(WebRtc_UWord8 *s, WebRtc_Word32 sp,
145 WebRtc_UWord8 *r, WebRtc_Word32 rp)
146{
147 WebRtc_UWord64 sum_s = 0;
148 WebRtc_UWord64 sum_r = 0;
149 WebRtc_UWord64 sum_sq_s = 0;
150 WebRtc_UWord64 sum_sq_r = 0;
151 WebRtc_UWord64 sum_sxr = 0;
152
153 WebRtc_Word32 i, j;
154 for (i = 0; i < 8; i++, s += sp,r += rp)
155 {
156 for (j = 0; j < 8; j++)
157 {
158 sum_s += s[j];
159 sum_r += r[j];
160 sum_sq_s += s[j] * s[j];
161 sum_sq_r += r[j] * r[j];
162 sum_sxr += s[j] * r[j];
163 }
164 }
165 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
166}
167
168#if defined(WEBRTC_USE_SSE2)
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000169#include <emmintrin.h>
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000170#include <xmmintrin.h>
171static double
172Ssim8x8Sse2(WebRtc_UWord8 *s, WebRtc_Word32 sp,
173 WebRtc_UWord8 *r, WebRtc_Word32 rp)
174{
175 WebRtc_Word32 i;
176 const __m128i z = _mm_setzero_si128();
177 __m128i sum_s_16 = _mm_setzero_si128();
178 __m128i sum_r_16 = _mm_setzero_si128();
179 __m128i sum_sq_s_32 = _mm_setzero_si128();
180 __m128i sum_sq_r_32 = _mm_setzero_si128();
181 __m128i sum_sxr_32 = _mm_setzero_si128();
182
183 for (i = 0; i < 8; i++, s += sp,r += rp)
184 {
185 const __m128i s_8 = _mm_loadl_epi64((__m128i*)(s));
186 const __m128i r_8 = _mm_loadl_epi64((__m128i*)(r));
187
188 const __m128i s_16 = _mm_unpacklo_epi8(s_8,z);
189 const __m128i r_16 = _mm_unpacklo_epi8(r_8,z);
190
191 sum_s_16 = _mm_adds_epu16(sum_s_16, s_16);
192 sum_r_16 = _mm_adds_epu16(sum_r_16, r_16);
193 const __m128i sq_s_32 = _mm_madd_epi16(s_16, s_16);
194 sum_sq_s_32 = _mm_add_epi32(sum_sq_s_32, sq_s_32);
195 const __m128i sq_r_32 = _mm_madd_epi16(r_16, r_16);
196 sum_sq_r_32 = _mm_add_epi32(sum_sq_r_32, sq_r_32);
197 const __m128i sxr_32 = _mm_madd_epi16(s_16, r_16);
198 sum_sxr_32 = _mm_add_epi32(sum_sxr_32, sxr_32);
199 }
200
201 const __m128i sum_s_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_s_16, z),
202 _mm_unpacklo_epi16(sum_s_16, z));
203 const __m128i sum_r_32 = _mm_add_epi32(_mm_unpackhi_epi16(sum_r_16, z),
204 _mm_unpacklo_epi16(sum_r_16, z));
205
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000206 __m128i sum_s_128;
207 __m128i sum_r_128;
208 __m128i sum_sq_s_128;
209 __m128i sum_sq_r_128;
210 __m128i sum_sxr_128;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000211
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000212 _mm_store_si128 (&sum_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000213 _mm_add_epi64(_mm_unpackhi_epi32(sum_s_32, z),
214 _mm_unpacklo_epi32(sum_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000215 _mm_store_si128 (&sum_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000216 _mm_add_epi64(_mm_unpackhi_epi32(sum_r_32, z),
217 _mm_unpacklo_epi32(sum_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000218 _mm_store_si128 (&sum_sq_s_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000219 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_s_32, z),
220 _mm_unpacklo_epi32(sum_sq_s_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000221 _mm_store_si128 (&sum_sq_r_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000222 _mm_add_epi64(_mm_unpackhi_epi32(sum_sq_r_32, z),
223 _mm_unpacklo_epi32(sum_sq_r_32, z)));
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000224 _mm_store_si128 (&sum_sxr_128,
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000225 _mm_add_epi64(_mm_unpackhi_epi32(sum_sxr_32, z),
226 _mm_unpacklo_epi32(sum_sxr_32, z)));
227
frkoenig@google.comfc9bcef2011-10-26 00:07:32 +0000228 const WebRtc_UWord64 *sum_s_64 =
229 reinterpret_cast<WebRtc_UWord64*>(&sum_s_128);
230 const WebRtc_UWord64 *sum_r_64 =
231 reinterpret_cast<WebRtc_UWord64*>(&sum_r_128);
232 const WebRtc_UWord64 *sum_sq_s_64 =
233 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_s_128);
234 const WebRtc_UWord64 *sum_sq_r_64 =
235 reinterpret_cast<WebRtc_UWord64*>(&sum_sq_r_128);
236 const WebRtc_UWord64 *sum_sxr_64 =
237 reinterpret_cast<WebRtc_UWord64*>(&sum_sxr_128);
238
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000239 const WebRtc_UWord64 sum_s = sum_s_64[0] + sum_s_64[1];
240 const WebRtc_UWord64 sum_r = sum_r_64[0] + sum_r_64[1];
241 const WebRtc_UWord64 sum_sq_s = sum_sq_s_64[0] + sum_sq_s_64[1];
242 const WebRtc_UWord64 sum_sq_r = sum_sq_r_64[0] + sum_sq_r_64[1];
243 const WebRtc_UWord64 sum_sxr = sum_sxr_64[0] + sum_sxr_64[1];
244
245 return Similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
246}
247#endif
248
249double
250SsimFrame(WebRtc_UWord8 *img1, WebRtc_UWord8 *img2, WebRtc_Word32 stride_img1,
251 WebRtc_Word32 stride_img2, WebRtc_Word32 width, WebRtc_Word32 height)
252{
253 WebRtc_Word32 i,j;
254 WebRtc_UWord32 samples = 0;
255 double ssim_total = 0;
256 double (*ssim_8x8)(WebRtc_UWord8*, WebRtc_Word32,
257 WebRtc_UWord8*, WebRtc_Word32 rp);
258
259 ssim_8x8 = Ssim8x8C;
260 if (WebRtc_GetCPUInfo(kSSE2))
261 {
262#if defined(WEBRTC_USE_SSE2)
263 ssim_8x8 = Ssim8x8Sse2;
264#endif
265 }
266
267 // Sample point start with each 4x4 location
268 for (i = 0; i < height - 8; i += 4, img1 += stride_img1 * 4,
269 img2 += stride_img2 * 4)
270 {
271 for (j = 0; j < width - 8; j += 4 )
272 {
273 double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
274 ssim_total += v;
275 samples++;
276 }
277 }
278 ssim_total /= samples;
279 return ssim_total;
280}
281
282WebRtc_Word32
283SsimFromFiles(const WebRtc_Word8 *refFileName, const WebRtc_Word8 *testFileName,
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000284 WebRtc_Word32 width, WebRtc_Word32 height, QualityMetricsResult *result)
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000285{
286 FILE *refFp = fopen(refFileName, "rb");
287 if (refFp == NULL)
288 {
289 // cannot open reference file
290 fprintf(stderr, "Cannot open file %s\n", refFileName);
291 return -1;
292 }
293
294 FILE *testFp = fopen(testFileName, "rb");
295 if (testFp == NULL)
296 {
297 // cannot open test file
298 fprintf(stderr, "Cannot open file %s\n", testFileName);
299 return -2;
300 }
301
302 WebRtc_Word32 frames = 0;
303
304 // Bytes in one frame I420
305 const WebRtc_Word32 frameBytes = 3 * width * height / 2;
306 WebRtc_UWord8 *ref = new WebRtc_UWord8[frameBytes];
307 WebRtc_UWord8 *test = new WebRtc_UWord8[frameBytes];
308
309 WebRtc_Word32 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
310 WebRtc_Word32 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
311
312 double ssimScene = 0.0; //average SSIM for sequence
313
314 while (refBytes == frameBytes && testBytes == frameBytes )
315 {
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000316 double ssimFrameValue = SsimFrame(ref, test, width, width, width, height);
317 // Save statistics for this specific frame
318 FrameResult frame_result;
319 frame_result.value = ssimFrameValue;
320 frame_result.frame_number = frames;
321 result->frames.push_back(frame_result);
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000322
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000323 ssimScene += ssimFrameValue;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000324 frames++;
325
326 refBytes = (WebRtc_Word32) fread(ref, 1, frameBytes, refFp);
327 testBytes = (WebRtc_Word32) fread(test, 1, frameBytes, testFp);
328 }
329
330 // SSIM: normalize/average for sequence
331 ssimScene = ssimScene / frames;
kjellander@webrtc.orgf0a84642011-09-12 13:45:39 +0000332 result->average = ssimScene;
333
334 // Calculate min/max statistics
335 std::vector<FrameResult>::iterator element;
336 element = min_element(result->frames.begin(),
337 result->frames.end(), lessForFrameResultValue);
338 result->min = element->value;
339 result->min_frame_number = element->frame_number;
340 element = max_element(result->frames.begin(),
341 result->frames.end(), lessForFrameResultValue);
342 result->max = element->value;
343 result->max_frame_number = element->frame_number;
mikhal@webrtc.org552f1732011-08-26 17:38:09 +0000344
345 delete [] ref;
346 delete [] test;
347
348 fclose(refFp);
349 fclose(testFp);
350
351 return 0;
352}