FFmpeg
vf_ssim360.c
Go to the documentation of this file.
1 /**
2  * Copyright (c) 2015-2021, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 /* Computes the Structural Similarity Metric between two 360 video streams.
23  * original SSIM algorithm:
24  * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli,
25  * "Image quality assessment: From error visibility to structural similarity,"
26  * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.
27  *
28  * To improve speed, this implementation uses the standard approximation of
29  * overlapped 8x8 block sums, rather than the original gaussian weights.
30  *
31  * To address warping from 360 projections for videos with same
32  * projection and resolution, the 8x8 blocks sampled are weighted by
33  * their location in the image.
34  *
35  * To apply SSIM across projections and video sizes, we render the video on to
36  * a flat "tape" from which the 8x8 are selected and compared.
37  */
38 
39 /*
40  * @file
41  * Caculate the SSIM between two input 360 videos.
42  */
43 
44 #include <math.h>
45 
46 #include "libavutil/avstring.h"
47 #include "libavutil/file_open.h"
48 #include "libavutil/opt.h"
49 #include "libavutil/pixdesc.h"
50 
51 #include "avfilter.h"
52 #include "drawutils.h"
53 #include "formats.h"
54 #include "internal.h"
55 #include "video.h"
56 #include "framesync.h"
57 
58 #define RIGHT 0
59 #define LEFT 1
60 #define TOP 2
61 #define BOTTOM 3
62 #define FRONT 4
63 #define BACK 5
64 
65 #define DEFAULT_HEATMAP_W 32
66 #define DEFAULT_HEATMAP_H 16
67 
68 #define M_PI_F ((float)M_PI)
69 #define M_PI_2_F ((float)M_PI_2)
70 #define M_PI_4_F ((float)M_PI_4)
71 #define M_SQRT2_F ((float)M_SQRT2)
72 
73 #define DEFAULT_EXPANSION_COEF 1.01f
74 
75 #define BARREL_THETA_RANGE (DEFAULT_EXPANSION_COEF * 2.0f * M_PI_F)
76 #define BARREL_PHI_RANGE (DEFAULT_EXPANSION_COEF * M_PI_2_F)
77 
78 // Use fixed-point with 16 bit precision for fast bilinear math
79 #define FIXED_POINT_PRECISION 16
80 
81 // Use 1MB per channel for the histogram to get 5-digit precise SSIM value
82 #define SSIM360_HIST_SIZE 131072
83 
84 // The last number is a marker < 0 to mark end of list
85 static const double PERCENTILE_LIST[] = {
86  1.0, 0.9, 0.8, 0.7, 0.6,
87  0.5, 0.4, 0.3, 0.2, 0.1, 0, -1
88 };
89 
90 typedef enum StereoFormat {
95 } StereoFormat;
96 
97 typedef enum Projection {
104 } Projection;
105 
106 typedef struct Map2D {
107  int w, h;
108  double *value;
109 } Map2D;
110 
111 typedef struct HeatmapList {
113  struct HeatmapList *next;
114 } HeatmapList;
115 
116 typedef struct SampleParams {
117  int stride;
125  float expand_coef;
126 } SampleParams;
127 
128 typedef struct BilinearMap {
129  // Indices to the 4 samples to compute bilinear
130  int tli;
131  int tri;
132  int bli;
133  int bri;
134 
135  // Fixed point factors with which the above 4 sample vector's
136  // dot product needs to be computed for the final bilinear value
137  int tlf;
138  int trf;
139  int blf;
140  int brf;
141 } BilinearMap;
142 
143 typedef struct SSIM360Context {
144  const AVClass *class;
145 
147  // Stats file configuration
148  FILE *stats_file;
150 
151  // Component properties
153  double coefs[4];
154  char comps[4];
155  int max;
156 
157  // Channel configuration & properties
159 
160  int is_rgb;
161  uint8_t rgba_map[4];
162 
163  // Standard SSIM computation configuration & workspace
165 
166  int *temp;
167  uint64_t nb_ssim_frames;
168  uint64_t nb_net_frames;
169  double ssim360[4], ssim360_total;
170  double *ssim360_hist[4];
171  double ssim360_hist_net[4];
172  double ssim360_percentile_sum[4][256];
173 
174  // 360 projection configuration & workspace
179  float ref_pad;
180  float main_pad;
181  int use_tape;
182  char *heatmap_str;
185 
192  int tape_length[4];
195  float angular_resolution[4][2];
197  uint8_t *main, int main_stride,
198  uint8_t *ref, int ref_stride,
199  int width, int height, void *temp,
200  int max, Map2D density);
202 
203 #define OFFSET(x) offsetof(SSIM360Context, x)
204 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
205 
206 static const AVOption ssim360_options[] = {
207  { "stats_file", "Set file where to store per-frame difference information",
208  OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
209  { "f", "Set file where to store per-frame difference information",
210  OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
211 
212  { "compute_chroma",
213  "Specifies if non-luma channels must be computed",
214  OFFSET(compute_chroma), AV_OPT_TYPE_INT, {.i64 = 1},
215  0, 1, .flags = FLAGS },
216 
217  { "frame_skip_ratio",
218  "Specifies the number of frames to be skipped from evaluation, for every evaluated frame",
219  OFFSET(frame_skip_ratio), AV_OPT_TYPE_INT, {.i64 = 0},
220  0, 1000000, .flags = FLAGS },
221 
222  { "ref_projection", "projection of the reference video",
223  OFFSET(ref_projection), AV_OPT_TYPE_INT, {.i64 = PROJECTION_EQUIRECT},
224  0, PROJECTION_N - 1, .flags = FLAGS, "projection" },
225 
226  { "e", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_EQUIRECT}, 0, 0, FLAGS, "projection" },
227  { "equirect", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_EQUIRECT}, 0, 0, FLAGS, "projection" },
228  { "c3x2", "cubemap 3x2", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_CUBEMAP32}, 0, 0, FLAGS, "projection" },
229  { "c2x3", "cubemap 2x3", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_CUBEMAP23}, 0, 0, FLAGS, "projection" },
230  { "barrel", "barrel facebook's 360 format", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_BARREL}, 0, 0, FLAGS, "projection" },
231  { "barrelsplit", "barrel split facebook's 360 format", 0, AV_OPT_TYPE_CONST, {.i64 = PROJECTION_BARREL_SPLIT}, 0, 0, FLAGS, "projection" },
232 
233  { "main_projection", "projection of the main video",
234  OFFSET(main_projection), AV_OPT_TYPE_INT, {.i64 = PROJECTION_N},
235  0, PROJECTION_N, .flags = FLAGS, "projection" },
236 
237  { "ref_stereo", "stereo format of the reference video",
238  OFFSET(ref_stereo_format), AV_OPT_TYPE_INT, {.i64 = STEREO_FORMAT_MONO},
239  0, STEREO_FORMAT_N - 1, .flags = FLAGS, "stereo_format" },
240 
241  { "mono", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_MONO }, 0, 0, FLAGS, "stereo_format" },
242  { "tb", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_TB }, 0, 0, FLAGS, "stereo_format" },
243  { "lr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_LR }, 0, 0, FLAGS, "stereo_format" },
244 
245  { "main_stereo", "stereo format of main video",
246  OFFSET(main_stereo_format), AV_OPT_TYPE_INT, {.i64 = STEREO_FORMAT_N},
247  0, STEREO_FORMAT_N, .flags = FLAGS, "stereo_format" },
248 
249  { "ref_pad",
250  "Expansion (padding) coefficient for each cube face of the reference video",
251  OFFSET(ref_pad), AV_OPT_TYPE_FLOAT, {.dbl = .0f}, 0, 10, .flags = FLAGS },
252 
253  { "main_pad",
254  "Expansion (padding) coeffiecient for each cube face of the main video",
255  OFFSET(main_pad), AV_OPT_TYPE_FLOAT, {.dbl = .0f}, 0, 10, .flags = FLAGS },
256 
257  { "use_tape",
258  "Specifies if the tape based SSIM 360 algorithm must be used independent of the input video types",
259  OFFSET(use_tape), AV_OPT_TYPE_INT, {.i64 = 0},
260  0, 1, .flags = FLAGS },
261 
262  { "heatmap_str",
263  "Heatmap data for view-based evaluation. For heatmap file format, please refer to EntSphericalVideoHeatmapData.",
264  OFFSET(heatmap_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, .flags = FLAGS },
265 
266  { "default_heatmap_width",
267  "Default heatmap dimension. Will be used when dimension is not specified in heatmap data.",
268  OFFSET(default_heatmap_w), AV_OPT_TYPE_INT, {.i64 = 32}, 1, 4096, .flags = FLAGS },
269 
270  { "default_heatmap_height",
271  "Default heatmap dimension. Will be used when dimension is not specified in heatmap data.",
272  OFFSET(default_heatmap_h), AV_OPT_TYPE_INT, {.i64 = 16}, 1, 4096, .flags = FLAGS },
273 
274  { NULL }
275 };
276 
278 
279 static void set_meta(AVDictionary **metadata, const char *key, char comp, float d)
280 {
281  char value[128];
282  snprintf(value, sizeof(value), "%0.2f", d);
283  if (comp) {
284  char key2[128];
285  snprintf(key2, sizeof(key2), "%s%c", key, comp);
286  av_dict_set(metadata, key2, value, 0);
287  } else {
288  av_dict_set(metadata, key, value, 0);
289  }
290 }
291 
292 static void map_uninit(Map2D *map)
293 {
294  av_freep(&map->value);
295 }
296 
297 static int map_init(Map2D *map, int w, int h)
298 {
299  map->value = av_calloc(h * w, sizeof(*map->value));
300  if (!map->value)
301  return AVERROR(ENOMEM);
302 
303  map->h = h;
304  map->w = w;
305 
306  return 0;
307 }
308 
309 static void map_list_free(HeatmapList **pl)
310 {
311  HeatmapList *l = *pl;
312 
313  while (l) {
314  HeatmapList *next = l->next;
315  map_uninit(&l->map);
316  av_freep(&l);
317  l = next;
318  }
319 
320  *pl = NULL;
321 }
322 
323 static int map_alloc(HeatmapList **pl, int w, int h)
324 {
325  HeatmapList *l;
326  int ret;
327 
328  l = av_mallocz(sizeof(*l));
329  if (!l)
330  return AVERROR(ENOMEM);
331 
332  ret = map_init(&l->map, w, h);
333  if (ret < 0) {
334  av_freep(&l);
335  return ret;
336  }
337 
338  *pl = l;
339  return 0;
340 }
341 
342 static void
343 ssim360_4x4xn_16bit(const uint8_t *main8, ptrdiff_t main_stride,
344  const uint8_t *ref8, ptrdiff_t ref_stride,
345  int64_t (*sums)[4], int width)
346 {
347  const uint16_t *main16 = (const uint16_t *)main8;
348  const uint16_t *ref16 = (const uint16_t *)ref8;
349 
350  main_stride >>= 1;
351  ref_stride >>= 1;
352 
353  for (int z = 0; z < width; z++) {
354  uint64_t s1 = 0, s2 = 0, ss = 0, s12 = 0;
355 
356  for (int y = 0; y < 4; y++) {
357  for (int x = 0; x < 4; x++) {
358  unsigned a = main16[x + y * main_stride];
359  unsigned b = ref16[x + y * ref_stride];
360 
361  s1 += a;
362  s2 += b;
363  ss += a*a;
364  ss += b*b;
365  s12 += a*b;
366  }
367  }
368 
369  sums[z][0] = s1;
370  sums[z][1] = s2;
371  sums[z][2] = ss;
372  sums[z][3] = s12;
373  main16 += 4;
374  ref16 += 4;
375  }
376 }
377 
378 static void
379 ssim360_4x4xn_8bit(const uint8_t *main, ptrdiff_t main_stride,
380  const uint8_t *ref, ptrdiff_t ref_stride,
381  int (*sums)[4], int width)
382 {
383  for (int z = 0; z < width; z++) {
384  uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0;
385 
386  for (int y = 0; y < 4; y++) {
387  for (int x = 0; x < 4; x++) {
388  int a = main[x + y * main_stride];
389  int b = ref[x + y * ref_stride];
390 
391  s1 += a;
392  s2 += b;
393  ss += a*a;
394  ss += b*b;
395  s12 += a*b;
396  }
397  }
398 
399  sums[z][0] = s1;
400  sums[z][1] = s2;
401  sums[z][2] = ss;
402  sums[z][3] = s12;
403  main += 4;
404  ref += 4;
405  }
406 }
407 
408 static float ssim360_end1x(int64_t s1, int64_t s2, int64_t ss, int64_t s12, int max)
409 {
410  int64_t ssim_c1 = (int64_t)(.01 * .01 * max * max * 64 + .5);
411  int64_t ssim_c2 = (int64_t)(.03 * .03 * max * max * 64 * 63 + .5);
412 
413  int64_t fs1 = s1;
414  int64_t fs2 = s2;
415  int64_t fss = ss;
416  int64_t fs12 = s12;
417  int64_t vars = fss * 64 - fs1 * fs1 - fs2 * fs2;
418  int64_t covar = fs12 * 64 - fs1 * fs2;
419 
420  return (float)(2 * fs1 * fs2 + ssim_c1) * (float)(2 * covar + ssim_c2)
421  / ((float)(fs1 * fs1 + fs2 * fs2 + ssim_c1) * (float)(vars + ssim_c2));
422 }
423 
424 static float ssim360_end1(int s1, int s2, int ss, int s12)
425 {
426  static const int ssim_c1 = (int)(.01*.01*255*255*64 + .5);
427  static const int ssim_c2 = (int)(.03*.03*255*255*64*63 + .5);
428 
429  int fs1 = s1;
430  int fs2 = s2;
431  int fss = ss;
432  int fs12 = s12;
433  int vars = fss * 64 - fs1 * fs1 - fs2 * fs2;
434  int covar = fs12 * 64 - fs1 * fs2;
435 
436  return (float)(2 * fs1 * fs2 + ssim_c1) * (float)(2 * covar + ssim_c2)
437  / ((float)(fs1 * fs1 + fs2 * fs2 + ssim_c1) * (float)(vars + ssim_c2));
438 }
439 
440 static double
441 ssim360_endn_16bit(const int64_t (*sum0)[4], const int64_t (*sum1)[4],
442  int width, int max,
443  double *density_map, int map_width, double *total_weight)
444 {
445  double ssim360 = 0.0, weight;
446 
447  for (int i = 0; i < width; i++) {
448  weight = density_map ? density_map[(int) ((0.5 + i) / width * map_width)] : 1.0;
449  ssim360 += weight * ssim360_end1x(
450  sum0[i][0] + sum0[i + 1][0] + sum1[i][0] + sum1[i + 1][0],
451  sum0[i][1] + sum0[i + 1][1] + sum1[i][1] + sum1[i + 1][1],
452  sum0[i][2] + sum0[i + 1][2] + sum1[i][2] + sum1[i + 1][2],
453  sum0[i][3] + sum0[i + 1][3] + sum1[i][3] + sum1[i + 1][3],
454  max);
455  *total_weight += weight;
456  }
457  return ssim360;
458 }
459 
460 static double
461 ssim360_endn_8bit(const int (*sum0)[4], const int (*sum1)[4], int width,
462  double *density_map, int map_width, double *total_weight)
463 {
464  double ssim360 = 0.0, weight;
465 
466  for (int i = 0; i < width; i++) {
467  weight = density_map ? density_map[(int) ((0.5 + i) / width * map_width)] : 1.0;
468  ssim360 += weight * ssim360_end1(
469  sum0[i][0] + sum0[i + 1][0] + sum1[i][0] + sum1[i + 1][0],
470  sum0[i][1] + sum0[i + 1][1] + sum1[i][1] + sum1[i + 1][1],
471  sum0[i][2] + sum0[i + 1][2] + sum1[i][2] + sum1[i + 1][2],
472  sum0[i][3] + sum0[i + 1][3] + sum1[i][3] + sum1[i + 1][3]);
473  *total_weight += weight;
474  }
475  return ssim360;
476 }
477 
478 static double
479 ssim360_plane_16bit(uint8_t *main, int main_stride,
480  uint8_t *ref, int ref_stride,
481  int width, int height, void *temp,
482  int max, Map2D density)
483 {
484  int z = 0;
485  double ssim360 = 0.0;
486  int64_t (*sum0)[4] = temp;
487  int64_t (*sum1)[4] = sum0 + (width >> 2) + 3;
488  double total_weight = 0.0;
489 
490  width >>= 2;
491  height >>= 2;
492 
493  for (int y = 1; y < height; y++) {
494  for (; z <= y; z++) {
495  FFSWAP(void*, sum0, sum1);
496  ssim360_4x4xn_16bit(&main[4 * z * main_stride], main_stride,
497  &ref[4 * z * ref_stride], ref_stride,
498  sum0, width);
499  }
500  ssim360 += ssim360_endn_16bit(
501  (const int64_t (*)[4])sum0, (const int64_t (*)[4])sum1,
502  width - 1, max,
503  density.value ? density.value + density.w * ((int) ((z - 1.0) / height * density.h)) : NULL,
504  density.w, &total_weight);
505  }
506 
507  return (double) (ssim360 / total_weight);
508 }
509 
510 static double
511 ssim360_plane_8bit(uint8_t *main, int main_stride,
512  uint8_t *ref, int ref_stride,
513  int width, int height, void *temp,
514  int max, Map2D density)
515 {
516  int z = 0;
517  double ssim360 = 0.0;
518  int (*sum0)[4] = temp;
519  int (*sum1)[4] = sum0 + (width >> 2) + 3;
520  double total_weight = 0.0;
521 
522  width >>= 2;
523  height >>= 2;
524 
525  for (int y = 1; y < height; y++) {
526  for (; z <= y; z++) {
527  FFSWAP(void*, sum0, sum1);
529  &main[4 * z * main_stride], main_stride,
530  &ref[4 * z * ref_stride], ref_stride,
531  sum0, width);
532  }
533  ssim360 += ssim360_endn_8bit(
534  (const int (*)[4])sum0, (const int (*)[4])sum1, width - 1,
535  density.value ? density.value + density.w * ((int) ((z - 1.0) / height * density.h)) : NULL,
536  density.w, &total_weight);
537  }
538 
539  return (double) (ssim360 / total_weight);
540 }
541 
542 static double ssim360_db(double ssim360, double weight)
543 {
544  return 10 * log10(weight / (weight - ssim360));
545 }
546 
547 static int get_bilinear_sample(const uint8_t *data, BilinearMap *m, int max_value)
548 {
549  static const int fixed_point_half = 1 << (FIXED_POINT_PRECISION - 1);
550  static const int inv_byte_mask = UINT_MAX << 8;
551 
552  int tl, tr, bl, br, v;
553 
554  if (max_value & inv_byte_mask) {
555  uint16_t *data16 = (uint16_t *)data;
556  tl = data16[m->tli];
557  tr = data16[m->tri];
558  bl = data16[m->bli];
559  br = data16[m->bri];
560  } else {
561  tl = data[m->tli];
562  tr = data[m->tri];
563  bl = data[m->bli];
564  br = data[m->bri];
565  }
566 
567  v = m->tlf * tl +
568  m->trf * tr +
569  m->blf * bl +
570  m->brf * br;
571 
572  // Round by half, and revert the fixed-point offset
573  return ((v + fixed_point_half) >> FIXED_POINT_PRECISION) & max_value;
574 }
575 
576 static void
577 ssim360_4x4x2_tape(const uint8_t *main, BilinearMap *main_maps,
578  const uint8_t *ref, BilinearMap *ref_maps,
579  int offset_y, int max_value, int (*sums)[4])
580 {
581  int offset_x = 0;
582 
583  // Two blocks along the width
584  for (int z = 0; z < 2; z++) {
585  int s1 = 0, s2 = 0, ss = 0, s12 = 0;
586 
587  // 4 pixel block from (offset_x, offset_y)
588  for (int y = offset_y; y < offset_y + 4; y++) {
589  int y_stride = y << 3;
590  for (int x = offset_x; x < offset_x + 4; x++) {
591  int map_index = x + y_stride;
592  int a = get_bilinear_sample(main, main_maps + map_index, max_value);
593  int b = get_bilinear_sample(ref, ref_maps + map_index, max_value);
594 
595  s1 += a;
596  s2 += b;
597  ss += a*a;
598  ss += b*b;
599  s12 += a*b;
600  }
601  }
602 
603  sums[z][0] = s1;
604  sums[z][1] = s2;
605  sums[z][2] = ss;
606  sums[z][3] = s12;
607 
608  offset_x += 4;
609  }
610 }
611 
613 {
614  int floor_theta_by_2pi, floor_theta_by_pi;
615 
616  // Convert theta to range [0, 2*pi]
617  floor_theta_by_2pi = (int)(theta / (2.0f * M_PI_F)) - (theta < 0.0f);
618  theta -= 2.0f * M_PI_F * floor_theta_by_2pi;
619 
620  // Convert theta to range [-pi, pi]
621  floor_theta_by_pi = theta / M_PI_F;
622  theta -= 2.0f * M_PI_F * floor_theta_by_pi;
623  return FFMIN(M_PI_F, FFMAX(-M_PI_F, theta));
624 }
625 
626 static float get_heat(HeatmapList *heatmaps, float angular_resoluation, float norm_tape_pos)
627 {
628  float pitch, yaw, norm_pitch, norm_yaw;
629  int w, h;
630 
631  if (!heatmaps)
632  return 1.0f;
633 
634  pitch = asinf(norm_tape_pos*2);
635  yaw = M_PI_2_F * pitch / angular_resoluation;
637 
638  // normalize into [0,1]
639  norm_pitch = 1.0f - (pitch / M_PI_F + 0.5f);
640  norm_yaw = yaw / 2.0f / M_PI_F + 0.5f;
641 
642  // get heat on map
643  w = FFMIN(heatmaps->map.w - 1, FFMAX(0, heatmaps->map.w * norm_yaw));
644  h = FFMIN(heatmaps->map.h - 1, FFMAX(0, heatmaps->map.h * norm_pitch));
645  return heatmaps->map.value[h * heatmaps->map.w + w];
646 }
647 
648 static double
649 ssim360_tape(uint8_t *main, BilinearMap *main_maps,
650  uint8_t *ref, BilinearMap *ref_maps,
651  int tape_length, int max_value, void *temp,
652  double *ssim360_hist, double *ssim360_hist_net,
653  float angular_resolution, HeatmapList *heatmaps)
654 {
655  int horizontal_block_count = 2;
656  int vertical_block_count = tape_length >> 2;
657 
658  int z = 0, y;
659  // Since the tape will be very long and we need to average over all 8x8 blocks, use double
660  double ssim360 = 0.0;
661  double sum_weight = 0.0;
662 
663  int (*sum0)[4] = temp;
664  int (*sum1)[4] = sum0 + horizontal_block_count + 3;
665 
666  for (y = 1; y < vertical_block_count; y++) {
667  int fs1, fs2, fss, fs12, hist_index;
668  float norm_tape_pos, weight;
669  double sample_ssim360;
670 
671  for (; z <= y; z++) {
672  FFSWAP(void*, sum0, sum1);
673  ssim360_4x4x2_tape(main, main_maps, ref, ref_maps, z*4, max_value, sum0);
674  }
675 
676  // Given we have only one 8x8 block, following sums fit within 26 bits even for 10bit videos
677  fs1 = sum0[0][0] + sum0[1][0] + sum1[0][0] + sum1[1][0];
678  fs2 = sum0[0][1] + sum0[1][1] + sum1[0][1] + sum1[1][1];
679  fss = sum0[0][2] + sum0[1][2] + sum1[0][2] + sum1[1][2];
680  fs12 = sum0[0][3] + sum0[1][3] + sum1[0][3] + sum1[1][3];
681 
682  if (max_value > 255) {
683  // Since we need high precision to multiply fss / fs12 by 64, use double
684  double ssim_c1_d = .01*.01*64*max_value*max_value;
685  double ssim_c2_d = .03*.03*64*63*max_value*max_value;
686 
687  double vars = 64. * fss - 1. * fs1 * fs1 - 1. * fs2 * fs2;
688  double covar = 64. * fs12 - 1.*fs1 * fs2;
689  sample_ssim360 = (2. * fs1 * fs2 + ssim_c1_d) * (2. * covar + ssim_c2_d)
690  / ((1. * fs1 * fs1 + 1. * fs2 * fs2 + ssim_c1_d) * (1. * vars + ssim_c2_d));
691  } else {
692  static const int ssim_c1 = (int)(.01*.01*255*255*64 + .5);
693  static const int ssim_c2 = (int)(.03*.03*255*255*64*63 + .5);
694 
695  int vars = fss * 64 - fs1 * fs1 - fs2 * fs2;
696  int covar = fs12 * 64 - fs1 * fs2;
697  sample_ssim360 = (double)(2 * fs1 * fs2 + ssim_c1) * (double)(2 * covar + ssim_c2)
698  / ((double)(fs1 * fs1 + fs2 * fs2 + ssim_c1) * (double)(vars + ssim_c2));
699  }
700 
701  hist_index = (int)(sample_ssim360 * ((double)SSIM360_HIST_SIZE - .5));
702  hist_index = av_clip(hist_index, 0, SSIM360_HIST_SIZE - 1);
703 
704  norm_tape_pos = (y - 0.5f) / (vertical_block_count - 1.0f) - 0.5f;
705  // weight from an input heatmap if available, otherwise weight = 1.0
706  weight = get_heat(heatmaps, angular_resolution, norm_tape_pos);
707  ssim360_hist[hist_index] += weight;
708  *ssim360_hist_net += weight;
709 
710  ssim360 += (sample_ssim360 * weight);
711  sum_weight += weight;
712  }
713 
714  return ssim360 / sum_weight;
715 }
716 
717 static void compute_bilinear_map(SampleParams *p, BilinearMap *m, float x, float y)
718 {
719  float fixed_point_scale = (float)(1 << FIXED_POINT_PRECISION);
720 
721  // All operations in here will fit in the 22 bit mantissa of floating point,
722  // since the fixed point precision is well under 22 bits
723  float x_image = av_clipf(x * p->x_image_range, 0, p->x_image_range) + p->x_image_offset;
724  float y_image = av_clipf(y * p->y_image_range, 0, p->y_image_range) + p->y_image_offset;
725 
726  int x_floor = x_image;
727  int y_floor = y_image;
728  float x_diff = x_image - x_floor;
729  float y_diff = y_image - y_floor;
730 
731  int x_ceil = x_floor + (x_diff > 1e-6);
732  int y_ceil = y_floor + (y_diff > 1e-6);
733  float x_inv_diff = 1.0f - x_diff;
734  float y_inv_diff = 1.0f - y_diff;
735 
736  // Indices of the 4 samples from source frame
737  m->tli = x_floor + y_floor * p->stride;
738  m->tri = x_ceil + y_floor * p->stride;
739  m->bli = x_floor + y_ceil * p->stride;
740  m->bri = x_ceil + y_ceil * p->stride;
741 
742  // Scale to be applied to each of the 4 samples from source frame
743  m->tlf = x_inv_diff * y_inv_diff * fixed_point_scale;
744  m->trf = x_diff * y_inv_diff * fixed_point_scale;
745  m->blf = x_inv_diff * y_diff * fixed_point_scale;
746  m->brf = x_diff * y_diff * fixed_point_scale;
747 }
748 
749 static void get_equirect_map(float phi, float theta, float *x, float *y)
750 {
751  *x = 0.5f + theta / (2.0f * M_PI_F);
752  // y increases downwards
753  *y = 0.5f - phi / M_PI_F;
754 }
755 
756 static void get_barrel_map(float phi, float theta, float *x, float *y)
757 {
758  float abs_phi = FFABS(phi);
759 
760  if (abs_phi <= M_PI_4_F) {
761  // Equirect region
762  *x = 0.8f * (0.5f + theta / BARREL_THETA_RANGE);
763  // y increases downwards
764  *y = 0.5f - phi / BARREL_PHI_RANGE;
765  } else {
766  // Radial ratio on a unit circle = cot(abs_phi) / (expansion_cefficient).
767  // Using cos(abs_phi)/sin(abs_phi) explicitly to avoid division by zero
768  float radial_ratio = cosf(abs_phi) / (sinf(abs_phi) * DEFAULT_EXPANSION_COEF);
769  float circle_x = radial_ratio * sinf(theta);
770  float circle_y = radial_ratio * cosf(theta);
771  float offset_y = 0.25f;
772  if (phi < 0) {
773  // Bottom circle: theta increases clockwise, and front is upward
774  circle_y *= -1.0f;
775  offset_y += 0.5f;
776  }
777 
778  *x = 0.8f + 0.1f * (1.0f + circle_x);
779  *y = offset_y + 0.25f * circle_y;
780  }
781 }
782 
783 static void get_barrel_split_map(float phi, float theta, float expand_coef, float *x, float *y)
784 {
785  float abs_phi = FFABS(phi);
786 
787  // Front Face [-PI/2, PI/2] -> [0,1].
788  // Back Face [PI/2, PI] and [-PI, -PI/2] -> [1, 2]
789  float radian_pi_theta = theta / M_PI_F + 0.5f;
790  int vFace;
791 
792  if (radian_pi_theta < 0.0f)
793  radian_pi_theta += 2.0f;
794 
795  // Front face at top (= 0), back face at bottom (= 1).
796  vFace = radian_pi_theta >= 1.0f;
797 
798  if (abs_phi <= M_PI_4_F) {
799  // Equirect region
800  *x = 2.0f / 3.0f * (0.5f + (radian_pi_theta - vFace - 0.5f) / expand_coef);
801  // y increases downwards
802  *y = 0.25f + 0.5f * vFace - phi / (M_PI_F * expand_coef);
803  } else {
804  // Radial ratio on a unit circle = cot(abs_phi) / (expansion_cefficient).
805  // Using cos(abs_phi)/sin(abs_phi) explicitly to avoid division by zero
806  float radial_ratio = cosf(abs_phi) / (sinf(abs_phi) * expand_coef);
807  float circle_x = radial_ratio * sinf(theta);
808  float circle_y = radial_ratio * cosf(theta);
809  float offset_y = 0.25f;
810 
811  if (vFace == 1) {
812  // Back Face: Flip
813  circle_x *= -1.0f;
814  circle_y = (circle_y >= 0.0f) ? (1 - circle_y) : (-1 - circle_y);
815  offset_y += 0.5f;
816 
817  // Bottom circle: theta increases clockwise
818  if (phi < 0)
819  circle_y *= -1.0f;
820  } else {
821  // Front Face
822  // Bottom circle: theta increases clockwise
823  if (phi < 0)
824  circle_y *= -1.0f;
825  }
826 
827  *x = 2.0f / 3.0f + 0.5f / 3.0f * (1.0f + circle_x);
828  *y = offset_y + 0.25f * circle_y / expand_coef; // y direction of expand_coeff (margin)
829  }
830 }
831 
832 // Returns cube face, and provided face_x & face_y will range from [0, 1]
833 static int get_cubemap_face_map(float axis_vec_x, float axis_vec_y, float axis_vec_z, float *face_x, float *face_y)
834 {
835  // To check if phi, theta hits the top / bottom faces, we check the hit point of
836  // the axis vector on planes y = 1 and y = -1, and see if x & z are within [-1, 1]
837 
838  // 0.577 < 1 / sqrt(3), which is less than the smallest sin(phi) falling on top/bottom faces
839  // This angle check will save computation from unnecessarily checking the top/bottom faces
840  if (FFABS(axis_vec_y) > 0.577f) {
841  float x_hit = axis_vec_x / FFABS(axis_vec_y);
842  float z_hit = axis_vec_z / axis_vec_y;
843 
844  if (FFABS(x_hit) <= 1.f && FFABS(z_hit) <= 1.f) {
845  *face_x = x_hit;
846  // y increases downwards
847  *face_y = z_hit;
848  return axis_vec_y > 0 ? TOP : BOTTOM;
849  }
850  }
851 
852  // Check for left / right faces
853  if (FFABS(axis_vec_x) > 0.577f) {
854  float z_hit = -axis_vec_z / axis_vec_x;
855  float y_hit = axis_vec_y / FFABS(axis_vec_x);
856 
857  if (FFABS(z_hit) <= 1.f && FFABS(y_hit) <= 1.f) {
858  *face_x = z_hit;
859  // y increases downwards
860  *face_y = -y_hit;
861  return axis_vec_x > 0 ? RIGHT : LEFT;
862  }
863  }
864 
865  // Front / back faces
866  *face_x = axis_vec_x / axis_vec_z;
867  // y increases downwards
868  *face_y = -axis_vec_y / FFABS(axis_vec_z);
869 
870  return axis_vec_z > 0 ? FRONT : BACK;
871 }
872 
873 static void get_cubemap32_map(float phi, float theta, float *x, float *y)
874 {
875  // face_projection_map maps each cube face to an index representing the face on the projection
876  // The indices 0->5 for cubemap 32 goes as:
877  // [0, 1, 2] as row 1, left to right
878  // [3, 4, 5] as row 2, left to right
879  static const int face_projection_map[] = {
880  [RIGHT] = 0, [LEFT] = 1, [TOP] = 2,
881  [BOTTOM] = 3, [FRONT] = 4, [BACK] = 5,
882  };
883 
884  float axis_vec_x = cosf(phi) * sinf(theta);
885  float axis_vec_y = sinf(phi);
886  float axis_vec_z = cosf(phi) * cosf(theta);
887  float face_x = 0, face_y = 0;
888  int face_index = get_cubemap_face_map(axis_vec_x, axis_vec_y, axis_vec_z, &face_x, &face_y);
889 
890  float x_offset = 1.f / 3.f * (face_projection_map[face_index] % 3);
891  float y_offset = .5f * (face_projection_map[face_index] / 3);
892 
893  *x = x_offset + (face_x / DEFAULT_EXPANSION_COEF + 1.f) / 6.f;
894  *y = y_offset + (face_y / DEFAULT_EXPANSION_COEF + 1.f) / 4.f;
895 }
896 
897 static void get_rotated_cubemap_map(float phi, float theta, float expand_coef, float *x, float *y)
898 {
899  // face_projection_map maps each cube face to an index representing the face on the projection
900  // The indices 0->5 for rotated cubemap goes as:
901  // [0, 1] as row 1, left to right
902  // [2, 3] as row 2, left to right
903  // [4, 5] as row 3, left to right
904  static const int face_projection_map[] = {
905  [LEFT] = 0, [TOP] = 1,
906  [FRONT] = 2, [BACK] = 3,
907  [RIGHT] = 4, [BOTTOM] = 5,
908  };
909 
910  float axis_yaw_vec_x, axis_yaw_vec_y, axis_yaw_vec_z;
911  float axis_pitch_vec_z, axis_pitch_vec_y;
912  float x_offset, y_offset;
913  float face_x = 0, face_y = 0;
914  int face_index;
915 
916  // Unrotate the cube and fix the face map:
917  // First undo the 45 degree yaw
918  theta += M_PI_4_F;
919 
920  // Now we are looking at the middle of an edge. So convert to axis vector & undo the pitch
921  axis_yaw_vec_x = cosf(phi) * sinf(theta);
922  axis_yaw_vec_y = sinf(phi);
923  axis_yaw_vec_z = cosf(phi) * cosf(theta);
924 
925  // The pitch axis is along +x, and has value of -45 degree. So, only y and z components change
926  axis_pitch_vec_z = (axis_yaw_vec_z - axis_yaw_vec_y) / M_SQRT2_F;
927  axis_pitch_vec_y = (axis_yaw_vec_y + axis_yaw_vec_z) / M_SQRT2_F;
928 
929  face_index = get_cubemap_face_map(axis_yaw_vec_x, axis_pitch_vec_y, axis_pitch_vec_z, &face_x, &face_y);
930 
931  // Correct for the orientation of the axes on the faces
932  if (face_index == LEFT || face_index == FRONT || face_index == RIGHT) {
933  // x increases downwards & y increases towards left
934  float upright_y = face_y;
935  face_y = face_x;
936  face_x = -upright_y;
937  } else if (face_index == TOP || face_index == BOTTOM) {
938  // turn the face upside-down for top and bottom
939  face_x *= -1.f;
940  face_y *= -1.f;
941  }
942 
943  x_offset = .5f * (face_projection_map[face_index] & 1);
944  y_offset = 1.f / 3.f * (face_projection_map[face_index] >> 1);
945 
946  *x = x_offset + (face_x / expand_coef + 1.f) / 4.f;
947  *y = y_offset + (face_y / expand_coef + 1.f) / 6.f;
948 }
949 
950 static void get_projected_map(float phi, float theta, SampleParams *p, BilinearMap *m)
951 {
952  float x = 0, y = 0;
953  switch(p->projection) {
954 // TODO: Calculate for CDS
956  get_rotated_cubemap_map(phi, theta, p->expand_coef, &x, &y);
957  break;
959  get_cubemap32_map(phi, theta, &x, &y);
960  break;
961  case PROJECTION_BARREL:
962  get_barrel_map(phi, theta, &x, &y);
963  break;
965  get_barrel_split_map(phi, theta, p->expand_coef, &x, &y);
966  break;
967  // Assume PROJECTION_EQUIRECT as the default
968  case PROJECTION_EQUIRECT:
969  default:
970  get_equirect_map(phi, theta, &x, &y);
971  break;
972  }
973  compute_bilinear_map(p, m, x, y);
974 }
975 
976 static int tape_supports_projection(int projection)
977 {
978  switch(projection) {
981  case PROJECTION_BARREL:
983  case PROJECTION_EQUIRECT:
984  return 1;
985  default:
986  return 0;
987  }
988 }
989 
990 static float get_tape_angular_resolution(int projection, float expand_coef, int image_width, int image_height)
991 {
992  // NOTE: The angular resolution of a projected sphere is defined as
993  // the maximum possible horizontal angle of a pixel on the equator.
994  // We apply an intentional bias to the horizon as opposed to the meridian,
995  // since the view direction of most content is rarely closer to the poles
996 
997  switch(projection) {
998 // TODO: Calculate for CDS
1000  // Approximating atanf(pixel_width / (half_edge_width * sqrt2)) = pixel_width / (half_face_width * sqrt2)
1001  return expand_coef / (M_SQRT2_F * image_width / 4.f);
1002  case PROJECTION_CUBEMAP32:
1003  // Approximating atanf(pixel_width / half_face_width) = pixel_width / half_face_width
1004  return DEFAULT_EXPANSION_COEF / (image_width / 6.f);
1005  case PROJECTION_BARREL:
1006  return FFMAX(BARREL_THETA_RANGE / (0.8f * image_width), BARREL_PHI_RANGE / image_height);
1008  return FFMAX((expand_coef * M_PI_F) / (2.0f / 3.0f * image_width),
1009  expand_coef * M_PI_2_F / (image_height / 2.0f));
1010  // Assume PROJECTION_EQUIRECT as the default
1011  case PROJECTION_EQUIRECT:
1012  default:
1013  return FFMAX(2.0f * M_PI_F / image_width, M_PI_F / image_height);
1014  }
1015 }
1016 
1017 static int
1019  int plane, int eye,
1020  SampleParams *ref_sample_params,
1021  SampleParams *main_sample_params)
1022 {
1023  int ref_image_width = ref_sample_params->x_image_range + 1;
1024  int ref_image_height = ref_sample_params->y_image_range + 1;
1025 
1026  float angular_resolution =
1027  get_tape_angular_resolution(s->ref_projection, 1.f + s->ref_pad,
1028  ref_image_width, ref_image_height);
1029 
1030  float conversion_factor = M_PI_2_F / (angular_resolution * angular_resolution);
1031  float start_phi = -M_PI_2_F + 4.0f * angular_resolution;
1032  float start_x = conversion_factor * sinf(start_phi);
1033  float end_phi = M_PI_2_F - 3.0f * angular_resolution;
1034  float end_x = conversion_factor * sinf(end_phi);
1035  float x_range = end_x - start_x;
1036 
1037  // Ensure tape length is a multiple of 4, for full SSIM block coverage
1038  int tape_length = s->tape_length[plane] = ((int)ROUNDED_DIV(x_range, 4)) << 2;
1039 
1040  s->ref_tape_map[plane][eye] = av_malloc_array(tape_length * 8, sizeof(BilinearMap));
1041  s->main_tape_map[plane][eye] = av_malloc_array(tape_length * 8, sizeof(BilinearMap));
1042  if (!s->ref_tape_map[plane][eye] || !s->main_tape_map[plane][eye])
1043  return AVERROR(ENOMEM);
1044 
1045  s->angular_resolution[plane][eye] = angular_resolution;
1046 
1047  // For easy memory access, we navigate the tape lengthwise on y
1048  for (int y_index = 0; y_index < tape_length; y_index ++) {
1049  int y_stride = y_index << 3;
1050 
1051  float x = start_x + x_range * (y_index / (tape_length - 1.0f));
1052  // phi will be in range [-pi/2, pi/2]
1053  float mid_phi = asinf(x / conversion_factor);
1054 
1055  float theta = mid_phi * M_PI_2_F / angular_resolution;
1057 
1058  for (int x_index = 0; x_index < 8; x_index ++) {
1059  float phi = mid_phi + angular_resolution * (3.0f - x_index);
1060  int tape_index = y_stride + x_index;
1061  get_projected_map(phi, theta, ref_sample_params, &s->ref_tape_map [plane][eye][tape_index]);
1062  get_projected_map(phi, theta, main_sample_params, &s->main_tape_map[plane][eye][tape_index]);
1063  }
1064  }
1065 
1066  return 0;
1067 }
1068 
1070 {
1071  // A tape is a long segment with 8 pixels thickness, with the angular center at the middle (below 4th pixel).
1072  // When it takes a full loop around a sphere, it will overlap the starting point at half the width from above.
1073  int ref_stereo_format = s->ref_stereo_format;
1074  int main_stereo_format = s->main_stereo_format;
1075  int are_both_stereo = (main_stereo_format != STEREO_FORMAT_MONO) && (ref_stereo_format != STEREO_FORMAT_MONO);
1076  int min_eye_count = 1 + are_both_stereo;
1077  int ret;
1078 
1079  for (int i = 0; i < s->nb_components; i ++) {
1080  int ref_width = s->ref_planewidth[i];
1081  int ref_height = s->ref_planeheight[i];
1082  int main_width = s->main_planewidth[i];
1083  int main_height = s->main_planeheight[i];
1084 
1085  int is_ref_LR = (ref_stereo_format == STEREO_FORMAT_LR);
1086  int is_ref_TB = (ref_stereo_format == STEREO_FORMAT_TB);
1087  int is_main_LR = (main_stereo_format == STEREO_FORMAT_LR);
1088  int is_main_TB = (main_stereo_format == STEREO_FORMAT_TB);
1089 
1090  int ref_image_width = is_ref_LR ? ref_width >> 1 : ref_width;
1091  int ref_image_height = is_ref_TB ? ref_height >> 1 : ref_height;
1092  int main_image_width = is_main_LR ? main_width >> 1 : main_width;
1093  int main_image_height = is_main_TB ? main_height >> 1 : main_height;
1094 
1095  for (int eye = 0; eye < min_eye_count; eye ++) {
1096  SampleParams ref_sample_params = {
1097  .stride = ref->linesize[i],
1098  .planewidth = ref_width,
1099  .planeheight = ref_height,
1100  .x_image_range = ref_image_width - 1,
1101  .y_image_range = ref_image_height - 1,
1102  .x_image_offset = is_ref_LR * eye * ref_image_width,
1103  .y_image_offset = is_ref_TB * eye * ref_image_height,
1104  .projection = s->ref_projection,
1105  .expand_coef = 1.f + s->ref_pad,
1106  };
1107 
1108  SampleParams main_sample_params = {
1109  .stride = main->linesize[i],
1110  .planewidth = main_width,
1111  .planeheight = main_height,
1112  .x_image_range = main_image_width - 1,
1113  .y_image_range = main_image_height - 1,
1114  .x_image_offset = is_main_LR * eye * main_image_width,
1115  .y_image_offset = is_main_TB * eye * main_image_height,
1116  .projection = s->main_projection,
1117  .expand_coef = 1.f + s->main_pad,
1118  };
1119 
1120  ret = generate_eye_tape_map(s, i, eye, &ref_sample_params, &main_sample_params);
1121  if (ret < 0)
1122  return ret;
1123  }
1124  }
1125 
1126  return 0;
1127 }
1128 
1130 {
1131  AVFilterContext *ctx = fs->parent;
1132  SSIM360Context *s = ctx->priv;
1133  AVFrame *master, *ref;
1134  AVDictionary **metadata;
1135  double c[4], ssim360v = 0.0, ssim360p50 = 0.0;
1136  int i, ret;
1137  int need_frame_skip = s->nb_net_frames % (s->frame_skip_ratio + 1);
1138  HeatmapList* h_ptr = NULL;
1139 
1141  if (ret < 0)
1142  return ret;
1143 
1144  s->nb_net_frames++;
1145 
1146  if (need_frame_skip)
1147  return ff_filter_frame(ctx->outputs[0], master);
1148 
1149  metadata = &master->metadata;
1150 
1151  if (s->use_tape && !s->tape_length[0]) {
1153  if (ret < 0)
1154  return ret;
1155  }
1156 
1157  for (i = 0; i < s->nb_components; i++) {
1158  if (s->use_tape) {
1159  c[i] = ssim360_tape(master->data[i], s->main_tape_map[i][0],
1160  ref->data[i], s->ref_tape_map [i][0],
1161  s->tape_length[i], s->max, s->temp,
1162  s->ssim360_hist[i], &s->ssim360_hist_net[i],
1163  s->angular_resolution[i][0], s->heatmaps);
1164 
1165  if (s->ref_tape_map[i][1]) {
1166  c[i] += ssim360_tape(master->data[i], s->main_tape_map[i][1],
1167  ref->data[i], s->ref_tape_map[i][1],
1168  s->tape_length[i], s->max, s->temp,
1169  s->ssim360_hist[i], &s->ssim360_hist_net[i],
1170  s->angular_resolution[i][1], s->heatmaps);
1171  c[i] /= 2.f;
1172  }
1173  } else {
1174  c[i] = s->ssim360_plane(master->data[i], master->linesize[i],
1175  ref->data[i], ref->linesize[i],
1176  s->ref_planewidth[i], s->ref_planeheight[i],
1177  s->temp, s->max, s->density);
1178  }
1179 
1180  s->ssim360[i] += c[i];
1181  ssim360v += s->coefs[i] * c[i];
1182  }
1183 
1184  s->nb_ssim_frames++;
1185  if (s->heatmaps) {
1186  map_uninit(&s->heatmaps->map);
1187  h_ptr = s->heatmaps;
1188  s->heatmaps = s->heatmaps->next;
1189  av_freep(&h_ptr);
1190  }
1191  s->ssim360_total += ssim360v;
1192 
1193  // Record percentiles from histogram and attach metadata when using tape
1194  if (s->use_tape) {
1195  int i, p, hist_indices[4];
1196  double hist_weight[4];
1197 
1198  for (i = 0; i < s->nb_components; i++) {
1199  hist_indices[i] = SSIM360_HIST_SIZE - 1;
1200  hist_weight[i] = 0;
1201  }
1202 
1203  for (p = 0; PERCENTILE_LIST[p] >= 0.0; p ++) {
1204  for (i = 0; i < s->nb_components; i++) {
1205  double target_weight, ssim360p;
1206 
1207  // Target weight = total number of samples above the specified percentile
1208  target_weight = (1. - PERCENTILE_LIST[p]) * s->ssim360_hist_net[i];
1209  target_weight = FFMAX(target_weight, 1);
1210  while(hist_indices[i] >= 0 && hist_weight[i] < target_weight) {
1211  hist_weight[i] += s->ssim360_hist[i][hist_indices[i]];
1212  hist_indices[i] --;
1213  }
1214 
1215  ssim360p = (double)(hist_indices[i] + 1) / (double)(SSIM360_HIST_SIZE - 1);
1216  if (PERCENTILE_LIST[p] == 0.5)
1217  ssim360p50 += s->coefs[i] * ssim360p;
1218  s->ssim360_percentile_sum[i][p] += ssim360p;
1219  }
1220  }
1221 
1222  for (i = 0; i < s->nb_components; i++) {
1223  memset(s->ssim360_hist[i], 0, SSIM360_HIST_SIZE * sizeof(double));
1224  s->ssim360_hist_net[i] = 0;
1225  }
1226 
1227  for (i = 0; i < s->nb_components; i++) {
1228  int cidx = s->is_rgb ? s->rgba_map[i] : i;
1229  set_meta(metadata, "lavfi.ssim360.", s->comps[i], c[cidx]);
1230  }
1231 
1232  // Use p50 as the aggregated value
1233  set_meta(metadata, "lavfi.ssim360.All", 0, ssim360p50);
1234  set_meta(metadata, "lavfi.ssim360.dB", 0, ssim360_db(ssim360p50, 1.0));
1235 
1236  if (s->stats_file) {
1237  fprintf(s->stats_file, "n:%"PRId64" ", s->nb_ssim_frames);
1238 
1239  for (i = 0; i < s->nb_components; i++) {
1240  int cidx = s->is_rgb ? s->rgba_map[i] : i;
1241  fprintf(s->stats_file, "%c:%f ", s->comps[i], c[cidx]);
1242  }
1243 
1244  fprintf(s->stats_file, "All:%f (%f)\n", ssim360p50, ssim360_db(ssim360p50, 1.0));
1245  }
1246  }
1247 
1248  return ff_filter_frame(ctx->outputs[0], master);
1249 }
1250 
1251 static int parse_heatmaps(void *logctx, HeatmapList **proot,
1252  const char *data, int w, int h)
1253 {
1254  HeatmapList *root = NULL;
1255  HeatmapList **next = &root;
1256 
1257  int ret;
1258 
1259  // skip video id line
1260  data = strchr(data, '\n');
1261  if (!data) {
1262  av_log(logctx, AV_LOG_ERROR, "Invalid heatmap syntax\n");
1263  return AVERROR(EINVAL);
1264  }
1265  data++;
1266 
1267  while (*data) {
1268  HeatmapList *cur;
1269  char *line = av_get_token(&data, "\n");
1270  char *saveptr, *val;
1271  int i;
1272 
1273  if (!line) {
1274  ret = AVERROR(ENOMEM);
1275  goto fail;
1276  }
1277  if (!line) {
1278  av_freep(&line);
1279  break;
1280  }
1281 
1282  // first value is frame id
1283  av_strtok(line, ",", &saveptr);
1284 
1285  ret = map_alloc(next, w, h);
1286  if (ret < 0)
1287  goto line_fail;
1288 
1289  cur = *next;
1290  next = &cur->next;
1291 
1292  i = 0;
1293  while ((val = av_strtok(NULL, ",", &saveptr))) {
1294  if (i >= w * h) {
1295  av_log(logctx, AV_LOG_ERROR, "Too many entries in a heat map\n");
1296  ret = AVERROR(EINVAL);
1297  goto line_fail;
1298  }
1299 
1300  cur->map.value[i++] = atof(val);
1301  }
1302 
1303 line_fail:
1304  av_freep(&line);
1305  if (ret < 0)
1306  goto fail;
1307  }
1308 
1309  *proot = root;
1310 
1311  return 0;
1312 fail:
1313  map_list_free(&root);
1314  return ret;
1315 }
1316 
1318 {
1319  SSIM360Context *s = ctx->priv;
1320  int err;
1321 
1322  if (s->stats_file_str) {
1323  if (!strcmp(s->stats_file_str, "-")) {
1324  s->stats_file = stdout;
1325  } else {
1326  s->stats_file = avpriv_fopen_utf8(s->stats_file_str, "w");
1327  if (!s->stats_file) {
1328  char buf[128];
1329 
1330  err = AVERROR(errno);
1331  av_strerror(err, buf, sizeof(buf));
1332  av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n",
1333  s->stats_file_str, buf);
1334  return err;
1335  }
1336  }
1337  }
1338 
1339  if (s->use_tape && s->heatmap_str) {
1340  err = parse_heatmaps(ctx, &s->heatmaps, s->heatmap_str,
1341  s->default_heatmap_w, s->default_heatmap_h);
1342  if (err < 0)
1343  return err;
1344  }
1345 
1346  s->fs.on_event = do_ssim360;
1347  return 0;
1348 }
1349 
1351 {
1353  AVFilterContext *ctx = inlink->dst;
1354  SSIM360Context *s = ctx->priv;
1355 
1356  s->main_planeheight[0] = inlink->h;
1357  s->main_planeheight[3] = inlink->h;
1358  s->main_planeheight[1] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
1359  s->main_planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
1360 
1361  s->main_planewidth[0] = inlink->w;
1362  s->main_planewidth[3] = inlink->w;
1363  s->main_planewidth[1] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
1364  s->main_planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
1365 
1366  // If main projection is unindentified, assume it is same as reference
1367  if (s->main_projection == PROJECTION_N)
1368  s->main_projection = s->ref_projection;
1369 
1370  // If main stereo format is unindentified, assume it is same as reference
1371  if (s->main_stereo_format == STEREO_FORMAT_N)
1372  s->main_stereo_format = s->ref_stereo_format;
1373 
1374  return 0;
1375 }
1376 
1377 static int generate_density_map(SSIM360Context *s, int w, int h)
1378 {
1379  double d, r_square, cos_square;
1380  int ow, oh, ret;
1381 
1382  ret = map_init(&s->density, w, h);
1383  if (ret < 0)
1384  return ret;
1385 
1386  switch (s->ref_stereo_format) {
1387  case STEREO_FORMAT_TB:
1388  h >>= 1;
1389  break;
1390  case STEREO_FORMAT_LR:
1391  w >>= 1;
1392  break;
1393  }
1394 
1395  switch (s->ref_projection) {
1396  case PROJECTION_EQUIRECT:
1397  for (int i = 0; i < h; i++) {
1398  d = cos(((0.5 + i) / h - 0.5) * M_PI);
1399  for (int j = 0; j < w; j++)
1400  s->density.value[i * w + j] = d;
1401  }
1402  break;
1403  case PROJECTION_CUBEMAP32:
1404  // for one quater of a face
1405  for (int i = 0; i < h / 4; i++) {
1406  for (int j = 0; j < w / 6; j++) {
1407  // r = normalized distance to the face center
1408  r_square =
1409  (0.5 + i) / (h / 2) * (0.5 + i) / (h / 2) +
1410  (0.5 + j) / (w / 3) * (0.5 + j) / (w / 3);
1412  cos_square = 0.25 / (r_square + 0.25);
1413  d = pow(cos_square, 1.5);
1414 
1415  for (int face = 0; face < 6; face++) {
1416  // center of a face
1417  switch (face) {
1418  case 0:
1419  oh = h / 4;
1420  ow = w / 6;
1421  break;
1422  case 1:
1423  oh = h / 4;
1424  ow = w / 6 + w / 3;
1425  break;
1426  case 2:
1427  oh = h / 4;
1428  ow = w / 6 + 2 * w / 3;
1429  break;
1430  case 3:
1431  oh = h / 4 + h / 2;
1432  ow = w / 6;
1433  break;
1434  case 4:
1435  oh = h / 4 + h / 2;
1436  ow = w / 6 + w / 3;
1437  break;
1438  case 5:
1439  oh = h / 4 + h / 2;
1440  ow = w / 6 + 2 * w / 3;
1441  break;
1442  }
1443  s->density.value[(oh - 1 - i) * w + ow - 1 - j] = d;
1444  s->density.value[(oh - 1 - i) * w + ow + j] = d;
1445  s->density.value[(oh + i) * w + ow - 1 - j] = d;
1446  s->density.value[(oh + i) * w + ow + j] = d;
1447  }
1448  }
1449  }
1450  break;
1451  case PROJECTION_CUBEMAP23:
1452  // for one quater of a face
1453  for (int i = 0; i < h / 6; i++) {
1454  for (int j = 0; j < w / 4; j++) {
1455  // r = normalized distance to the face center
1456  r_square =
1457  (0.5 + i) / (h / 3) * (0.5 + i) / (h / 3) +
1458  (0.5 + j) / (w / 2) * (0.5 + j) / (w / 2);
1459  r_square /= (1.f + s->ref_pad) * (1.f + s->ref_pad);
1460  cos_square = 0.25 / (r_square + 0.25);
1461  d = pow(cos_square, 1.5);
1462 
1463  for (int face = 0; face < 6; face++) {
1464  // center of a face
1465  switch (face) {
1466  case 0:
1467  ow = w / 4;
1468  oh = h / 6;
1469  break;
1470  case 1:
1471  ow = w / 4;
1472  oh = h / 6 + h / 3;
1473  break;
1474  case 2:
1475  ow = w / 4;
1476  oh = h / 6 + 2 * h / 3;
1477  break;
1478  case 3:
1479  ow = w / 4 + w / 2;
1480  oh = h / 6;
1481  break;
1482  case 4:
1483  ow = w / 4 + w / 2;
1484  oh = h / 6 + h / 3;
1485  break;
1486  case 5:
1487  ow = w / 4 + w / 2;
1488  oh = h / 6 + 2 * h / 3;
1489  break;
1490  }
1491  s->density.value[(oh - 1 - i) * w + ow - 1 - j] = d;
1492  s->density.value[(oh - 1 - i) * w + ow + j] = d;
1493  s->density.value[(oh + i) * w + ow - 1 - j] = d;
1494  s->density.value[(oh + i) * w + ow + j] = d;
1495  }
1496  }
1497  }
1498  break;
1499  case PROJECTION_BARREL:
1500  // side face
1501  for (int i = 0; i < h; i++) {
1502  for (int j = 0; j < w * 4 / 5; j++) {
1503  d = cos(((0.5 + i) / h - 0.5) * DEFAULT_EXPANSION_COEF * M_PI_2);
1504  s->density.value[i * w + j] = d * d * d;
1505  }
1506  }
1507  // top and bottom
1508  for (int i = 0; i < h; i++) {
1509  for (int j = w * 4 / 5; j < w; j++) {
1510  double dx = DEFAULT_EXPANSION_COEF * (0.5 + j - w * 0.90) / (w * 0.10);
1511  double dx_squared = dx * dx;
1512 
1513  double top_dy = DEFAULT_EXPANSION_COEF * (0.5 + i - h * 0.25) / (h * 0.25);
1514  double top_dy_squared = top_dy * top_dy;
1515 
1516  double bottom_dy = DEFAULT_EXPANSION_COEF * (0.5 + i - h * 0.75) / (h * 0.25);
1517  double bottom_dy_squared = bottom_dy * bottom_dy;
1518 
1519  // normalized distance to the circle center
1520  r_square = (i < h / 2 ? top_dy_squared : bottom_dy_squared) + dx_squared;
1521  if (r_square > 1.0)
1522  continue;
1523 
1524  cos_square = 1.0 / (r_square + 1.0);
1525  d = pow(cos_square, 1.5);
1526  s->density.value[i * w + j] = d;
1527  }
1528  }
1529  break;
1530  default:
1531  // TODO: SSIM360_v1
1532  for (int i = 0; i < h; i++) {
1533  for (int j = 0; j < w; j++)
1534  s->density.value[i * w + j] = 0;
1535  }
1536  }
1537 
1538  switch (s->ref_stereo_format) {
1539  case STEREO_FORMAT_TB:
1540  for (int i = 0; i < h; i++) {
1541  for (int j = 0; j < w; j++)
1542  s->density.value[(i + h) * w + j] = s->density.value[i * w + j];
1543  }
1544  break;
1545  case STEREO_FORMAT_LR:
1546  for (int i = 0; i < h; i++) {
1547  for (int j = 0; j < w; j++)
1548  s->density.value[i * w + j + w] = s->density.value[i * w + j];
1549  }
1550  }
1551 
1552  return 0;
1553 }
1554 
1556 {
1558  AVFilterContext *ctx = inlink->dst;
1559  SSIM360Context *s = ctx->priv;
1560  int sum = 0;
1561 
1562  s->nb_components = desc->nb_components;
1563 
1564  s->ref_planeheight[0] = inlink->h;
1565  s->ref_planeheight[3] = inlink->h;
1566  s->ref_planeheight[1] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
1567  s->ref_planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
1568 
1569  s->ref_planewidth[0] = inlink->w;
1570  s->ref_planewidth[3] = inlink->w;
1571  s->ref_planewidth[1] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
1572  s->ref_planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
1573 
1574  s->is_rgb = ff_fill_rgba_map(s->rgba_map, inlink->format) >= 0;
1575  s->comps[0] = s->is_rgb ? 'R' : 'Y';
1576  s->comps[1] = s->is_rgb ? 'G' : 'U';
1577  s->comps[2] = s->is_rgb ? 'B' : 'V';
1578  s->comps[3] = 'A';
1579 
1580  // If chroma computation is disabled, and the format is YUV, skip U & V channels
1581  if (!s->is_rgb && !s->compute_chroma)
1582  s->nb_components = 1;
1583 
1584  s->max = (1 << desc->comp[0].depth) - 1;
1585 
1586  s->ssim360_plane = desc->comp[0].depth > 8 ? ssim360_plane_16bit : ssim360_plane_8bit;
1587 
1588  for (int i = 0; i < s->nb_components; i++)
1589  sum += s->ref_planeheight[i] * s->ref_planewidth[i];
1590  for (int i = 0; i < s->nb_components; i++)
1591  s->coefs[i] = (double) s->ref_planeheight[i] * s->ref_planewidth[i] / sum;
1592 
1593  return 0;
1594 }
1595 
1596 static int config_output(AVFilterLink *outlink)
1597 {
1598  AVFilterContext *ctx = outlink->src;
1599  SSIM360Context *s = ctx->priv;
1600  AVFilterLink *mainlink = ctx->inputs[0];
1601  AVFilterLink *reflink = ctx->inputs[0];
1603  int ret;
1604 
1605  // Use tape algorithm if any of frame sizes, projections or stereo format are not equal
1606  if (ctx->inputs[0]->w != ctx->inputs[1]->w || ctx->inputs[0]->h != ctx->inputs[1]->h ||
1607  s->ref_projection != s->main_projection || s->ref_stereo_format != s->main_stereo_format)
1608  s->use_tape = 1;
1609 
1610  // Finally, if we have decided to / forced to use tape, check if tape supports both input and output projection
1611  if (s->use_tape &&
1612  !(tape_supports_projection(s->main_projection) &&
1613  tape_supports_projection(s->ref_projection))) {
1614  av_log(ctx, AV_LOG_ERROR, "Projection is unsupported for the tape based algorithm\n");
1615  return AVERROR(EINVAL);
1616  }
1617 
1618  if (s->use_tape) {
1619  // s->temp will be allocated for the tape width = 8. The tape is long downwards
1620  s->temp = av_malloc_array((2 * 8 + 12), sizeof(*s->temp));
1621  if (!s->temp)
1622  return AVERROR(ENOMEM);
1623 
1624  memset(s->ssim360_percentile_sum, 0, sizeof(s->ssim360_percentile_sum));
1625 
1626  for (int i = 0; i < s->nb_components; i++) {
1627  s->ssim360_hist[i] = av_calloc(SSIM360_HIST_SIZE, sizeof(*s->ssim360_hist));
1628  if (!s->ssim360_hist[i])
1629  return AVERROR(ENOMEM);
1630  }
1631  } else {
1632  s->temp = av_malloc_array((2 * reflink->w + 12), sizeof(*s->temp) * (1 + (desc->comp[0].depth > 8)));
1633  if (!s->temp)
1634  return AVERROR(ENOMEM);
1635 
1636  if (!s->density.value) {
1637  ret = generate_density_map(s, reflink->w, reflink->h);
1638  if (ret < 0)
1639  return ret;
1640  }
1641  }
1642 
1644  if (ret < 0)
1645  return ret;
1646 
1647  outlink->w = mainlink->w;
1648  outlink->h = mainlink->h;
1649  outlink->time_base = mainlink->time_base;
1650  outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
1651  outlink->frame_rate = mainlink->frame_rate;
1652 
1653  s->fs.opt_shortest = 1;
1654  s->fs.opt_repeatlast = 1;
1655 
1656  ret = ff_framesync_configure(&s->fs);
1657  if (ret < 0)
1658  return ret;
1659 
1660  return 0;
1661 }
1662 
1664 {
1665  SSIM360Context *s = ctx->priv;
1666  return ff_framesync_activate(&s->fs);
1667 }
1668 
1670 {
1671  SSIM360Context *s = ctx->priv;
1672 
1673  if (s->nb_ssim_frames > 0) {
1674  char buf[256];
1675  buf[0] = 0;
1676  // Log average SSIM360 values
1677  for (int i = 0; i < s->nb_components; i++) {
1678  int c = s->is_rgb ? s->rgba_map[i] : i;
1679  av_strlcatf(buf, sizeof(buf), " %c:%f (%f)", s->comps[i], s->ssim360[c] / s->nb_ssim_frames,
1680  ssim360_db(s->ssim360[c], s->nb_ssim_frames));
1681  }
1682  av_log(ctx, AV_LOG_INFO, "SSIM360%s All:%f (%f)\n", buf,
1683  s->ssim360_total / s->nb_ssim_frames, ssim360_db(s->ssim360_total, s->nb_ssim_frames));
1684 
1685  // Log percentiles from histogram when using tape
1686  if (s->use_tape) {
1687  for (int p = 0; PERCENTILE_LIST[p] >= 0.0; p++) {
1688  buf[0] = 0;
1689  for (int i = 0; i < s->nb_components; i++) {
1690  int c = s->is_rgb ? s->rgba_map[i] : i;
1691  double ssim360p = s->ssim360_percentile_sum[i][p] / (double)(s->nb_ssim_frames);
1692  av_strlcatf(buf, sizeof(buf), " %c:%f (%f)", s->comps[c], ssim360p, ssim360_db(ssim360p, 1));
1693  }
1694  av_log(ctx, AV_LOG_INFO, "SSIM360_p%d%s\n", (int)(PERCENTILE_LIST[p] * 100.), buf);
1695  }
1696  }
1697  }
1698 
1699  // free density map
1700  map_uninit(&s->density);
1701 
1702  map_list_free(&s->heatmaps);
1703 
1704  for (int i = 0; i < s->nb_components; i++) {
1705  for (int eye = 0; eye < 2; eye++) {
1706  av_freep(&s->ref_tape_map[i][eye]);
1707  av_freep(&s->main_tape_map[i][eye]);
1708  }
1709  av_freep(&s->ssim360_hist[i]);
1710  }
1711 
1712  ff_framesync_uninit(&s->fs);
1713 
1714  if (s->stats_file && s->stats_file != stdout)
1715  fclose(s->stats_file);
1716 
1717  av_freep(&s->temp);
1718 }
1719 
1720 #define PF(suf) AV_PIX_FMT_YUV420##suf, AV_PIX_FMT_YUV422##suf, AV_PIX_FMT_YUV444##suf, AV_PIX_FMT_GBR##suf
1721 static const enum AVPixelFormat ssim360_pixfmts[] = {
1728  PF(P9), PF(P10), PF(P12), PF(P14), PF(P16),
1730 };
1731 #undef PF
1732 
1733 static const AVFilterPad ssim360_inputs[] = {
1734  {
1735  .name = "main",
1736  .type = AVMEDIA_TYPE_VIDEO,
1737  .config_props = config_input_main,
1738  },
1739  {
1740  .name = "reference",
1741  .type = AVMEDIA_TYPE_VIDEO,
1742  .config_props = config_input_ref,
1743  },
1744 };
1745 
1746 static const AVFilterPad ssim360_outputs[] = {
1747  {
1748  .name = "default",
1749  .type = AVMEDIA_TYPE_VIDEO,
1750  .config_props = config_output,
1751  },
1752 };
1753 
1755  .name = "ssim360",
1756  .description = NULL_IF_CONFIG_SMALL("Calculate the SSIM between two 360 video streams."),
1757  .preinit = ssim360_framesync_preinit,
1758  .init = init,
1759  .uninit = uninit,
1760  .activate = activate,
1761  .priv_size = sizeof(SSIM360Context),
1762  .priv_class = &ssim360_class,
1766 };
tape_supports_projection
static int tape_supports_projection(int projection)
Definition: vf_ssim360.c:976
SSIM360Context::temp
int * temp
Definition: vf_ssim360.c:166
HeatmapList
Definition: vf_ssim360.c:111
map_uninit
static void map_uninit(Map2D *map)
Definition: vf_ssim360.c:292
map_init
static int map_init(Map2D *map, int w, int h)
Definition: vf_ssim360.c:297
ff_framesync_configure
int ff_framesync_configure(FFFrameSync *fs)
Configure a frame sync structure.
Definition: framesync.c:134
AVPixelFormat
AVPixelFormat
Pixel format.
Definition: pixfmt.h:64
SampleParams::expand_coef
float expand_coef
Definition: vf_ssim360.c:125
M_PI_4_F
#define M_PI_4_F
Definition: vf_ssim360.c:70
av_clip
#define av_clip
Definition: common.h:95
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
opt.h
SSIM360Context::is_rgb
int is_rgb
Definition: vf_ssim360.c:160
SSIM360Context::nb_ssim_frames
uint64_t nb_ssim_frames
Definition: vf_ssim360.c:167
ff_framesync_uninit
void ff_framesync_uninit(FFFrameSync *fs)
Free all memory currently allocated.
Definition: framesync.c:304
comp
static void comp(unsigned char *dst, ptrdiff_t dst_stride, unsigned char *src, ptrdiff_t src_stride, int add)
Definition: eamad.c:80
ff_filter_frame
int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
Send a frame of data to the next filter.
Definition: avfilter.c:969
av_pix_fmt_desc_get
const AVPixFmtDescriptor * av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt)
Definition: pixdesc.c:2888
SSIM360Context::ref_projection
int ref_projection
Definition: vf_ssim360.c:175
get_projected_map
static void get_projected_map(float phi, float theta, SampleParams *p, BilinearMap *m)
Definition: vf_ssim360.c:950
FILTER_PIXFMTS_ARRAY
#define FILTER_PIXFMTS_ARRAY(array)
Definition: internal.h:174
SSIM360Context::heatmaps
HeatmapList * heatmaps
Definition: vf_ssim360.c:187
STEREO_FORMAT_MONO
@ STEREO_FORMAT_MONO
Definition: vf_ssim360.c:93
compute_bilinear_map
static void compute_bilinear_map(SampleParams *p, BilinearMap *m, float x, float y)
Definition: vf_ssim360.c:717
inlink
The exact code depends on how similar the blocks are and how related they are to the and needs to apply these operations to the correct inlink or outlink if there are several Macros are available to factor that when no extra processing is inlink
Definition: filter_design.txt:212
SSIM360Context::comps
char comps[4]
Definition: vf_ssim360.c:154
AVFrame
This structure describes decoded (raw) audio or video data.
Definition: frame.h:330
HeatmapList::next
struct HeatmapList * next
Definition: vf_ssim360.c:113
pixdesc.h
FRONT
#define FRONT
Definition: vf_ssim360.c:62
w
uint8_t w
Definition: llviddspenc.c:38
M_PI_2
#define M_PI_2
Definition: mathematics.h:55
SSIM360Context::coefs
double coefs[4]
Definition: vf_ssim360.c:153
AVOption
AVOption.
Definition: opt.h:251
SSIM360Context::main_projection
int main_projection
Definition: vf_ssim360.c:176
b
#define b
Definition: input.c:41
ssim360_plane_8bit
static double ssim360_plane_8bit(uint8_t *main, int main_stride, uint8_t *ref, int ref_stride, int width, int height, void *temp, int max, Map2D density)
Definition: vf_ssim360.c:511
SampleParams
Definition: vf_ssim360.c:116
data
const char data[16]
Definition: mxf.c:146
ssim360_db
static double ssim360_db(double ssim360, double weight)
Definition: vf_ssim360.c:542
SSIM360Context::ref_pad
float ref_pad
Definition: vf_ssim360.c:179
SSIM360Context::main_stereo_format
int main_stereo_format
Definition: vf_ssim360.c:178
ff_vf_ssim360
const AVFilter ff_vf_ssim360
Definition: vf_ssim360.c:1754
Map2D::w
int w
Definition: vf_ssim360.c:107
AV_PIX_FMT_YUV440P
@ AV_PIX_FMT_YUV440P
planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples)
Definition: pixfmt.h:99
max
#define max(a, b)
Definition: cuda_runtime.h:33
SampleParams::y_image_offset
int y_image_offset
Definition: vf_ssim360.c:121
AVDictionary
Definition: dict.c:32
FFMAX
#define FFMAX(a, b)
Definition: macros.h:47
AVFilter::name
const char * name
Filter name.
Definition: avfilter.h:165
get_tape_angular_resolution
static float get_tape_angular_resolution(int projection, float expand_coef, int image_width, int image_height)
Definition: vf_ssim360.c:990
FFFrameSync
Frame sync structure.
Definition: framesync.h:168
SSIM360Context::ssim360_total
double ssim360_total
Definition: vf_ssim360.c:169
video.h
av_strlcatf
size_t av_strlcatf(char *dst, size_t size, const char *fmt,...)
Definition: avstring.c:104
ssim360_inputs
static const AVFilterPad ssim360_inputs[]
Definition: vf_ssim360.c:1733
get_cubemap_face_map
static int get_cubemap_face_map(float axis_vec_x, float axis_vec_y, float axis_vec_z, float *face_x, float *face_y)
Definition: vf_ssim360.c:833
get_barrel_map
static void get_barrel_map(float phi, float theta, float *x, float *y)
Definition: vf_ssim360.c:756
formats.h
ssim360_end1
static float ssim360_end1(int s1, int s2, int ss, int s12)
Definition: vf_ssim360.c:424
SSIM360_HIST_SIZE
#define SSIM360_HIST_SIZE
Definition: vf_ssim360.c:82
SSIM360Context::ssim360
double ssim360[4]
Definition: vf_ssim360.c:169
SSIM360Context::default_heatmap_w
int default_heatmap_w
Definition: vf_ssim360.c:183
SSIM360Context::ref_planeheight
int ref_planeheight[4]
Definition: vf_ssim360.c:189
SSIM360Context::stats_file
FILE * stats_file
Definition: vf_ssim360.c:148
FRAMESYNC_DEFINE_CLASS
FRAMESYNC_DEFINE_CLASS(ssim360, SSIM360Context, fs)
BilinearMap
Definition: vf_ssim360.c:128
cosf
#define cosf(x)
Definition: libm.h:78
fail
#define fail()
Definition: checkasm.h:134
av_strerror
int av_strerror(int errnum, char *errbuf, size_t errbuf_size)
Put a description of the AVERROR code errnum in errbuf.
Definition: error.c:108
PROJECTION_BARREL
@ PROJECTION_BARREL
Definition: vf_ssim360.c:100
val
static double val(void *priv, double ch)
Definition: aeval.c:77
SSIM360Context::max
int max
Definition: vf_ssim360.c:155
uninit
static av_cold void uninit(AVFilterContext *ctx)
Definition: vf_ssim360.c:1669
get_cubemap32_map
static void get_cubemap32_map(float phi, float theta, float *x, float *y)
Definition: vf_ssim360.c:873
ss
#define ss(width, name, subs,...)
Definition: cbs_vp9.c:260
BilinearMap::brf
int brf
Definition: vf_ssim360.c:140
SSIM360Context::angular_resolution
float angular_resolution[4][2]
Definition: vf_ssim360.c:195
AVFilterPad
A filter pad used for either input or output.
Definition: internal.h:49
ssim360_options
static const AVOption ssim360_options[]
Definition: vf_ssim360.c:206
AV_PIX_FMT_YUVJ411P
@ AV_PIX_FMT_YUVJ411P
planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) full scale (JPEG), deprecated in favor ...
Definition: pixfmt.h:276
do_ssim360
static int do_ssim360(FFFrameSync *fs)
Definition: vf_ssim360.c:1129
STEREO_FORMAT_TB
@ STEREO_FORMAT_TB
Definition: vf_ssim360.c:91
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:180
av_cold
#define av_cold
Definition: attributes.h:90
init
static av_cold int init(AVFilterContext *ctx)
Definition: vf_ssim360.c:1317
get_barrel_split_map
static void get_barrel_split_map(float phi, float theta, float expand_coef, float *x, float *y)
Definition: vf_ssim360.c:783
AV_PIX_FMT_YUVJ422P
@ AV_PIX_FMT_YUVJ422P
planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV422P and setting col...
Definition: pixfmt.h:79
float
float
Definition: af_crystalizer.c:122
width
#define width
s
#define s(width, name)
Definition: cbs_vp9.c:256
SSIM360Context::compute_chroma
int compute_chroma
Definition: vf_ssim360.c:158
AV_CEIL_RSHIFT
#define AV_CEIL_RSHIFT(a, b)
Definition: common.h:50
RIGHT
#define RIGHT
Copyright (c) 2015-2021, Facebook, Inc.
Definition: vf_ssim360.c:58
SSIM360Context::main_planewidth
int main_planewidth[4]
Definition: vf_ssim360.c:190
BilinearMap::tli
int tli
Definition: vf_ssim360.c:130
config_input_main
static int config_input_main(AVFilterLink *inlink)
Definition: vf_ssim360.c:1350
s1
#define s1
Definition: regdef.h:38
av_strtok
char * av_strtok(char *s, const char *delim, char **saveptr)
Split the string into several tokens which can be accessed by successive calls to av_strtok().
Definition: avstring.c:179
M_SQRT2_F
#define M_SQRT2_F
Definition: vf_ssim360.c:71
FIXED_POINT_PRECISION
#define FIXED_POINT_PRECISION
Definition: vf_ssim360.c:79
SSIM360Context
Definition: vf_ssim360.c:143
SampleParams::x_image_offset
int x_image_offset
Definition: vf_ssim360.c:120
ctx
AVFormatContext * ctx
Definition: movenc.c:48
SSIM360Context::use_tape
int use_tape
Definition: vf_ssim360.c:181
PROJECTION_EQUIRECT
@ PROJECTION_EQUIRECT
Definition: vf_ssim360.c:102
ssim360_endn_16bit
static double ssim360_endn_16bit(const int64_t(*sum0)[4], const int64_t(*sum1)[4], int width, int max, double *density_map, int map_width, double *total_weight)
Definition: vf_ssim360.c:441
SSIM360Context::main_planeheight
int main_planeheight[4]
Definition: vf_ssim360.c:191
AV_PIX_FMT_YUV420P
@ AV_PIX_FMT_YUV420P
planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
Definition: pixfmt.h:66
BilinearMap::bri
int bri
Definition: vf_ssim360.c:133
key
const char * key
Definition: hwcontext_opencl.c:174
FILTER_INPUTS
#define FILTER_INPUTS(array)
Definition: internal.h:194
file_open.h
AV_PIX_FMT_YUVJ444P
@ AV_PIX_FMT_YUVJ444P
planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV444P and setting col...
Definition: pixfmt.h:80
SSIM360Context::ref_planewidth
int ref_planewidth[4]
Definition: vf_ssim360.c:188
SSIM360Context::fs
FFFrameSync fs
Definition: vf_ssim360.c:146
FFABS
#define FFABS(a)
Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as they are not representable ...
Definition: common.h:64
AVClass
Describe the class of an AVClass context structure.
Definition: log.h:66
Map2D::value
double * value
Definition: vf_ssim360.c:108
SSIM360Context::rgba_map
uint8_t rgba_map[4]
Definition: vf_ssim360.c:161
SampleParams::planeheight
int planeheight
Definition: vf_ssim360.c:119
NULL
#define NULL
Definition: coverity.c:32
fs
#define fs(width, name, subs,...)
Definition: cbs_vp9.c:258
SSIM360Context::ssim360_percentile_sum
double ssim360_percentile_sum[4][256]
Definition: vf_ssim360.c:172
vars
static const uint8_t vars[2][12]
Definition: camellia.c:183
SampleParams::projection
int projection
Definition: vf_ssim360.c:124
BilinearMap::trf
int trf
Definition: vf_ssim360.c:138
AV_PIX_FMT_YUVJ420P
@ AV_PIX_FMT_YUVJ420P
planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting col...
Definition: pixfmt.h:78
ROUNDED_DIV
#define ROUNDED_DIV(a, b)
Definition: common.h:48
PROJECTION_CUBEMAP23
@ PROJECTION_CUBEMAP23
Definition: vf_ssim360.c:99
StereoFormat
StereoFormat
Definition: vf_ssim360.c:90
double
double
Definition: af_crystalizer.c:132
M_PI_F
#define M_PI_F
Definition: vf_ssim360.c:68
sinf
#define sinf(x)
Definition: libm.h:419
LEFT
#define LEFT
Definition: vf_ssim360.c:59
av_clipf
av_clipf
Definition: af_crystalizer.c:122
get_heat
static float get_heat(HeatmapList *heatmaps, float angular_resoluation, float norm_tape_pos)
Definition: vf_ssim360.c:626
AV_PIX_FMT_GRAY8
@ AV_PIX_FMT_GRAY8
Y , 8bpp.
Definition: pixfmt.h:74
PERCENTILE_LIST
static const double PERCENTILE_LIST[]
Definition: vf_ssim360.c:85
TOP
#define TOP
Definition: vf_ssim360.c:60
SSIM360Context::nb_components
int nb_components
Definition: vf_ssim360.c:152
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
weight
static int weight(int i, int blen, int offset)
Definition: diracdec.c:1562
DEFAULT_EXPANSION_COEF
#define DEFAULT_EXPANSION_COEF
Definition: vf_ssim360.c:73
SSIM360Context::ssim360_plane
double(* ssim360_plane)(uint8_t *main, int main_stride, uint8_t *ref, int ref_stride, int width, int height, void *temp, int max, Map2D density)
Definition: vf_ssim360.c:196
SampleParams::y_image_range
int y_image_range
Definition: vf_ssim360.c:123
main
int main(int argc, char **argv)
Definition: avio_http_serve_files.c:99
s2
#define s2
Definition: regdef.h:39
f
f
Definition: af_crystalizer.c:122
M_PI_2_F
#define M_PI_2_F
Definition: vf_ssim360.c:69
NULL_IF_CONFIG_SMALL
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition: internal.h:115
ff_framesync_init_dualinput
int ff_framesync_init_dualinput(FFFrameSync *fs, AVFilterContext *parent)
Initialize a frame sync structure for dualinput.
Definition: framesync.c:372
PROJECTION_BARREL_SPLIT
@ PROJECTION_BARREL_SPLIT
Definition: vf_ssim360.c:101
master
const char * master
Definition: vf_curves.c:129
ssim360_endn_8bit
static double ssim360_endn_8bit(const int(*sum0)[4], const int(*sum1)[4], int width, double *density_map, int map_width, double *total_weight)
Definition: vf_ssim360.c:461
SampleParams::x_image_range
int x_image_range
Definition: vf_ssim360.c:122
for
for(k=2;k<=8;++k)
Definition: h264pred_template.c:425
ssim360_tape
static double ssim360_tape(uint8_t *main, BilinearMap *main_maps, uint8_t *ref, BilinearMap *ref_maps, int tape_length, int max_value, void *temp, double *ssim360_hist, double *ssim360_hist_net, float angular_resolution, HeatmapList *heatmaps)
Definition: vf_ssim360.c:649
ssim360_4x4x2_tape
static void ssim360_4x4x2_tape(const uint8_t *main, BilinearMap *main_maps, const uint8_t *ref, BilinearMap *ref_maps, int offset_y, int max_value, int(*sums)[4])
Definition: vf_ssim360.c:577
generate_tape_maps
static int generate_tape_maps(SSIM360Context *s, AVFrame *main, const AVFrame *ref)
Definition: vf_ssim360.c:1069
FLAGS
#define FLAGS
Definition: vf_ssim360.c:204
PF
#define PF(suf)
Definition: vf_ssim360.c:1720
ssim360_4x4xn_8bit
static void ssim360_4x4xn_8bit(const uint8_t *main, ptrdiff_t main_stride, const uint8_t *ref, ptrdiff_t ref_stride, int(*sums)[4], int width)
Definition: vf_ssim360.c:379
PROJECTION_N
@ PROJECTION_N
Definition: vf_ssim360.c:103
map_alloc
static int map_alloc(HeatmapList **pl, int w, int h)
Definition: vf_ssim360.c:323
ssim360_4x4xn_16bit
static void ssim360_4x4xn_16bit(const uint8_t *main8, ptrdiff_t main_stride, const uint8_t *ref8, ptrdiff_t ref_stride, int64_t(*sums)[4], int width)
Definition: vf_ssim360.c:343
height
#define height
a
The reader does not expect b to be semantically here and if the code is changed by maybe adding a a division or other the signedness will almost certainly be mistaken To avoid this confusion a new type was SUINT is the C unsigned type but it holds a signed int to use the same example SUINT a
Definition: undefined.txt:41
line
Definition: graph2dot.c:48
Map2D::h
int h
Definition: vf_ssim360.c:107
BilinearMap::blf
int blf
Definition: vf_ssim360.c:139
SSIM360Context::stats_file_str
char * stats_file_str
Definition: vf_ssim360.c:149
M_PI
#define M_PI
Definition: mathematics.h:52
AV_LOG_INFO
#define AV_LOG_INFO
Standard information.
Definition: log.h:191
config_input_ref
static int config_input_ref(AVFilterLink *inlink)
Definition: vf_ssim360.c:1555
internal.h
AV_OPT_TYPE_FLOAT
@ AV_OPT_TYPE_FLOAT
Definition: opt.h:228
SSIM360Context::frame_skip_ratio
uint64_t frame_skip_ratio
Definition: vf_ssim360.c:164
generate_eye_tape_map
static int generate_eye_tape_map(SSIM360Context *s, int plane, int eye, SampleParams *ref_sample_params, SampleParams *main_sample_params)
Definition: vf_ssim360.c:1018
BACK
#define BACK
Definition: vf_ssim360.c:63
config_output
static int config_output(AVFilterLink *outlink)
Definition: vf_ssim360.c:1596
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:269
BilinearMap::tri
int tri
Definition: vf_ssim360.c:131
av_malloc_array
#define av_malloc_array(a, b)
Definition: tableprint_vlc.h:31
SSIM360Context::ssim360_hist
double * ssim360_hist[4]
Definition: vf_ssim360.c:170
value
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf default value
Definition: writing_filters.txt:86
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
AV_PIX_FMT_YUVJ440P
@ AV_PIX_FMT_YUVJ440P
planar YUV 4:4:0 full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV440P and setting color_range
Definition: pixfmt.h:100
av_mallocz
void * av_mallocz(size_t size)
Allocate a memory block with alignment suitable for all memory accesses (including vectors if availab...
Definition: mem.c:254
AVFilterPad::name
const char * name
Pad name.
Definition: internal.h:55
avpriv_fopen_utf8
FILE * avpriv_fopen_utf8(const char *path, const char *mode)
Open a file using a UTF-8 filename.
Definition: file_open.c:159
av_calloc
void * av_calloc(size_t nmemb, size_t size)
Definition: mem.c:262
OFFSET
#define OFFSET(x)
Definition: vf_ssim360.c:203
BilinearMap::tlf
int tlf
Definition: vf_ssim360.c:137
AVFilter
Filter definition.
Definition: avfilter.h:161
Map2D
Definition: vf_ssim360.c:106
BARREL_PHI_RANGE
#define BARREL_PHI_RANGE
Definition: vf_ssim360.c:76
SSIM360Context::ref_stereo_format
int ref_stereo_format
Definition: vf_ssim360.c:177
SSIM360Context::tape_length
int tape_length[4]
Definition: vf_ssim360.c:192
ret
ret
Definition: filter_design.txt:187
FFSWAP
#define FFSWAP(type, a, b)
Definition: macros.h:52
parse_heatmaps
static int parse_heatmaps(void *logctx, HeatmapList **proot, const char *data, int w, int h)
Definition: vf_ssim360.c:1251
SSIM360Context::density
Map2D density
Definition: vf_ssim360.c:186
get_radius_between_negative_and_positive_pi
static float get_radius_between_negative_and_positive_pi(float theta)
Definition: vf_ssim360.c:612
get_rotated_cubemap_map
static void get_rotated_cubemap_map(float phi, float theta, float expand_coef, float *x, float *y)
Definition: vf_ssim360.c:897
ssim360_pixfmts
static enum AVPixelFormat ssim360_pixfmts[]
Definition: vf_ssim360.c:1721
SSIM360Context::heatmap_str
char * heatmap_str
Definition: vf_ssim360.c:182
STEREO_FORMAT_N
@ STEREO_FORMAT_N
Definition: vf_ssim360.c:94
ssim360_outputs
static const AVFilterPad ssim360_outputs[]
Definition: vf_ssim360.c:1746
framesync.h
SSIM360Context::nb_net_frames
uint64_t nb_net_frames
Definition: vf_ssim360.c:168
AV_PIX_FMT_NONE
@ AV_PIX_FMT_NONE
Definition: pixfmt.h:65
AV_OPT_TYPE_INT
@ AV_OPT_TYPE_INT
Definition: opt.h:225
avfilter.h
av_get_token
char * av_get_token(const char **buf, const char *term)
Unescape the given string until a non escaped terminating char, and return the token corresponding to...
Definition: avstring.c:144
STEREO_FORMAT_LR
@ STEREO_FORMAT_LR
Definition: vf_ssim360.c:92
map_list_free
static void map_list_free(HeatmapList **pl)
Definition: vf_ssim360.c:309
PROJECTION_CUBEMAP32
@ PROJECTION_CUBEMAP32
Definition: vf_ssim360.c:98
SSIM360Context::default_heatmap_h
int default_heatmap_h
Definition: vf_ssim360.c:184
BilinearMap::bli
int bli
Definition: vf_ssim360.c:132
ref
static int ref[MAX_W *MAX_W]
Definition: jpeg2000dwt.c:112
temp
else temp
Definition: vf_mcdeint.c:248
BARREL_THETA_RANGE
#define BARREL_THETA_RANGE
Definition: vf_ssim360.c:75
AV_PIX_FMT_YUV444P
@ AV_PIX_FMT_YUV444P
planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
Definition: pixfmt.h:71
AVFilterContext
An instance of a filter.
Definition: avfilter.h:392
ssim360_end1x
static float ssim360_end1x(int64_t s1, int64_t s2, int64_t ss, int64_t s12, int max)
Definition: vf_ssim360.c:408
AV_PIX_FMT_GBRP
@ AV_PIX_FMT_GBRP
planar GBR 4:4:4 24bpp
Definition: pixfmt.h:158
SampleParams::planewidth
int planewidth
Definition: vf_ssim360.c:118
desc
const char * desc
Definition: libsvtav1.c:83
AVMEDIA_TYPE_VIDEO
@ AVMEDIA_TYPE_VIDEO
Definition: avutil.h:201
AV_PIX_FMT_YUV422P
@ AV_PIX_FMT_YUV422P
planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
Definition: pixfmt.h:70
BOTTOM
#define BOTTOM
Definition: vf_ssim360.c:61
AVPixFmtDescriptor
Descriptor that unambiguously describes how the bits of a pixel are stored in the up to 4 data planes...
Definition: pixdesc.h:69
map
const VDPAUPixFmtMap * map
Definition: hwcontext_vdpau.c:71
generate_density_map
static int generate_density_map(SSIM360Context *s, int w, int h)
Definition: vf_ssim360.c:1377
FILTER_OUTPUTS
#define FILTER_OUTPUTS(array)
Definition: internal.h:195
SampleParams::stride
int stride
Definition: vf_ssim360.c:117
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:34
av_dict_set
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags)
Set the given entry in *pm, overwriting an existing entry.
Definition: dict.c:86
AV_PIX_FMT_YUV411P
@ AV_PIX_FMT_YUV411P
planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
Definition: pixfmt.h:73
ff_fill_rgba_map
int ff_fill_rgba_map(uint8_t *rgba_map, enum AVPixelFormat pix_fmt)
Definition: drawutils.c:35
get_bilinear_sample
static int get_bilinear_sample(const uint8_t *data, BilinearMap *m, int max_value)
Definition: vf_ssim360.c:547
d
d
Definition: ffmpeg_filter.c:156
ssim360_plane_16bit
static double ssim360_plane_16bit(uint8_t *main, int main_stride, uint8_t *ref, int ref_stride, int width, int height, void *temp, int max, Map2D density)
Definition: vf_ssim360.c:479
AV_PIX_FMT_YUV410P
@ AV_PIX_FMT_YUV410P
planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples)
Definition: pixfmt.h:72
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
set_meta
static void set_meta(AVDictionary **metadata, const char *key, char comp, float d)
Definition: vf_ssim360.c:279
activate
static int activate(AVFilterContext *ctx)
Definition: vf_ssim360.c:1663
h
h
Definition: vp9dsp_template.c:2038
SSIM360Context::main_tape_map
BilinearMap * main_tape_map[4][2]
Definition: vf_ssim360.c:194
ff_framesync_activate
int ff_framesync_activate(FFFrameSync *fs)
Examine the frames in the filter's input and try to produce output.
Definition: framesync.c:355
avstring.h
ff_framesync_dualinput_get
int ff_framesync_dualinput_get(FFFrameSync *fs, AVFrame **f0, AVFrame **f1)
Definition: framesync.c:390
AV_OPT_TYPE_STRING
@ AV_OPT_TYPE_STRING
Definition: opt.h:229
drawutils.h
int
int
Definition: ffmpeg_filter.c:156
Projection
Projection
Definition: vf_ssim360.c:97
AV_OPT_TYPE_CONST
@ AV_OPT_TYPE_CONST
Definition: opt.h:234
snprintf
#define snprintf
Definition: snprintf.h:34
SSIM360Context::main_pad
float main_pad
Definition: vf_ssim360.c:180
SSIM360Context::ref_tape_map
BilinearMap * ref_tape_map[4][2]
Definition: vf_ssim360.c:193
SSIM360Context::ssim360_hist_net
double ssim360_hist_net[4]
Definition: vf_ssim360.c:171
get_equirect_map
static void get_equirect_map(float phi, float theta, float *x, float *y)
Definition: vf_ssim360.c:749
HeatmapList::map
Map2D map
Definition: vf_ssim360.c:112