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