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