• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2016 The WebM 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 <assert.h>
12 #include <errno.h>
13 #include <math.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include "vpx/vpx_codec.h"
18 #include "vpx/vpx_integer.h"
19 #include "./y4minput.h"
20 #include "vpx_dsp/ssim.h"
21 #include "vpx_ports/mem.h"
22 
23 static const int64_t cc1 = 26634;        // (64^2*(.01*255)^2
24 static const int64_t cc2 = 239708;       // (64^2*(.03*255)^2
25 static const int64_t cc1_10 = 428658;    // (64^2*(.01*1023)^2
26 static const int64_t cc2_10 = 3857925;   // (64^2*(.03*1023)^2
27 static const int64_t cc1_12 = 6868593;   // (64^2*(.01*4095)^2
28 static const int64_t cc2_12 = 61817334;  // (64^2*(.03*4095)^2
29 
30 #if CONFIG_VP9_HIGHBITDEPTH
calc_plane_error16(uint16_t * orig,int orig_stride,uint16_t * recon,int recon_stride,unsigned int cols,unsigned int rows)31 static uint64_t calc_plane_error16(uint16_t *orig, int orig_stride,
32                                    uint16_t *recon, int recon_stride,
33                                    unsigned int cols, unsigned int rows) {
34   unsigned int row, col;
35   uint64_t total_sse = 0;
36   int diff;
37   if (orig == NULL || recon == NULL) {
38     assert(0);
39     return 0;
40   }
41 
42   for (row = 0; row < rows; row++) {
43     for (col = 0; col < cols; col++) {
44       diff = orig[col] - recon[col];
45       total_sse += diff * diff;
46     }
47 
48     orig += orig_stride;
49     recon += recon_stride;
50   }
51   return total_sse;
52 }
53 #endif  // CONFIG_VP9_HIGHBITDEPTH
54 
calc_plane_error(uint8_t * orig,int orig_stride,uint8_t * recon,int recon_stride,unsigned int cols,unsigned int rows)55 static uint64_t calc_plane_error(uint8_t *orig, int orig_stride, uint8_t *recon,
56                                  int recon_stride, unsigned int cols,
57                                  unsigned int rows) {
58   unsigned int row, col;
59   uint64_t total_sse = 0;
60   int diff;
61   if (orig == NULL || recon == NULL) {
62     assert(0);
63     return 0;
64   }
65 
66   for (row = 0; row < rows; row++) {
67     for (col = 0; col < cols; col++) {
68       diff = orig[col] - recon[col];
69       total_sse += diff * diff;
70     }
71 
72     orig += orig_stride;
73     recon += recon_stride;
74   }
75   return total_sse;
76 }
77 
78 #define MAX_PSNR 100
mse2psnr(double samples,double peak,double mse)79 static double mse2psnr(double samples, double peak, double mse) {
80   double psnr;
81 
82   if (mse > 0.0)
83     psnr = 10.0 * log10(peak * peak * samples / mse);
84   else
85     psnr = MAX_PSNR;  // Limit to prevent / 0
86 
87   if (psnr > MAX_PSNR) psnr = MAX_PSNR;
88 
89   return psnr;
90 }
91 
92 typedef enum { RAW_YUV, Y4M } input_file_type;
93 
94 typedef struct input_file {
95   FILE *file;
96   input_file_type type;
97   unsigned char *buf;
98   y4m_input y4m;
99   vpx_image_t img;
100   int w;
101   int h;
102   int bit_depth;
103   int frame_size;
104 } input_file_t;
105 
106 // Open a file and determine if its y4m or raw.  If y4m get the header.
open_input_file(const char * file_name,input_file_t * input,int w,int h,int bit_depth)107 static int open_input_file(const char *file_name, input_file_t *input, int w,
108                            int h, int bit_depth) {
109   char y4m_buf[4];
110   input->w = w;
111   input->h = h;
112   input->bit_depth = bit_depth;
113   input->type = RAW_YUV;
114   input->buf = NULL;
115   input->file = strcmp(file_name, "-") ? fopen(file_name, "rb") : stdin;
116   if (input->file == NULL) return -1;
117   if (fread(y4m_buf, 1, 4, input->file) != 4) return -1;
118   if (memcmp(y4m_buf, "YUV4", 4) == 0) input->type = Y4M;
119   switch (input->type) {
120     case Y4M:
121       y4m_input_open(&input->y4m, input->file, y4m_buf, 4, 0);
122       input->w = input->y4m.pic_w;
123       input->h = input->y4m.pic_h;
124       input->bit_depth = input->y4m.bit_depth;
125       // Y4M alloc's its own buf. Init this to avoid problems if we never
126       // read frames.
127       memset(&input->img, 0, sizeof(input->img));
128       break;
129     case RAW_YUV:
130       fseek(input->file, 0, SEEK_SET);
131       input->w = w;
132       input->h = h;
133       // handle odd frame sizes
134       input->frame_size = w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
135       if (bit_depth > 8) {
136         input->frame_size *= 2;
137       }
138       input->buf = malloc(input->frame_size);
139       break;
140   }
141   return 0;
142 }
143 
close_input_file(input_file_t * in)144 static void close_input_file(input_file_t *in) {
145   if (in->file) fclose(in->file);
146   if (in->type == Y4M) {
147     vpx_img_free(&in->img);
148   } else {
149     free(in->buf);
150   }
151 }
152 
read_input_file(input_file_t * in,unsigned char ** y,unsigned char ** u,unsigned char ** v,int bd)153 static size_t read_input_file(input_file_t *in, unsigned char **y,
154                               unsigned char **u, unsigned char **v, int bd) {
155   size_t r1 = 0;
156   switch (in->type) {
157     case Y4M:
158       r1 = y4m_input_fetch_frame(&in->y4m, in->file, &in->img);
159       *y = in->img.planes[0];
160       *u = in->img.planes[1];
161       *v = in->img.planes[2];
162       break;
163     case RAW_YUV:
164       if (bd < 9) {
165         r1 = fread(in->buf, in->frame_size, 1, in->file);
166         *y = in->buf;
167         *u = in->buf + in->w * in->h;
168         *v = *u + ((1 + in->w) / 2) * ((1 + in->h) / 2);
169       } else {
170         r1 = fread(in->buf, in->frame_size, 1, in->file);
171         *y = in->buf;
172         *u = in->buf + (in->w * in->h) * 2;
173         *v = *u + 2 * ((1 + in->w) / 2) * ((1 + in->h) / 2);
174       }
175       break;
176   }
177 
178   return r1;
179 }
180 
ssim_parms_8x8(const uint8_t * s,int sp,const uint8_t * r,int rp,uint32_t * sum_s,uint32_t * sum_r,uint32_t * sum_sq_s,uint32_t * sum_sq_r,uint32_t * sum_sxr)181 static void ssim_parms_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp,
182                            uint32_t *sum_s, uint32_t *sum_r, uint32_t *sum_sq_s,
183                            uint32_t *sum_sq_r, uint32_t *sum_sxr) {
184   int i, j;
185   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
186       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
187     assert(0);
188     return;
189   }
190   for (i = 0; i < 8; i++, s += sp, r += rp) {
191     for (j = 0; j < 8; j++) {
192       *sum_s += s[j];
193       *sum_r += r[j];
194       *sum_sq_s += s[j] * s[j];
195       *sum_sq_r += r[j] * r[j];
196       *sum_sxr += s[j] * r[j];
197     }
198   }
199 }
200 
201 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim_parms_8x8(const uint16_t * s,int sp,const uint16_t * r,int rp,uint32_t * sum_s,uint32_t * sum_r,uint32_t * sum_sq_s,uint32_t * sum_sq_r,uint32_t * sum_sxr)202 static void highbd_ssim_parms_8x8(const uint16_t *s, int sp, const uint16_t *r,
203                                   int rp, uint32_t *sum_s, uint32_t *sum_r,
204                                   uint32_t *sum_sq_s, uint32_t *sum_sq_r,
205                                   uint32_t *sum_sxr) {
206   int i, j;
207   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
208       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
209     assert(0);
210     return;
211   }
212   for (i = 0; i < 8; i++, s += sp, r += rp) {
213     for (j = 0; j < 8; j++) {
214       *sum_s += s[j];
215       *sum_r += r[j];
216       *sum_sq_s += s[j] * s[j];
217       *sum_sq_r += r[j] * r[j];
218       *sum_sxr += s[j] * r[j];
219     }
220   }
221 }
222 #endif  // CONFIG_VP9_HIGHBITDEPTH
223 
similarity(uint32_t sum_s,uint32_t sum_r,uint32_t sum_sq_s,uint32_t sum_sq_r,uint32_t sum_sxr,int count,uint32_t bd)224 static double similarity(uint32_t sum_s, uint32_t sum_r, uint32_t sum_sq_s,
225                          uint32_t sum_sq_r, uint32_t sum_sxr, int count,
226                          uint32_t bd) {
227   double ssim_n, ssim_d;
228   int64_t c1 = 0, c2 = 0;
229   if (bd == 8) {
230     // scale the constants by number of pixels
231     c1 = (cc1 * count * count) >> 12;
232     c2 = (cc2 * count * count) >> 12;
233   } else if (bd == 10) {
234     c1 = (cc1_10 * count * count) >> 12;
235     c2 = (cc2_10 * count * count) >> 12;
236   } else if (bd == 12) {
237     c1 = (cc1_12 * count * count) >> 12;
238     c2 = (cc2_12 * count * count) >> 12;
239   } else {
240     assert(0);
241   }
242 
243   ssim_n = (2.0 * sum_s * sum_r + c1) *
244            (2.0 * count * sum_sxr - 2.0 * sum_s * sum_r + c2);
245 
246   ssim_d = ((double)sum_s * sum_s + (double)sum_r * sum_r + c1) *
247            ((double)count * sum_sq_s - (double)sum_s * sum_s +
248             (double)count * sum_sq_r - (double)sum_r * sum_r + c2);
249 
250   return ssim_n / ssim_d;
251 }
252 
ssim_8x8(const uint8_t * s,int sp,const uint8_t * r,int rp)253 static double ssim_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp) {
254   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
255   ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r, &sum_sxr);
256   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, 8);
257 }
258 
259 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim_8x8(const uint16_t * s,int sp,const uint16_t * r,int rp,uint32_t bd)260 static double highbd_ssim_8x8(const uint16_t *s, int sp, const uint16_t *r,
261                               int rp, uint32_t bd) {
262   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
263   highbd_ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r,
264                         &sum_sxr);
265   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, bd);
266 }
267 #endif  // CONFIG_VP9_HIGHBITDEPTH
268 
269 // We are using a 8x8 moving window with starting location of each 8x8 window
270 // on the 4x4 pixel grid. Such arrangement allows the windows to overlap
271 // block boundaries to penalize blocking artifacts.
ssim2(const uint8_t * img1,const uint8_t * img2,int stride_img1,int stride_img2,int width,int height)272 static double ssim2(const uint8_t *img1, const uint8_t *img2, int stride_img1,
273                     int stride_img2, int width, int height) {
274   int i, j;
275   int samples = 0;
276   double ssim_total = 0;
277 
278   // sample point start with each 4x4 location
279   for (i = 0; i <= height - 8;
280        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
281     for (j = 0; j <= width - 8; j += 4) {
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 
291 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim2(const uint8_t * img1,const uint8_t * img2,int stride_img1,int stride_img2,int width,int height,uint32_t bd)292 static double highbd_ssim2(const uint8_t *img1, const uint8_t *img2,
293                            int stride_img1, int stride_img2, int width,
294                            int height, uint32_t bd) {
295   int i, j;
296   int samples = 0;
297   double ssim_total = 0;
298 
299   // sample point start with each 4x4 location
300   for (i = 0; i <= height - 8;
301        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
302     for (j = 0; j <= width - 8; j += 4) {
303       double v =
304           highbd_ssim_8x8(CONVERT_TO_SHORTPTR(img1 + j), stride_img1,
305                           CONVERT_TO_SHORTPTR(img2 + j), stride_img2, bd);
306       ssim_total += v;
307       samples++;
308     }
309   }
310   ssim_total /= samples;
311   return ssim_total;
312 }
313 #endif  // CONFIG_VP9_HIGHBITDEPTH
314 
main(int argc,char * argv[])315 int main(int argc, char *argv[]) {
316   FILE *framestats = NULL;
317   int bit_depth = 8;
318   int w = 0, h = 0, tl_skip = 0, tl_skips_remaining = 0;
319   double ssimavg = 0, ssimyavg = 0, ssimuavg = 0, ssimvavg = 0;
320   double psnrglb = 0, psnryglb = 0, psnruglb = 0, psnrvglb = 0;
321   double psnravg = 0, psnryavg = 0, psnruavg = 0, psnrvavg = 0;
322   double *ssimy = NULL, *ssimu = NULL, *ssimv = NULL;
323   uint64_t *psnry = NULL, *psnru = NULL, *psnrv = NULL;
324   size_t i, n_frames = 0, allocated_frames = 0;
325   int return_value = 0;
326   input_file_t in[2];
327   double peak = 255.0;
328 
329   memset(in, 0, sizeof(in));
330 
331   if (argc < 2) {
332     fprintf(stderr,
333             "Usage: %s file1.{yuv|y4m} file2.{yuv|y4m}"
334             "[WxH tl_skip={0,1,3} frame_stats_file bits]\n",
335             argv[0]);
336     return 1;
337   }
338 
339   if (argc > 3) {
340     sscanf(argv[3], "%dx%d", &w, &h);
341   }
342 
343   if (argc > 6) {
344     sscanf(argv[6], "%d", &bit_depth);
345   }
346 
347   if (open_input_file(argv[1], &in[0], w, h, bit_depth) < 0) {
348     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[1]);
349     goto clean_up;
350   }
351 
352   if (w == 0 && h == 0) {
353     // If a y4m is the first file and w, h is not set grab from first file.
354     w = in[0].w;
355     h = in[0].h;
356     bit_depth = in[0].bit_depth;
357   }
358   if (bit_depth == 10) peak = 1023.0;
359 
360   if (bit_depth == 12) peak = 4095.0;
361 
362   if (open_input_file(argv[2], &in[1], w, h, bit_depth) < 0) {
363     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
364     goto clean_up;
365   }
366 
367   if (in[0].w != in[1].w || in[0].h != in[1].h || in[0].w != w ||
368       in[0].h != h || w == 0 || h == 0) {
369     fprintf(stderr,
370             "Failing: Image dimensions don't match or are unspecified!\n");
371     return_value = 1;
372     goto clean_up;
373   }
374 
375   if (in[0].bit_depth != in[1].bit_depth) {
376     fprintf(stderr,
377             "Failing: Image bit depths don't match or are unspecified!\n");
378     return_value = 1;
379     goto clean_up;
380   }
381 
382   bit_depth = in[0].bit_depth;
383 
384   // Number of frames to skip from file1.yuv for every frame used. Normal
385   // values 0, 1 and 3 correspond to TL2, TL1 and TL0 respectively for a 3TL
386   // encoding in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer
387   // encoding.
388   if (argc > 4) {
389     sscanf(argv[4], "%d", &tl_skip);
390     if (argc > 5) {
391       framestats = fopen(argv[5], "w");
392       if (!framestats) {
393         fprintf(stderr, "Could not open \"%s\" for writing: %s\n", argv[5],
394                 strerror(errno));
395         return_value = 1;
396         goto clean_up;
397       }
398     }
399   }
400 
401   while (1) {
402     size_t r1, r2;
403     unsigned char *y[2], *u[2], *v[2];
404 
405     r1 = read_input_file(&in[0], &y[0], &u[0], &v[0], bit_depth);
406 
407     if (r1) {
408       // Reading parts of file1.yuv that were not used in temporal layer.
409       if (tl_skips_remaining > 0) {
410         --tl_skips_remaining;
411         continue;
412       }
413       // Use frame, but skip |tl_skip| after it.
414       tl_skips_remaining = tl_skip;
415     }
416 
417     r2 = read_input_file(&in[1], &y[1], &u[1], &v[1], bit_depth);
418 
419     if (r1 && r2 && r1 != r2) {
420       fprintf(stderr, "Failed to read data: %s [%d/%d]\n", strerror(errno),
421               (int)r1, (int)r2);
422       return_value = 1;
423       goto clean_up;
424     } else if (r1 == 0 || r2 == 0) {
425       break;
426     }
427 #if CONFIG_VP9_HIGHBITDEPTH
428 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h)                            \
429   if (bit_depth < 9) {                                                         \
430     ssim = ssim2(buf0, buf1, w, w, w, h);                                      \
431     psnr = calc_plane_error(buf0, w, buf1, w, w, h);                           \
432   } else {                                                                     \
433     ssim = highbd_ssim2(CONVERT_TO_BYTEPTR(buf0), CONVERT_TO_BYTEPTR(buf1), w, \
434                         w, w, h, bit_depth);                                   \
435     psnr = calc_plane_error16(CAST_TO_SHORTPTR(buf0), w,                       \
436                               CAST_TO_SHORTPTR(buf1), w, w, h);                \
437   }
438 #else
439 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h) \
440   ssim = ssim2(buf0, buf1, w, w, w, h);             \
441   psnr = calc_plane_error(buf0, w, buf1, w, w, h);
442 #endif  // CONFIG_VP9_HIGHBITDEPTH
443 
444     if (n_frames == allocated_frames) {
445       allocated_frames = allocated_frames == 0 ? 1024 : allocated_frames * 2;
446       ssimy = realloc(ssimy, allocated_frames * sizeof(*ssimy));
447       ssimu = realloc(ssimu, allocated_frames * sizeof(*ssimu));
448       ssimv = realloc(ssimv, allocated_frames * sizeof(*ssimv));
449       psnry = realloc(psnry, allocated_frames * sizeof(*psnry));
450       psnru = realloc(psnru, allocated_frames * sizeof(*psnru));
451       psnrv = realloc(psnrv, allocated_frames * sizeof(*psnrv));
452     }
453     psnr_and_ssim(ssimy[n_frames], psnry[n_frames], y[0], y[1], w, h);
454     psnr_and_ssim(ssimu[n_frames], psnru[n_frames], u[0], u[1], (w + 1) / 2,
455                   (h + 1) / 2);
456     psnr_and_ssim(ssimv[n_frames], psnrv[n_frames], v[0], v[1], (w + 1) / 2,
457                   (h + 1) / 2);
458 
459     n_frames++;
460   }
461 
462   if (framestats) {
463     fprintf(framestats,
464             "ssim,ssim-y,ssim-u,ssim-v,psnr,psnr-y,psnr-u,psnr-v\n");
465   }
466 
467   for (i = 0; i < n_frames; ++i) {
468     double frame_ssim;
469     double frame_psnr, frame_psnry, frame_psnru, frame_psnrv;
470 
471     frame_ssim = 0.8 * ssimy[i] + 0.1 * (ssimu[i] + ssimv[i]);
472     ssimavg += frame_ssim;
473     ssimyavg += ssimy[i];
474     ssimuavg += ssimu[i];
475     ssimvavg += ssimv[i];
476 
477     frame_psnr =
478         mse2psnr(w * h * 6 / 4, peak, (double)psnry[i] + psnru[i] + psnrv[i]);
479     frame_psnry = mse2psnr(w * h * 4 / 4, peak, (double)psnry[i]);
480     frame_psnru = mse2psnr(w * h * 1 / 4, peak, (double)psnru[i]);
481     frame_psnrv = mse2psnr(w * h * 1 / 4, peak, (double)psnrv[i]);
482 
483     psnravg += frame_psnr;
484     psnryavg += frame_psnry;
485     psnruavg += frame_psnru;
486     psnrvavg += frame_psnrv;
487 
488     psnryglb += psnry[i];
489     psnruglb += psnru[i];
490     psnrvglb += psnrv[i];
491 
492     if (framestats) {
493       fprintf(framestats, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf\n", frame_ssim,
494               ssimy[i], ssimu[i], ssimv[i], frame_psnr, frame_psnry,
495               frame_psnru, frame_psnrv);
496     }
497   }
498 
499   ssimavg /= n_frames;
500   ssimyavg /= n_frames;
501   ssimuavg /= n_frames;
502   ssimvavg /= n_frames;
503 
504   printf("VpxSSIM: %lf\n", 100 * pow(ssimavg, 8.0));
505   printf("SSIM: %lf\n", ssimavg);
506   printf("SSIM-Y: %lf\n", ssimyavg);
507   printf("SSIM-U: %lf\n", ssimuavg);
508   printf("SSIM-V: %lf\n", ssimvavg);
509   puts("");
510 
511   psnravg /= n_frames;
512   psnryavg /= n_frames;
513   psnruavg /= n_frames;
514   psnrvavg /= n_frames;
515 
516   printf("AvgPSNR: %lf\n", psnravg);
517   printf("AvgPSNR-Y: %lf\n", psnryavg);
518   printf("AvgPSNR-U: %lf\n", psnruavg);
519   printf("AvgPSNR-V: %lf\n", psnrvavg);
520   puts("");
521 
522   psnrglb = psnryglb + psnruglb + psnrvglb;
523   psnrglb = mse2psnr((double)n_frames * w * h * 6 / 4, peak, psnrglb);
524   psnryglb = mse2psnr((double)n_frames * w * h * 4 / 4, peak, psnryglb);
525   psnruglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnruglb);
526   psnrvglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnrvglb);
527 
528   printf("GlbPSNR: %lf\n", psnrglb);
529   printf("GlbPSNR-Y: %lf\n", psnryglb);
530   printf("GlbPSNR-U: %lf\n", psnruglb);
531   printf("GlbPSNR-V: %lf\n", psnrvglb);
532   puts("");
533 
534   printf("Nframes: %d\n", (int)n_frames);
535 
536 clean_up:
537 
538   close_input_file(&in[0]);
539   close_input_file(&in[1]);
540 
541   if (framestats) fclose(framestats);
542 
543   free(ssimy);
544   free(ssimu);
545   free(ssimv);
546 
547   free(psnry);
548   free(psnru);
549   free(psnrv);
550 
551   return return_value;
552 }
553