xref: /aosp_15_r20/external/libvpx/tools/tiny_ssim.c (revision fb1b10ab9aebc7c7068eedab379b749d7e3900be)
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 
153 // Returns 1 on success, 0 on failure due to a read error or eof (or format
154 // error in the case of y4m).
read_input_file(input_file_t * in,unsigned char ** y,unsigned char ** u,unsigned char ** v,int bd)155 static int read_input_file(input_file_t *in, unsigned char **y,
156                            unsigned char **u, unsigned char **v, int bd) {
157   size_t r1 = 0;
158   switch (in->type) {
159     case Y4M:
160       r1 = y4m_input_fetch_frame(&in->y4m, in->file, &in->img);
161       if (r1 == (size_t)-1) return 0;
162       *y = in->img.planes[0];
163       *u = in->img.planes[1];
164       *v = in->img.planes[2];
165       break;
166     case RAW_YUV:
167       if (bd < 9) {
168         r1 = fread(in->buf, in->frame_size, 1, in->file);
169         *y = in->buf;
170         *u = in->buf + in->w * in->h;
171         *v = *u + ((1 + in->w) / 2) * ((1 + in->h) / 2);
172       } else {
173         r1 = fread(in->buf, in->frame_size, 1, in->file);
174         *y = in->buf;
175         *u = in->buf + (in->w * in->h) * 2;
176         *v = *u + 2 * ((1 + in->w) / 2) * ((1 + in->h) / 2);
177       }
178       break;
179   }
180 
181   return r1 != 0;
182 }
183 
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)184 static void ssim_parms_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp,
185                            uint32_t *sum_s, uint32_t *sum_r, uint32_t *sum_sq_s,
186                            uint32_t *sum_sq_r, uint32_t *sum_sxr) {
187   int i, j;
188   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
189       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
190     assert(0);
191     return;
192   }
193   for (i = 0; i < 8; i++, s += sp, r += rp) {
194     for (j = 0; j < 8; j++) {
195       *sum_s += s[j];
196       *sum_r += r[j];
197       *sum_sq_s += s[j] * s[j];
198       *sum_sq_r += r[j] * r[j];
199       *sum_sxr += s[j] * r[j];
200     }
201   }
202 }
203 
204 #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)205 static void highbd_ssim_parms_8x8(const uint16_t *s, int sp, const uint16_t *r,
206                                   int rp, uint32_t *sum_s, uint32_t *sum_r,
207                                   uint32_t *sum_sq_s, uint32_t *sum_sq_r,
208                                   uint32_t *sum_sxr) {
209   int i, j;
210   if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
211       sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
212     assert(0);
213     return;
214   }
215   for (i = 0; i < 8; i++, s += sp, r += rp) {
216     for (j = 0; j < 8; j++) {
217       *sum_s += s[j];
218       *sum_r += r[j];
219       *sum_sq_s += s[j] * s[j];
220       *sum_sq_r += r[j] * r[j];
221       *sum_sxr += s[j] * r[j];
222     }
223   }
224 }
225 #endif  // CONFIG_VP9_HIGHBITDEPTH
226 
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)227 static double similarity(uint32_t sum_s, uint32_t sum_r, uint32_t sum_sq_s,
228                          uint32_t sum_sq_r, uint32_t sum_sxr, int count,
229                          uint32_t bd) {
230   double ssim_n, ssim_d;
231   int64_t c1 = 0, c2 = 0;
232   if (bd == 8) {
233     // scale the constants by number of pixels
234     c1 = (cc1 * count * count) >> 12;
235     c2 = (cc2 * count * count) >> 12;
236   } else if (bd == 10) {
237     c1 = (cc1_10 * count * count) >> 12;
238     c2 = (cc2_10 * count * count) >> 12;
239   } else if (bd == 12) {
240     c1 = (cc1_12 * count * count) >> 12;
241     c2 = (cc2_12 * count * count) >> 12;
242   } else {
243     assert(0);
244   }
245 
246   ssim_n = (2.0 * sum_s * sum_r + c1) *
247            (2.0 * count * sum_sxr - 2.0 * sum_s * sum_r + c2);
248 
249   ssim_d = ((double)sum_s * sum_s + (double)sum_r * sum_r + c1) *
250            ((double)count * sum_sq_s - (double)sum_s * sum_s +
251             (double)count * sum_sq_r - (double)sum_r * sum_r + c2);
252 
253   return ssim_n / ssim_d;
254 }
255 
ssim_8x8(const uint8_t * s,int sp,const uint8_t * r,int rp)256 static double ssim_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp) {
257   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
258   ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r, &sum_sxr);
259   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, 8);
260 }
261 
262 #if CONFIG_VP9_HIGHBITDEPTH
highbd_ssim_8x8(const uint16_t * s,int sp,const uint16_t * r,int rp,uint32_t bd)263 static double highbd_ssim_8x8(const uint16_t *s, int sp, const uint16_t *r,
264                               int rp, uint32_t bd) {
265   uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
266   highbd_ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r,
267                         &sum_sxr);
268   return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, bd);
269 }
270 #endif  // CONFIG_VP9_HIGHBITDEPTH
271 
272 // We are using a 8x8 moving window with starting location of each 8x8 window
273 // on the 4x4 pixel grid. Such arrangement allows the windows to overlap
274 // 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)275 static double ssim2(const uint8_t *img1, const uint8_t *img2, int stride_img1,
276                     int stride_img2, int width, int height) {
277   int i, j;
278   int samples = 0;
279   double ssim_total = 0;
280 
281   // sample point start with each 4x4 location
282   for (i = 0; i <= height - 8;
283        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
284     for (j = 0; j <= width - 8; j += 4) {
285       double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
286       ssim_total += v;
287       samples++;
288     }
289   }
290   ssim_total /= samples;
291   return ssim_total;
292 }
293 
294 #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)295 static double highbd_ssim2(const uint8_t *img1, const uint8_t *img2,
296                            int stride_img1, int stride_img2, int width,
297                            int height, uint32_t bd) {
298   int i, j;
299   int samples = 0;
300   double ssim_total = 0;
301 
302   // sample point start with each 4x4 location
303   for (i = 0; i <= height - 8;
304        i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
305     for (j = 0; j <= width - 8; j += 4) {
306       double v =
307           highbd_ssim_8x8(CONVERT_TO_SHORTPTR(img1 + j), stride_img1,
308                           CONVERT_TO_SHORTPTR(img2 + j), stride_img2, bd);
309       ssim_total += v;
310       samples++;
311     }
312   }
313   ssim_total /= samples;
314   return ssim_total;
315 }
316 #endif  // CONFIG_VP9_HIGHBITDEPTH
317 
main(int argc,char * argv[])318 int main(int argc, char *argv[]) {
319   FILE *framestats = NULL;
320   int bit_depth = 8;
321   int w = 0, h = 0, tl_skip = 0, tl_skips_remaining = 0;
322   double ssimavg = 0, ssimyavg = 0, ssimuavg = 0, ssimvavg = 0;
323   double psnrglb = 0, psnryglb = 0, psnruglb = 0, psnrvglb = 0;
324   double psnravg = 0, psnryavg = 0, psnruavg = 0, psnrvavg = 0;
325   double *ssimy = NULL, *ssimu = NULL, *ssimv = NULL;
326   uint64_t *psnry = NULL, *psnru = NULL, *psnrv = NULL;
327   size_t i, n_frames = 0, allocated_frames = 0;
328   int return_value = 0;
329   input_file_t in[2];
330   double peak = 255.0;
331 
332   memset(in, 0, sizeof(in));
333 
334   if (argc < 3) {
335     fprintf(stderr,
336             "Usage: %s file1.{yuv|y4m} file2.{yuv|y4m}"
337             " [WxH tl_skip={0,1,3} frame_stats_file bits]\n",
338             argv[0]);
339     return 1;
340   }
341 
342   if (argc > 3) {
343     sscanf(argv[3], "%dx%d", &w, &h);
344   }
345 
346   if (argc > 6) {
347     sscanf(argv[6], "%d", &bit_depth);
348   }
349 
350   if (open_input_file(argv[1], &in[0], w, h, bit_depth) < 0) {
351     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[1]);
352     goto clean_up;
353   }
354 
355   if (w == 0 && h == 0) {
356     // If a y4m is the first file and w, h is not set grab from first file.
357     w = in[0].w;
358     h = in[0].h;
359     bit_depth = in[0].bit_depth;
360   }
361   if (bit_depth == 10) peak = 1023.0;
362 
363   if (bit_depth == 12) peak = 4095.0;
364 
365   if (open_input_file(argv[2], &in[1], w, h, bit_depth) < 0) {
366     fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
367     goto clean_up;
368   }
369 
370   if (in[0].w != in[1].w || in[0].h != in[1].h || in[0].w != w ||
371       in[0].h != h || w == 0 || h == 0) {
372     fprintf(stderr,
373             "Failing: Image dimensions don't match or are unspecified!\n");
374     return_value = 1;
375     goto clean_up;
376   }
377 
378   if (in[0].bit_depth != in[1].bit_depth) {
379     fprintf(stderr,
380             "Failing: Image bit depths don't match or are unspecified!\n");
381     return_value = 1;
382     goto clean_up;
383   }
384 
385   bit_depth = in[0].bit_depth;
386 
387   // Number of frames to skip from file1.yuv for every frame used. Normal
388   // values 0, 1 and 3 correspond to TL2, TL1 and TL0 respectively for a 3TL
389   // encoding in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer
390   // encoding.
391   if (argc > 4) {
392     sscanf(argv[4], "%d", &tl_skip);
393     if (argc > 5) {
394       framestats = fopen(argv[5], "w");
395       if (!framestats) {
396         fprintf(stderr, "Could not open \"%s\" for writing: %s\n", argv[5],
397                 strerror(errno));
398         return_value = 1;
399         goto clean_up;
400       }
401     }
402   }
403 
404   while (1) {
405     int r1, r2;
406     unsigned char *y[2], *u[2], *v[2];
407 
408     r1 = read_input_file(&in[0], &y[0], &u[0], &v[0], bit_depth);
409     if (r1 == 0) {
410       if (ferror(in[0].file)) {
411         fprintf(stderr, "Failed to read data from '%s'\n", argv[1]);
412         return_value = 1;
413         goto clean_up;
414       }
415       break;
416     }
417 
418     // Reading parts of file1.yuv that were not used in temporal layer.
419     if (tl_skips_remaining > 0) {
420       --tl_skips_remaining;
421       continue;
422     }
423     // Use frame, but skip |tl_skip| after it.
424     tl_skips_remaining = tl_skip;
425 
426     r2 = read_input_file(&in[1], &y[1], &u[1], &v[1], bit_depth);
427     if (r2 == 0) {
428       if (ferror(in[1].file)) {
429         fprintf(stderr, "Failed to read data from '%s'\n", argv[2]);
430         return_value = 1;
431         goto clean_up;
432       }
433       break;
434     }
435 
436 #if CONFIG_VP9_HIGHBITDEPTH
437 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h)                           \
438   do {                                                                        \
439     if (bit_depth < 9) {                                                      \
440       ssim = ssim2(buf0, buf1, w, w, w, h);                                   \
441       psnr = calc_plane_error(buf0, w, buf1, w, w, h);                        \
442     } else {                                                                  \
443       ssim = highbd_ssim2(CONVERT_TO_BYTEPTR(buf0), CONVERT_TO_BYTEPTR(buf1), \
444                           w, w, w, h, bit_depth);                             \
445       psnr = calc_plane_error16(CAST_TO_SHORTPTR(buf0), w,                    \
446                                 CAST_TO_SHORTPTR(buf1), w, w, h);             \
447     }                                                                         \
448   } while (0)
449 #else
450 #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h)  \
451   do {                                               \
452     ssim = ssim2(buf0, buf1, w, w, w, h);            \
453     psnr = calc_plane_error(buf0, w, buf1, w, w, h); \
454   } while (0)
455 #endif  // CONFIG_VP9_HIGHBITDEPTH
456 
457     if (n_frames == allocated_frames) {
458       allocated_frames = allocated_frames == 0 ? 1024 : allocated_frames * 2;
459       ssimy = realloc(ssimy, allocated_frames * sizeof(*ssimy));
460       ssimu = realloc(ssimu, allocated_frames * sizeof(*ssimu));
461       ssimv = realloc(ssimv, allocated_frames * sizeof(*ssimv));
462       psnry = realloc(psnry, allocated_frames * sizeof(*psnry));
463       psnru = realloc(psnru, allocated_frames * sizeof(*psnru));
464       psnrv = realloc(psnrv, allocated_frames * sizeof(*psnrv));
465       if (!(ssimy && ssimu && ssimv && psnry && psnru && psnrv)) {
466         fprintf(stderr, "Error allocating SSIM/PSNR data.\n");
467         exit(EXIT_FAILURE);
468       }
469     }
470     psnr_and_ssim(ssimy[n_frames], psnry[n_frames], y[0], y[1], w, h);
471     psnr_and_ssim(ssimu[n_frames], psnru[n_frames], u[0], u[1], (w + 1) / 2,
472                   (h + 1) / 2);
473     psnr_and_ssim(ssimv[n_frames], psnrv[n_frames], v[0], v[1], (w + 1) / 2,
474                   (h + 1) / 2);
475 
476     n_frames++;
477   }
478 
479   if (framestats) {
480     fprintf(framestats,
481             "ssim,ssim-y,ssim-u,ssim-v,psnr,psnr-y,psnr-u,psnr-v\n");
482   }
483 
484   for (i = 0; i < n_frames; ++i) {
485     double frame_ssim;
486     double frame_psnr, frame_psnry, frame_psnru, frame_psnrv;
487 
488     frame_ssim = 0.8 * ssimy[i] + 0.1 * (ssimu[i] + ssimv[i]);
489     ssimavg += frame_ssim;
490     ssimyavg += ssimy[i];
491     ssimuavg += ssimu[i];
492     ssimvavg += ssimv[i];
493 
494     frame_psnr =
495         mse2psnr(w * h * 6 / 4, peak, (double)psnry[i] + psnru[i] + psnrv[i]);
496     frame_psnry = mse2psnr(w * h * 4 / 4, peak, (double)psnry[i]);
497     frame_psnru = mse2psnr(w * h * 1 / 4, peak, (double)psnru[i]);
498     frame_psnrv = mse2psnr(w * h * 1 / 4, peak, (double)psnrv[i]);
499 
500     psnravg += frame_psnr;
501     psnryavg += frame_psnry;
502     psnruavg += frame_psnru;
503     psnrvavg += frame_psnrv;
504 
505     psnryglb += psnry[i];
506     psnruglb += psnru[i];
507     psnrvglb += psnrv[i];
508 
509     if (framestats) {
510       fprintf(framestats, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf\n", frame_ssim,
511               ssimy[i], ssimu[i], ssimv[i], frame_psnr, frame_psnry,
512               frame_psnru, frame_psnrv);
513     }
514   }
515 
516   ssimavg /= n_frames;
517   ssimyavg /= n_frames;
518   ssimuavg /= n_frames;
519   ssimvavg /= n_frames;
520 
521   printf("VpxSSIM: %lf\n", 100 * pow(ssimavg, 8.0));
522   printf("SSIM: %lf\n", ssimavg);
523   printf("SSIM-Y: %lf\n", ssimyavg);
524   printf("SSIM-U: %lf\n", ssimuavg);
525   printf("SSIM-V: %lf\n", ssimvavg);
526   puts("");
527 
528   psnravg /= n_frames;
529   psnryavg /= n_frames;
530   psnruavg /= n_frames;
531   psnrvavg /= n_frames;
532 
533   printf("AvgPSNR: %lf\n", psnravg);
534   printf("AvgPSNR-Y: %lf\n", psnryavg);
535   printf("AvgPSNR-U: %lf\n", psnruavg);
536   printf("AvgPSNR-V: %lf\n", psnrvavg);
537   puts("");
538 
539   psnrglb = psnryglb + psnruglb + psnrvglb;
540   psnrglb = mse2psnr((double)n_frames * w * h * 6 / 4, peak, psnrglb);
541   psnryglb = mse2psnr((double)n_frames * w * h * 4 / 4, peak, psnryglb);
542   psnruglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnruglb);
543   psnrvglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnrvglb);
544 
545   printf("GlbPSNR: %lf\n", psnrglb);
546   printf("GlbPSNR-Y: %lf\n", psnryglb);
547   printf("GlbPSNR-U: %lf\n", psnruglb);
548   printf("GlbPSNR-V: %lf\n", psnrvglb);
549   puts("");
550 
551   printf("Nframes: %d\n", (int)n_frames);
552 
553 clean_up:
554 
555   close_input_file(&in[0]);
556   close_input_file(&in[1]);
557 
558   if (framestats) fclose(framestats);
559 
560   free(ssimy);
561   free(ssimu);
562   free(ssimv);
563 
564   free(psnry);
565   free(psnru);
566   free(psnrv);
567 
568   return return_value;
569 }
570