]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_v360.c
avfilter/vf_v360: fix flat projection field of view calculation
[ffmpeg] / libavfilter / vf_v360.c
1 /*
2  * Copyright (c) 2019 Eugene Lyapustin
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 /**
22  * @file
23  * 360 video conversion filter.
24  * Principle of operation:
25  *
26  * (for each pixel in output frame)
27  * 1) Calculate OpenGL-like coordinates (x, y, z) for pixel position (i, j)
28  * 2) Apply 360 operations (rotation, mirror) to (x, y, z)
29  * 3) Calculate pixel position (u, v) in input frame
30  * 4) Calculate interpolation window and weight for each pixel
31  *
32  * (for each frame)
33  * 5) Remap input frame to output frame using precalculated data
34  */
35
36 #include <math.h>
37
38 #include "libavutil/avassert.h"
39 #include "libavutil/imgutils.h"
40 #include "libavutil/pixdesc.h"
41 #include "libavutil/opt.h"
42 #include "avfilter.h"
43 #include "formats.h"
44 #include "internal.h"
45 #include "video.h"
46 #include "v360.h"
47
48 typedef struct ThreadData {
49     AVFrame *in;
50     AVFrame *out;
51 } ThreadData;
52
53 #define OFFSET(x) offsetof(V360Context, x)
54 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
55
56 static const AVOption v360_options[] = {
57     {     "input", "set input projection",              OFFSET(in), AV_OPT_TYPE_INT,    {.i64=EQUIRECTANGULAR}, 0,    NB_PROJECTIONS-1, FLAGS, "in" },
58     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
59     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
60     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "in" },
61     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "in" },
62     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "in" },
63     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "in" },
64     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
65     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
66     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "in" },
67     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "in" },
68     {    "output", "set output projection",            OFFSET(out), AV_OPT_TYPE_INT,    {.i64=CUBEMAP_3_2},     0,    NB_PROJECTIONS-1, FLAGS, "out" },
69     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
70     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
71     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "out" },
72     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "out" },
73     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "out" },
74     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "out" },
75     {      "flat", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
76     {"rectilinear", "regular video",                             0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
77     {  "gnomonic", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
78     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
79     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
80     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "out" },
81     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "out" },
82     {    "interp", "set interpolation method",      OFFSET(interp), AV_OPT_TYPE_INT,    {.i64=BILINEAR},        0, NB_INTERP_METHODS-1, FLAGS, "interp" },
83     {      "near", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
84     {   "nearest", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
85     {      "line", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
86     {    "linear", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
87     {      "cube", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
88     {     "cubic", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
89     {      "lanc", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
90     {   "lanczos", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
91     {         "w", "output width",                   OFFSET(width), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "w"},
92     {         "h", "output height",                 OFFSET(height), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "h"},
93     { "in_forder", "input cubemap face order",   OFFSET(in_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "in_forder"},
94     {"out_forder", "output cubemap face order", OFFSET(out_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "out_forder"},
95     {   "in_frot", "input cubemap face rotation",  OFFSET(in_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "in_frot"},
96     {  "out_frot", "output cubemap face rotation",OFFSET(out_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "out_frot"},
97     {    "in_pad", "input cubemap pads",            OFFSET(in_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f, FLAGS, "in_pad"},
98     {   "out_pad", "output cubemap pads",          OFFSET(out_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f, FLAGS, "out_pad"},
99     {       "yaw", "yaw rotation",                     OFFSET(yaw), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "yaw"},
100     {     "pitch", "pitch rotation",                 OFFSET(pitch), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "pitch"},
101     {      "roll", "roll rotation",                   OFFSET(roll), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "roll"},
102     {    "rorder", "rotation order",                OFFSET(rorder), AV_OPT_TYPE_STRING, {.str="ypr"},           0,                   0, FLAGS, "rorder"},
103     {     "h_fov", "horizontal field of view",       OFFSET(h_fov), AV_OPT_TYPE_FLOAT,  {.dbl=90.f},     0.00001f,               360.f, FLAGS, "h_fov"},
104     {     "v_fov", "vertical field of view",         OFFSET(v_fov), AV_OPT_TYPE_FLOAT,  {.dbl=45.f},     0.00001f,               360.f, FLAGS, "v_fov"},
105     {    "h_flip", "flip out video horizontally",   OFFSET(h_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "h_flip"},
106     {    "v_flip", "flip out video vertically",     OFFSET(v_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "v_flip"},
107     {    "d_flip", "flip out video indepth",        OFFSET(d_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "d_flip"},
108     {   "ih_flip", "flip in video horizontally",   OFFSET(ih_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "ih_flip"},
109     {   "iv_flip", "flip in video vertically",     OFFSET(iv_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "iv_flip"},
110     {  "in_trans", "transpose video input",   OFFSET(in_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "in_transpose"},
111     { "out_trans", "transpose video output", OFFSET(out_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "out_transpose"},
112     { NULL }
113 };
114
115 AVFILTER_DEFINE_CLASS(v360);
116
117 static int query_formats(AVFilterContext *ctx)
118 {
119     static const enum AVPixelFormat pix_fmts[] = {
120         // YUVA444
121         AV_PIX_FMT_YUVA444P,   AV_PIX_FMT_YUVA444P9,
122         AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
123         AV_PIX_FMT_YUVA444P16,
124
125         // YUVA422
126         AV_PIX_FMT_YUVA422P,   AV_PIX_FMT_YUVA422P9,
127         AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
128         AV_PIX_FMT_YUVA422P16,
129
130         // YUVA420
131         AV_PIX_FMT_YUVA420P,   AV_PIX_FMT_YUVA420P9,
132         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
133
134         // YUVJ
135         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
136         AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
137         AV_PIX_FMT_YUVJ411P,
138
139         // YUV444
140         AV_PIX_FMT_YUV444P,   AV_PIX_FMT_YUV444P9,
141         AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12,
142         AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16,
143
144         // YUV440
145         AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV440P10,
146         AV_PIX_FMT_YUV440P12,
147
148         // YUV422
149         AV_PIX_FMT_YUV422P,   AV_PIX_FMT_YUV422P9,
150         AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12,
151         AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV422P16,
152
153         // YUV420
154         AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV420P9,
155         AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12,
156         AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV420P16,
157
158         // YUV411
159         AV_PIX_FMT_YUV411P,
160
161         // YUV410
162         AV_PIX_FMT_YUV410P,
163
164         // GBR
165         AV_PIX_FMT_GBRP,   AV_PIX_FMT_GBRP9,
166         AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12,
167         AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
168
169         // GBRA
170         AV_PIX_FMT_GBRAP,   AV_PIX_FMT_GBRAP10,
171         AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
172
173         // GRAY
174         AV_PIX_FMT_GRAY8,  AV_PIX_FMT_GRAY9,
175         AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12,
176         AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
177
178         AV_PIX_FMT_NONE
179     };
180
181     AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
182     if (!fmts_list)
183         return AVERROR(ENOMEM);
184     return ff_set_common_formats(ctx, fmts_list);
185 }
186
187 #define DEFINE_REMAP1_LINE(bits, div)                                                           \
188 static void remap1_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *src,              \
189                                       ptrdiff_t in_linesize,                                    \
190                                       const uint16_t *u, const uint16_t *v, const int16_t *ker) \
191 {                                                                                               \
192     const uint##bits##_t *s = (const uint##bits##_t *)src;                                      \
193     uint##bits##_t *d = (uint##bits##_t *)dst;                                                  \
194                                                                                                 \
195     in_linesize /= div;                                                                         \
196                                                                                                 \
197     for (int x = 0; x < width; x++)                                                             \
198         d[x] = s[v[x] * in_linesize + u[x]];                                                    \
199 }
200
201 DEFINE_REMAP1_LINE( 8, 1)
202 DEFINE_REMAP1_LINE(16, 2)
203
204 typedef struct XYRemap {
205     uint16_t u[4][4];
206     uint16_t v[4][4];
207     float ker[4][4];
208 } XYRemap;
209
210 /**
211  * Generate remapping function with a given window size and pixel depth.
212  *
213  * @param ws size of interpolation window
214  * @param bits number of bits per pixel
215  */
216 #define DEFINE_REMAP(ws, bits)                                                                             \
217 static int remap##ws##_##bits##bit_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)          \
218 {                                                                                                          \
219     ThreadData *td = (ThreadData*)arg;                                                                     \
220     const V360Context *s = ctx->priv;                                                                      \
221     const AVFrame *in = td->in;                                                                            \
222     AVFrame *out = td->out;                                                                                \
223                                                                                                            \
224     for (int plane = 0; plane < s->nb_planes; plane++) {                                                   \
225         const int in_linesize  = in->linesize[plane];                                                      \
226         const int out_linesize = out->linesize[plane];                                                     \
227         const int uv_linesize = s->uv_linesize[plane];                                                     \
228         const uint8_t *src = in->data[plane];                                                              \
229         uint8_t *dst = out->data[plane];                                                                   \
230         const int width = s->planewidth[plane];                                                            \
231         const int height = s->planeheight[plane];                                                          \
232                                                                                                            \
233         const int slice_start = (height *  jobnr     ) / nb_jobs;                                          \
234         const int slice_end   = (height * (jobnr + 1)) / nb_jobs;                                          \
235                                                                                                            \
236         for (int y = slice_start; y < slice_end; y++) {                                                    \
237             const unsigned map = s->map[plane];                                                            \
238             const uint16_t *u = s->u[map] + y * uv_linesize * ws * ws;                                     \
239             const uint16_t *v = s->v[map] + y * uv_linesize * ws * ws;                                     \
240             const int16_t *ker = s->ker[map] + y * uv_linesize * ws * ws;                                  \
241                                                                                                            \
242             s->remap_line(dst + y * out_linesize, width, src, in_linesize, u, v, ker);                     \
243         }                                                                                                  \
244     }                                                                                                      \
245                                                                                                            \
246     return 0;                                                                                              \
247 }
248
249 DEFINE_REMAP(1,  8)
250 DEFINE_REMAP(2,  8)
251 DEFINE_REMAP(4,  8)
252 DEFINE_REMAP(1, 16)
253 DEFINE_REMAP(2, 16)
254 DEFINE_REMAP(4, 16)
255
256 #define DEFINE_REMAP_LINE(ws, bits, div)                                                                   \
257 static void remap##ws##_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *src,                    \
258                                            ptrdiff_t in_linesize,                                          \
259                                            const uint16_t *u, const uint16_t *v, const int16_t *ker)       \
260 {                                                                                                          \
261     const uint##bits##_t *s = (const uint##bits##_t *)src;                                                 \
262     uint##bits##_t *d = (uint##bits##_t *)dst;                                                             \
263                                                                                                            \
264     in_linesize /= div;                                                                                    \
265                                                                                                            \
266     for (int x = 0; x < width; x++) {                                                                      \
267         const uint16_t *uu = u + x * ws * ws;                                                              \
268         const uint16_t *vv = v + x * ws * ws;                                                              \
269         const int16_t *kker = ker + x * ws * ws;                                                           \
270         int tmp = 0;                                                                                       \
271                                                                                                            \
272         for (int i = 0; i < ws; i++) {                                                                     \
273             for (int j = 0; j < ws; j++) {                                                                 \
274                 tmp += kker[i * ws + j] * s[vv[i * ws + j] * in_linesize + uu[i * ws + j]];                \
275             }                                                                                              \
276         }                                                                                                  \
277                                                                                                            \
278         d[x] = av_clip_uint##bits(tmp >> 14);                                                              \
279     }                                                                                                      \
280 }
281
282 DEFINE_REMAP_LINE(2,  8, 1)
283 DEFINE_REMAP_LINE(4,  8, 1)
284 DEFINE_REMAP_LINE(2, 16, 2)
285 DEFINE_REMAP_LINE(4, 16, 2)
286
287 void ff_v360_init(V360Context *s, int depth)
288 {
289     switch (s->interp) {
290     case NEAREST:
291         s->remap_line = depth <= 8 ? remap1_8bit_line_c : remap1_16bit_line_c;
292         break;
293     case BILINEAR:
294         s->remap_line = depth <= 8 ? remap2_8bit_line_c : remap2_16bit_line_c;
295         break;
296     case BICUBIC:
297     case LANCZOS:
298         s->remap_line = depth <= 8 ? remap4_8bit_line_c : remap4_16bit_line_c;
299         break;
300     }
301
302     if (ARCH_X86)
303         ff_v360_init_x86(s, depth);
304 }
305
306 /**
307  * Save nearest pixel coordinates for remapping.
308  *
309  * @param du horizontal relative coordinate
310  * @param dv vertical relative coordinate
311  * @param r_tmp calculated 4x4 window
312  * @param u u remap data
313  * @param v v remap data
314  * @param ker ker remap data
315  */
316 static void nearest_kernel(float du, float dv, const XYRemap *r_tmp,
317                            uint16_t *u, uint16_t *v, int16_t *ker)
318 {
319     const int i = roundf(dv) + 1;
320     const int j = roundf(du) + 1;
321
322     u[0] = r_tmp->u[i][j];
323     v[0] = r_tmp->v[i][j];
324 }
325
326 /**
327  * Calculate kernel for bilinear interpolation.
328  *
329  * @param du horizontal relative coordinate
330  * @param dv vertical relative coordinate
331  * @param r_tmp calculated 4x4 window
332  * @param u u remap data
333  * @param v v remap data
334  * @param ker ker remap data
335  */
336 static void bilinear_kernel(float du, float dv, const XYRemap *r_tmp,
337                             uint16_t *u, uint16_t *v, int16_t *ker)
338 {
339     int i, j;
340
341     for (i = 0; i < 2; i++) {
342         for (j = 0; j < 2; j++) {
343             u[i * 2 + j] = r_tmp->u[i + 1][j + 1];
344             v[i * 2 + j] = r_tmp->v[i + 1][j + 1];
345         }
346     }
347
348     ker[0] = (1.f - du) * (1.f - dv) * 16384;
349     ker[1] =        du  * (1.f - dv) * 16384;
350     ker[2] = (1.f - du) *        dv  * 16384;
351     ker[3] =        du  *        dv  * 16384;
352 }
353
354 /**
355  * Calculate 1-dimensional cubic coefficients.
356  *
357  * @param t relative coordinate
358  * @param coeffs coefficients
359  */
360 static inline void calculate_bicubic_coeffs(float t, float *coeffs)
361 {
362     const float tt  = t * t;
363     const float ttt = t * t * t;
364
365     coeffs[0] =     - t / 3.f + tt / 2.f - ttt / 6.f;
366     coeffs[1] = 1.f - t / 2.f - tt       + ttt / 2.f;
367     coeffs[2] =       t       + tt / 2.f - ttt / 2.f;
368     coeffs[3] =     - t / 6.f            + ttt / 6.f;
369 }
370
371 /**
372  * Calculate kernel for bicubic interpolation.
373  *
374  * @param du horizontal relative coordinate
375  * @param dv vertical relative coordinate
376  * @param r_tmp calculated 4x4 window
377  * @param u u remap data
378  * @param v v remap data
379  * @param ker ker remap data
380  */
381 static void bicubic_kernel(float du, float dv, const XYRemap *r_tmp,
382                            uint16_t *u, uint16_t *v, int16_t *ker)
383 {
384     int i, j;
385     float du_coeffs[4];
386     float dv_coeffs[4];
387
388     calculate_bicubic_coeffs(du, du_coeffs);
389     calculate_bicubic_coeffs(dv, dv_coeffs);
390
391     for (i = 0; i < 4; i++) {
392         for (j = 0; j < 4; j++) {
393             u[i * 4 + j] = r_tmp->u[i][j];
394             v[i * 4 + j] = r_tmp->v[i][j];
395             ker[i * 4 + j] = du_coeffs[j] * dv_coeffs[i] * 16384;
396         }
397     }
398 }
399
400 /**
401  * Calculate 1-dimensional lanczos coefficients.
402  *
403  * @param t relative coordinate
404  * @param coeffs coefficients
405  */
406 static inline void calculate_lanczos_coeffs(float t, float *coeffs)
407 {
408     int i;
409     float sum = 0.f;
410
411     for (i = 0; i < 4; i++) {
412         const float x = M_PI * (t - i + 1);
413         if (x == 0.f) {
414             coeffs[i] = 1.f;
415         } else {
416             coeffs[i] = sinf(x) * sinf(x / 2.f) / (x * x / 2.f);
417         }
418         sum += coeffs[i];
419     }
420
421     for (i = 0; i < 4; i++) {
422         coeffs[i] /= sum;
423     }
424 }
425
426 /**
427  * Calculate kernel for lanczos interpolation.
428  *
429  * @param du horizontal relative coordinate
430  * @param dv vertical relative coordinate
431  * @param r_tmp calculated 4x4 window
432  * @param u u remap data
433  * @param v v remap data
434  * @param ker ker remap data
435  */
436 static void lanczos_kernel(float du, float dv, const XYRemap *r_tmp,
437                            uint16_t *u, uint16_t *v, int16_t *ker)
438 {
439     int i, j;
440     float du_coeffs[4];
441     float dv_coeffs[4];
442
443     calculate_lanczos_coeffs(du, du_coeffs);
444     calculate_lanczos_coeffs(dv, dv_coeffs);
445
446     for (i = 0; i < 4; i++) {
447         for (j = 0; j < 4; j++) {
448             u[i * 4 + j] = r_tmp->u[i][j];
449             v[i * 4 + j] = r_tmp->v[i][j];
450             ker[i * 4 + j] = du_coeffs[j] * dv_coeffs[i] * 16384;
451         }
452     }
453 }
454
455 /**
456  * Modulo operation with only positive remainders.
457  *
458  * @param a dividend
459  * @param b divisor
460  *
461  * @return positive remainder of (a / b)
462  */
463 static inline int mod(int a, int b)
464 {
465     const int res = a % b;
466     if (res < 0) {
467         return res + b;
468     } else {
469         return res;
470     }
471 }
472
473 /**
474  * Convert char to corresponding direction.
475  * Used for cubemap options.
476  */
477 static int get_direction(char c)
478 {
479     switch (c) {
480     case 'r':
481         return RIGHT;
482     case 'l':
483         return LEFT;
484     case 'u':
485         return UP;
486     case 'd':
487         return DOWN;
488     case 'f':
489         return FRONT;
490     case 'b':
491         return BACK;
492     default:
493         return -1;
494     }
495 }
496
497 /**
498  * Convert char to corresponding rotation angle.
499  * Used for cubemap options.
500  */
501 static int get_rotation(char c)
502 {
503     switch (c) {
504     case '0':
505         return ROT_0;
506     case '1':
507         return ROT_90;
508     case '2':
509         return ROT_180;
510     case '3':
511         return ROT_270;
512     default:
513         return -1;
514     }
515 }
516
517 /**
518  * Convert char to corresponding rotation order.
519  */
520 static int get_rorder(char c)
521 {
522     switch (c) {
523     case 'Y':
524     case 'y':
525         return YAW;
526     case 'P':
527     case 'p':
528         return PITCH;
529     case 'R':
530     case 'r':
531         return ROLL;
532     default:
533         return -1;
534     }
535 }
536
537 /**
538  * Prepare data for processing cubemap input format.
539  *
540  * @param ctx filter context
541  *
542  * @return error code
543  */
544 static int prepare_cube_in(AVFilterContext *ctx)
545 {
546     V360Context *s = ctx->priv;
547
548     for (int face = 0; face < NB_FACES; face++) {
549         const char c = s->in_forder[face];
550         int direction;
551
552         if (c == '\0') {
553             av_log(ctx, AV_LOG_ERROR,
554                    "Incomplete in_forder option. Direction for all 6 faces should be specified.\n");
555             return AVERROR(EINVAL);
556         }
557
558         direction = get_direction(c);
559         if (direction == -1) {
560             av_log(ctx, AV_LOG_ERROR,
561                    "Incorrect direction symbol '%c' in in_forder option.\n", c);
562             return AVERROR(EINVAL);
563         }
564
565         s->in_cubemap_face_order[direction] = face;
566     }
567
568     for (int face = 0; face < NB_FACES; face++) {
569         const char c = s->in_frot[face];
570         int rotation;
571
572         if (c == '\0') {
573             av_log(ctx, AV_LOG_ERROR,
574                    "Incomplete in_frot option. Rotation for all 6 faces should be specified.\n");
575             return AVERROR(EINVAL);
576         }
577
578         rotation = get_rotation(c);
579         if (rotation == -1) {
580             av_log(ctx, AV_LOG_ERROR,
581                    "Incorrect rotation symbol '%c' in in_frot option.\n", c);
582             return AVERROR(EINVAL);
583         }
584
585         s->in_cubemap_face_rotation[face] = rotation;
586     }
587
588     return 0;
589 }
590
591 /**
592  * Prepare data for processing cubemap output format.
593  *
594  * @param ctx filter context
595  *
596  * @return error code
597  */
598 static int prepare_cube_out(AVFilterContext *ctx)
599 {
600     V360Context *s = ctx->priv;
601
602     for (int face = 0; face < NB_FACES; face++) {
603         const char c = s->out_forder[face];
604         int direction;
605
606         if (c == '\0') {
607             av_log(ctx, AV_LOG_ERROR,
608                    "Incomplete out_forder option. Direction for all 6 faces should be specified.\n");
609             return AVERROR(EINVAL);
610         }
611
612         direction = get_direction(c);
613         if (direction == -1) {
614             av_log(ctx, AV_LOG_ERROR,
615                    "Incorrect direction symbol '%c' in out_forder option.\n", c);
616             return AVERROR(EINVAL);
617         }
618
619         s->out_cubemap_direction_order[face] = direction;
620     }
621
622     for (int face = 0; face < NB_FACES; face++) {
623         const char c = s->out_frot[face];
624         int rotation;
625
626         if (c == '\0') {
627             av_log(ctx, AV_LOG_ERROR,
628                    "Incomplete out_frot option. Rotation for all 6 faces should be specified.\n");
629             return AVERROR(EINVAL);
630         }
631
632         rotation = get_rotation(c);
633         if (rotation == -1) {
634             av_log(ctx, AV_LOG_ERROR,
635                    "Incorrect rotation symbol '%c' in out_frot option.\n", c);
636             return AVERROR(EINVAL);
637         }
638
639         s->out_cubemap_face_rotation[face] = rotation;
640     }
641
642     return 0;
643 }
644
645 static inline void rotate_cube_face(float *uf, float *vf, int rotation)
646 {
647     float tmp;
648
649     switch (rotation) {
650     case ROT_0:
651         break;
652     case ROT_90:
653         tmp =  *uf;
654         *uf = -*vf;
655         *vf =  tmp;
656         break;
657     case ROT_180:
658         *uf = -*uf;
659         *vf = -*vf;
660         break;
661     case ROT_270:
662         tmp = -*uf;
663         *uf =  *vf;
664         *vf =  tmp;
665         break;
666     default:
667         av_assert0(0);
668     }
669 }
670
671 static inline void rotate_cube_face_inverse(float *uf, float *vf, int rotation)
672 {
673     float tmp;
674
675     switch (rotation) {
676     case ROT_0:
677         break;
678     case ROT_90:
679         tmp = -*uf;
680         *uf =  *vf;
681         *vf =  tmp;
682         break;
683     case ROT_180:
684         *uf = -*uf;
685         *vf = -*vf;
686         break;
687     case ROT_270:
688         tmp =  *uf;
689         *uf = -*vf;
690         *vf =  tmp;
691         break;
692     default:
693         av_assert0(0);
694     }
695 }
696
697 /**
698  * Normalize vector.
699  *
700  * @param vec vector
701  */
702 static void normalize_vector(float *vec)
703 {
704     const float norm = sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
705
706     vec[0] /= norm;
707     vec[1] /= norm;
708     vec[2] /= norm;
709 }
710
711 /**
712  * Calculate 3D coordinates on sphere for corresponding cubemap position.
713  * Common operation for every cubemap.
714  *
715  * @param s filter context
716  * @param uf horizontal cubemap coordinate [0, 1)
717  * @param vf vertical cubemap coordinate [0, 1)
718  * @param face face of cubemap
719  * @param vec coordinates on sphere
720  */
721 static void cube_to_xyz(const V360Context *s,
722                         float uf, float vf, int face,
723                         float *vec)
724 {
725     const int direction = s->out_cubemap_direction_order[face];
726     float l_x, l_y, l_z;
727
728     uf /= (1.f - s->out_pad);
729     vf /= (1.f - s->out_pad);
730
731     rotate_cube_face_inverse(&uf, &vf, s->out_cubemap_face_rotation[face]);
732
733     switch (direction) {
734     case RIGHT:
735         l_x =  1.f;
736         l_y = -vf;
737         l_z =  uf;
738         break;
739     case LEFT:
740         l_x = -1.f;
741         l_y = -vf;
742         l_z = -uf;
743         break;
744     case UP:
745         l_x =  uf;
746         l_y =  1.f;
747         l_z = -vf;
748         break;
749     case DOWN:
750         l_x =  uf;
751         l_y = -1.f;
752         l_z =  vf;
753         break;
754     case FRONT:
755         l_x =  uf;
756         l_y = -vf;
757         l_z = -1.f;
758         break;
759     case BACK:
760         l_x = -uf;
761         l_y = -vf;
762         l_z =  1.f;
763         break;
764     }
765
766     vec[0] = l_x;
767     vec[1] = l_y;
768     vec[2] = l_z;
769
770     normalize_vector(vec);
771 }
772
773 /**
774  * Calculate cubemap position for corresponding 3D coordinates on sphere.
775  * Common operation for every cubemap.
776  *
777  * @param s filter context
778  * @param vec coordinated on sphere
779  * @param uf horizontal cubemap coordinate [0, 1)
780  * @param vf vertical cubemap coordinate [0, 1)
781  * @param direction direction of view
782  */
783 static void xyz_to_cube(const V360Context *s,
784                         const float *vec,
785                         float *uf, float *vf, int *direction)
786 {
787     const float phi   = atan2f(vec[0], -vec[2]);
788     const float theta = asinf(-vec[1]);
789     float phi_norm, theta_threshold;
790     int face;
791
792     if (phi >= -M_PI_4 && phi < M_PI_4) {
793         *direction = FRONT;
794         phi_norm = phi;
795     } else if (phi >= -(M_PI_2 + M_PI_4) && phi < -M_PI_4) {
796         *direction = LEFT;
797         phi_norm = phi + M_PI_2;
798     } else if (phi >= M_PI_4 && phi < M_PI_2 + M_PI_4) {
799         *direction = RIGHT;
800         phi_norm = phi - M_PI_2;
801     } else {
802         *direction = BACK;
803         phi_norm = phi + ((phi > 0.f) ? -M_PI : M_PI);
804     }
805
806     theta_threshold = atanf(cosf(phi_norm));
807     if (theta > theta_threshold) {
808         *direction = DOWN;
809     } else if (theta < -theta_threshold) {
810         *direction = UP;
811     }
812
813     switch (*direction) {
814     case RIGHT:
815         *uf =  vec[2] / vec[0];
816         *vf = -vec[1] / vec[0];
817         break;
818     case LEFT:
819         *uf =  vec[2] / vec[0];
820         *vf =  vec[1] / vec[0];
821         break;
822     case UP:
823         *uf =  vec[0] / vec[1];
824         *vf = -vec[2] / vec[1];
825         break;
826     case DOWN:
827         *uf = -vec[0] / vec[1];
828         *vf = -vec[2] / vec[1];
829         break;
830     case FRONT:
831         *uf = -vec[0] / vec[2];
832         *vf =  vec[1] / vec[2];
833         break;
834     case BACK:
835         *uf = -vec[0] / vec[2];
836         *vf = -vec[1] / vec[2];
837         break;
838     default:
839         av_assert0(0);
840     }
841
842     face = s->in_cubemap_face_order[*direction];
843     rotate_cube_face(uf, vf, s->in_cubemap_face_rotation[face]);
844
845     (*uf) *= s->input_mirror_modifier[0];
846     (*vf) *= s->input_mirror_modifier[1];
847 }
848
849 /**
850  * Find position on another cube face in case of overflow/underflow.
851  * Used for calculation of interpolation window.
852  *
853  * @param s filter context
854  * @param uf horizontal cubemap coordinate
855  * @param vf vertical cubemap coordinate
856  * @param direction direction of view
857  * @param new_uf new horizontal cubemap coordinate
858  * @param new_vf new vertical cubemap coordinate
859  * @param face face position on cubemap
860  */
861 static void process_cube_coordinates(const V360Context *s,
862                                      float uf, float vf, int direction,
863                                      float *new_uf, float *new_vf, int *face)
864 {
865     /*
866      *  Cubemap orientation
867      *
868      *           width
869      *         <------->
870      *         +-------+
871      *         |       |                              U
872      *         | up    |                   h       ------->
873      * +-------+-------+-------+-------+ ^ e      |
874      * |       |       |       |       | | i    V |
875      * | left  | front | right | back  | | g      |
876      * +-------+-------+-------+-------+ v h      v
877      *         |       |                   t
878      *         | down  |
879      *         +-------+
880      */
881
882     *face = s->in_cubemap_face_order[direction];
883     rotate_cube_face_inverse(&uf, &vf, s->in_cubemap_face_rotation[*face]);
884
885     if ((uf < -1.f || uf >= 1.f) && (vf < -1.f || vf >= 1.f)) {
886         // There are no pixels to use in this case
887         *new_uf = uf;
888         *new_vf = vf;
889     } else if (uf < -1.f) {
890         uf += 2.f;
891         switch (direction) {
892         case RIGHT:
893             direction = FRONT;
894             *new_uf =  uf;
895             *new_vf =  vf;
896             break;
897         case LEFT:
898             direction = BACK;
899             *new_uf =  uf;
900             *new_vf =  vf;
901             break;
902         case UP:
903             direction = LEFT;
904             *new_uf =  vf;
905             *new_vf = -uf;
906             break;
907         case DOWN:
908             direction = LEFT;
909             *new_uf = -vf;
910             *new_vf =  uf;
911             break;
912         case FRONT:
913             direction = LEFT;
914             *new_uf =  uf;
915             *new_vf =  vf;
916             break;
917         case BACK:
918             direction = RIGHT;
919             *new_uf =  uf;
920             *new_vf =  vf;
921             break;
922         default:
923             av_assert0(0);
924         }
925     } else if (uf >= 1.f) {
926         uf -= 2.f;
927         switch (direction) {
928         case RIGHT:
929             direction = BACK;
930             *new_uf =  uf;
931             *new_vf =  vf;
932             break;
933         case LEFT:
934             direction = FRONT;
935             *new_uf =  uf;
936             *new_vf =  vf;
937             break;
938         case UP:
939             direction = RIGHT;
940             *new_uf = -vf;
941             *new_vf =  uf;
942             break;
943         case DOWN:
944             direction = RIGHT;
945             *new_uf =  vf;
946             *new_vf = -uf;
947             break;
948         case FRONT:
949             direction = RIGHT;
950             *new_uf =  uf;
951             *new_vf =  vf;
952             break;
953         case BACK:
954             direction = LEFT;
955             *new_uf =  uf;
956             *new_vf =  vf;
957             break;
958         default:
959             av_assert0(0);
960         }
961     } else if (vf < -1.f) {
962         vf += 2.f;
963         switch (direction) {
964         case RIGHT:
965             direction = UP;
966             *new_uf =  vf;
967             *new_vf = -uf;
968             break;
969         case LEFT:
970             direction = UP;
971             *new_uf = -vf;
972             *new_vf =  uf;
973             break;
974         case UP:
975             direction = BACK;
976             *new_uf = -uf;
977             *new_vf = -vf;
978             break;
979         case DOWN:
980             direction = FRONT;
981             *new_uf =  uf;
982             *new_vf =  vf;
983             break;
984         case FRONT:
985             direction = UP;
986             *new_uf =  uf;
987             *new_vf =  vf;
988             break;
989         case BACK:
990             direction = UP;
991             *new_uf = -uf;
992             *new_vf = -vf;
993             break;
994         default:
995             av_assert0(0);
996         }
997     } else if (vf >= 1.f) {
998         vf -= 2.f;
999         switch (direction) {
1000         case RIGHT:
1001             direction = DOWN;
1002             *new_uf = -vf;
1003             *new_vf =  uf;
1004             break;
1005         case LEFT:
1006             direction = DOWN;
1007             *new_uf =  vf;
1008             *new_vf = -uf;
1009             break;
1010         case UP:
1011             direction = FRONT;
1012             *new_uf =  uf;
1013             *new_vf =  vf;
1014             break;
1015         case DOWN:
1016             direction = BACK;
1017             *new_uf = -uf;
1018             *new_vf = -vf;
1019             break;
1020         case FRONT:
1021             direction = DOWN;
1022             *new_uf =  uf;
1023             *new_vf =  vf;
1024             break;
1025         case BACK:
1026             direction = DOWN;
1027             *new_uf = -uf;
1028             *new_vf = -vf;
1029             break;
1030         default:
1031             av_assert0(0);
1032         }
1033     } else {
1034         // Inside cube face
1035         *new_uf = uf;
1036         *new_vf = vf;
1037     }
1038
1039     *face = s->in_cubemap_face_order[direction];
1040     rotate_cube_face(new_uf, new_vf, s->in_cubemap_face_rotation[*face]);
1041 }
1042
1043 /**
1044  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap3x2 format.
1045  *
1046  * @param s filter context
1047  * @param i horizontal position on frame [0, width)
1048  * @param j vertical position on frame [0, height)
1049  * @param width frame width
1050  * @param height frame height
1051  * @param vec coordinates on sphere
1052  */
1053 static void cube3x2_to_xyz(const V360Context *s,
1054                            int i, int j, int width, int height,
1055                            float *vec)
1056 {
1057     const float ew = width  / 3.f;
1058     const float eh = height / 2.f;
1059
1060     const int u_face = floorf(i / ew);
1061     const int v_face = floorf(j / eh);
1062     const int face = u_face + 3 * v_face;
1063
1064     const int u_shift = ceilf(ew * u_face);
1065     const int v_shift = ceilf(eh * v_face);
1066     const int ewi = ceilf(ew * (u_face + 1)) - u_shift;
1067     const int ehi = ceilf(eh * (v_face + 1)) - v_shift;
1068
1069     const float uf = 2.f * (i - u_shift) / ewi - 1.f;
1070     const float vf = 2.f * (j - v_shift) / ehi - 1.f;
1071
1072     cube_to_xyz(s, uf, vf, face, vec);
1073 }
1074
1075 /**
1076  * Calculate frame position in cubemap3x2 format for corresponding 3D coordinates on sphere.
1077  *
1078  * @param s filter context
1079  * @param vec coordinates on sphere
1080  * @param width frame width
1081  * @param height frame height
1082  * @param us horizontal coordinates for interpolation window
1083  * @param vs vertical coordinates for interpolation window
1084  * @param du horizontal relative coordinate
1085  * @param dv vertical relative coordinate
1086  */
1087 static void xyz_to_cube3x2(const V360Context *s,
1088                            const float *vec, int width, int height,
1089                            uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1090 {
1091     const float ew = width  / 3.f;
1092     const float eh = height / 2.f;
1093     float uf, vf;
1094     int ui, vi;
1095     int ewi, ehi;
1096     int i, j;
1097     int direction, face;
1098     int u_face, v_face;
1099
1100     xyz_to_cube(s, vec, &uf, &vf, &direction);
1101
1102     uf *= (1.f - s->in_pad);
1103     vf *= (1.f - s->in_pad);
1104
1105     face = s->in_cubemap_face_order[direction];
1106     u_face = face % 3;
1107     v_face = face / 3;
1108     ewi = ceilf(ew * (u_face + 1)) - ceilf(ew * u_face);
1109     ehi = ceilf(eh * (v_face + 1)) - ceilf(eh * v_face);
1110
1111     uf = 0.5f * ewi * (uf + 1.f);
1112     vf = 0.5f * ehi * (vf + 1.f);
1113
1114     ui = floorf(uf);
1115     vi = floorf(vf);
1116
1117     *du = uf - ui;
1118     *dv = vf - vi;
1119
1120     for (i = -1; i < 3; i++) {
1121         for (j = -1; j < 3; j++) {
1122             int new_ui = ui + j;
1123             int new_vi = vi + i;
1124             int u_shift, v_shift;
1125             int new_ewi, new_ehi;
1126
1127             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1128                 face = s->in_cubemap_face_order[direction];
1129
1130                 u_face = face % 3;
1131                 v_face = face / 3;
1132                 u_shift = ceilf(ew * u_face);
1133                 v_shift = ceilf(eh * v_face);
1134             } else {
1135                 uf = 2.f * new_ui / ewi - 1.f;
1136                 vf = 2.f * new_vi / ehi - 1.f;
1137
1138                 uf /= (1.f - s->in_pad);
1139                 vf /= (1.f - s->in_pad);
1140
1141                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1142
1143                 uf *= (1.f - s->in_pad);
1144                 vf *= (1.f - s->in_pad);
1145
1146                 u_face = face % 3;
1147                 v_face = face / 3;
1148                 u_shift = ceilf(ew * u_face);
1149                 v_shift = ceilf(eh * v_face);
1150                 new_ewi = ceilf(ew * (u_face + 1)) - u_shift;
1151                 new_ehi = ceilf(eh * (v_face + 1)) - v_shift;
1152
1153                 new_ui = av_clip(roundf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1154                 new_vi = av_clip(roundf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1155             }
1156
1157             us[i + 1][j + 1] = u_shift + new_ui;
1158             vs[i + 1][j + 1] = v_shift + new_vi;
1159         }
1160     }
1161 }
1162
1163 /**
1164  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap1x6 format.
1165  *
1166  * @param s filter context
1167  * @param i horizontal position on frame [0, width)
1168  * @param j vertical position on frame [0, height)
1169  * @param width frame width
1170  * @param height frame height
1171  * @param vec coordinates on sphere
1172  */
1173 static void cube1x6_to_xyz(const V360Context *s,
1174                            int i, int j, int width, int height,
1175                            float *vec)
1176 {
1177     const float ew = width;
1178     const float eh = height / 6.f;
1179
1180     const int face = floorf(j / eh);
1181
1182     const int v_shift = ceilf(eh * face);
1183     const int ehi = ceilf(eh * (face + 1)) - v_shift;
1184
1185     const float uf = 2.f *  i            / ew  - 1.f;
1186     const float vf = 2.f * (j - v_shift) / ehi - 1.f;
1187
1188     cube_to_xyz(s, uf, vf, face, vec);
1189 }
1190
1191 /**
1192  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap6x1 format.
1193  *
1194  * @param s filter context
1195  * @param i horizontal position on frame [0, width)
1196  * @param j vertical position on frame [0, height)
1197  * @param width frame width
1198  * @param height frame height
1199  * @param vec coordinates on sphere
1200  */
1201 static void cube6x1_to_xyz(const V360Context *s,
1202                            int i, int j, int width, int height,
1203                            float *vec)
1204 {
1205     const float ew = width / 6.f;
1206     const float eh = height;
1207
1208     const int face = floorf(i / ew);
1209
1210     const int u_shift = ceilf(ew * face);
1211     const int ewi = ceilf(ew * (face + 1)) - u_shift;
1212
1213     const float uf = 2.f * (i - u_shift) / ewi - 1.f;
1214     const float vf = 2.f *  j            / eh  - 1.f;
1215
1216     cube_to_xyz(s, uf, vf, face, vec);
1217 }
1218
1219 /**
1220  * Calculate frame position in cubemap1x6 format for corresponding 3D coordinates on sphere.
1221  *
1222  * @param s filter context
1223  * @param vec coordinates on sphere
1224  * @param width frame width
1225  * @param height frame height
1226  * @param us horizontal coordinates for interpolation window
1227  * @param vs vertical coordinates for interpolation window
1228  * @param du horizontal relative coordinate
1229  * @param dv vertical relative coordinate
1230  */
1231 static void xyz_to_cube1x6(const V360Context *s,
1232                            const float *vec, int width, int height,
1233                            uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1234 {
1235     const float eh = height / 6.f;
1236     const int ewi = width;
1237     float uf, vf;
1238     int ui, vi;
1239     int ehi;
1240     int i, j;
1241     int direction, face;
1242
1243     xyz_to_cube(s, vec, &uf, &vf, &direction);
1244
1245     uf *= (1.f - s->in_pad);
1246     vf *= (1.f - s->in_pad);
1247
1248     face = s->in_cubemap_face_order[direction];
1249     ehi = ceilf(eh * (face + 1)) - ceilf(eh * face);
1250
1251     uf = 0.5f * ewi * (uf + 1.f);
1252     vf = 0.5f * ehi * (vf + 1.f);
1253
1254     ui = floorf(uf);
1255     vi = floorf(vf);
1256
1257     *du = uf - ui;
1258     *dv = vf - vi;
1259
1260     for (i = -1; i < 3; i++) {
1261         for (j = -1; j < 3; j++) {
1262             int new_ui = ui + j;
1263             int new_vi = vi + i;
1264             int v_shift;
1265             int new_ehi;
1266
1267             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1268                 face = s->in_cubemap_face_order[direction];
1269
1270                 v_shift = ceilf(eh * face);
1271             } else {
1272                 uf = 2.f * new_ui / ewi - 1.f;
1273                 vf = 2.f * new_vi / ehi - 1.f;
1274
1275                 uf /= (1.f - s->in_pad);
1276                 vf /= (1.f - s->in_pad);
1277
1278                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1279
1280                 uf *= (1.f - s->in_pad);
1281                 vf *= (1.f - s->in_pad);
1282
1283                 v_shift = ceilf(eh * face);
1284                 new_ehi = ceilf(eh * (face + 1)) - v_shift;
1285
1286                 new_ui = av_clip(roundf(0.5f *     ewi * (uf + 1.f)), 0,     ewi - 1);
1287                 new_vi = av_clip(roundf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1288             }
1289
1290             us[i + 1][j + 1] =           new_ui;
1291             vs[i + 1][j + 1] = v_shift + new_vi;
1292         }
1293     }
1294 }
1295
1296 /**
1297  * Calculate frame position in cubemap6x1 format for corresponding 3D coordinates on sphere.
1298  *
1299  * @param s filter context
1300  * @param vec coordinates on sphere
1301  * @param width frame width
1302  * @param height frame height
1303  * @param us horizontal coordinates for interpolation window
1304  * @param vs vertical coordinates for interpolation window
1305  * @param du horizontal relative coordinate
1306  * @param dv vertical relative coordinate
1307  */
1308 static void xyz_to_cube6x1(const V360Context *s,
1309                            const float *vec, int width, int height,
1310                            uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1311 {
1312     const float ew = width / 6.f;
1313     const int ehi = height;
1314     float uf, vf;
1315     int ui, vi;
1316     int ewi;
1317     int i, j;
1318     int direction, face;
1319
1320     xyz_to_cube(s, vec, &uf, &vf, &direction);
1321
1322     uf *= (1.f - s->in_pad);
1323     vf *= (1.f - s->in_pad);
1324
1325     face = s->in_cubemap_face_order[direction];
1326     ewi = ceilf(ew * (face + 1)) - ceilf(ew * face);
1327
1328     uf = 0.5f * ewi * (uf + 1.f);
1329     vf = 0.5f * ehi * (vf + 1.f);
1330
1331     ui = floorf(uf);
1332     vi = floorf(vf);
1333
1334     *du = uf - ui;
1335     *dv = vf - vi;
1336
1337     for (i = -1; i < 3; i++) {
1338         for (j = -1; j < 3; j++) {
1339             int new_ui = ui + j;
1340             int new_vi = vi + i;
1341             int u_shift;
1342             int new_ewi;
1343
1344             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1345                 face = s->in_cubemap_face_order[direction];
1346
1347                 u_shift = ceilf(ew * face);
1348             } else {
1349                 uf = 2.f * new_ui / ewi - 1.f;
1350                 vf = 2.f * new_vi / ehi - 1.f;
1351
1352                 uf /= (1.f - s->in_pad);
1353                 vf /= (1.f - s->in_pad);
1354
1355                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1356
1357                 uf *= (1.f - s->in_pad);
1358                 vf *= (1.f - s->in_pad);
1359
1360                 u_shift = ceilf(ew * face);
1361                 new_ewi = ceilf(ew * (face + 1)) - u_shift;
1362
1363                 new_ui = av_clip(roundf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1364                 new_vi = av_clip(roundf(0.5f *     ehi * (vf + 1.f)), 0,     ehi - 1);
1365             }
1366
1367             us[i + 1][j + 1] = u_shift + new_ui;
1368             vs[i + 1][j + 1] =           new_vi;
1369         }
1370     }
1371 }
1372
1373 /**
1374  * Calculate 3D coordinates on sphere for corresponding frame position in equirectangular format.
1375  *
1376  * @param s filter context
1377  * @param i horizontal position on frame [0, width)
1378  * @param j vertical position on frame [0, height)
1379  * @param width frame width
1380  * @param height frame height
1381  * @param vec coordinates on sphere
1382  */
1383 static void equirect_to_xyz(const V360Context *s,
1384                             int i, int j, int width, int height,
1385                             float *vec)
1386 {
1387     const float phi   = ((2.f * i) / width  - 1.f) * M_PI;
1388     const float theta = ((2.f * j) / height - 1.f) * M_PI_2;
1389
1390     const float sin_phi   = sinf(phi);
1391     const float cos_phi   = cosf(phi);
1392     const float sin_theta = sinf(theta);
1393     const float cos_theta = cosf(theta);
1394
1395     vec[0] =  cos_theta * sin_phi;
1396     vec[1] = -sin_theta;
1397     vec[2] = -cos_theta * cos_phi;
1398 }
1399
1400 /**
1401  * Prepare data for processing stereographic output format.
1402  *
1403  * @param ctx filter context
1404  *
1405  * @return error code
1406  */
1407 static int prepare_stereographic_out(AVFilterContext *ctx)
1408 {
1409     V360Context *s = ctx->priv;
1410
1411     const float h_angle = tan(FFMIN(s->h_fov, 359.f) * M_PI / 720.f);
1412     const float v_angle = tan(FFMIN(s->v_fov, 359.f) * M_PI / 720.f);
1413
1414     s->flat_range[0] = h_angle;
1415     s->flat_range[1] = v_angle;
1416
1417     return 0;
1418 }
1419
1420 /**
1421  * Calculate 3D coordinates on sphere for corresponding frame position in stereographic format.
1422  *
1423  * @param s filter context
1424  * @param i horizontal position on frame [0, width)
1425  * @param j vertical position on frame [0, height)
1426  * @param width frame width
1427  * @param height frame height
1428  * @param vec coordinates on sphere
1429  */
1430 static void stereographic_to_xyz(const V360Context *s,
1431                                  int i, int j, int width, int height,
1432                                  float *vec)
1433 {
1434     const float x = ((2.f * i) / width  - 1.f) * s->flat_range[0];
1435     const float y = ((2.f * j) / height - 1.f) * s->flat_range[1];
1436     const float xy = x * x + y * y;
1437
1438     vec[0] = 2.f * x / (1.f + xy);
1439     vec[1] = (-1.f + xy) / (1.f + xy);
1440     vec[2] = 2.f * y / (1.f + xy);
1441
1442     normalize_vector(vec);
1443 }
1444
1445 /**
1446  * Calculate frame position in stereographic format for corresponding 3D coordinates on sphere.
1447  *
1448  * @param s filter context
1449  * @param vec coordinates on sphere
1450  * @param width frame width
1451  * @param height frame height
1452  * @param us horizontal coordinates for interpolation window
1453  * @param vs vertical coordinates for interpolation window
1454  * @param du horizontal relative coordinate
1455  * @param dv vertical relative coordinate
1456  */
1457 static void xyz_to_stereographic(const V360Context *s,
1458                                  const float *vec, int width, int height,
1459                                  uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1460 {
1461     const float x = av_clipf(vec[0] / (1.f - vec[1]), -1.f, 1.f) * s->input_mirror_modifier[0];
1462     const float y = av_clipf(vec[2] / (1.f - vec[1]), -1.f, 1.f) * s->input_mirror_modifier[1];
1463     float uf, vf;
1464     int ui, vi;
1465     int i, j;
1466
1467     uf = (x + 1.f) * width  / 2.f;
1468     vf = (y + 1.f) * height / 2.f;
1469     ui = floorf(uf);
1470     vi = floorf(vf);
1471
1472     *du = uf - ui;
1473     *dv = vf - vi;
1474
1475     for (i = -1; i < 3; i++) {
1476         for (j = -1; j < 3; j++) {
1477             us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1478             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1479         }
1480     }
1481 }
1482
1483 /**
1484  * Calculate frame position in equirectangular format for corresponding 3D coordinates on sphere.
1485  *
1486  * @param s filter context
1487  * @param vec coordinates on sphere
1488  * @param width frame width
1489  * @param height frame height
1490  * @param us horizontal coordinates for interpolation window
1491  * @param vs vertical coordinates for interpolation window
1492  * @param du horizontal relative coordinate
1493  * @param dv vertical relative coordinate
1494  */
1495 static void xyz_to_equirect(const V360Context *s,
1496                             const float *vec, int width, int height,
1497                             uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1498 {
1499     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1500     const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
1501     float uf, vf;
1502     int ui, vi;
1503     int i, j;
1504
1505     uf = (phi   / M_PI   + 1.f) * width  / 2.f;
1506     vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1507     ui = floorf(uf);
1508     vi = floorf(vf);
1509
1510     *du = uf - ui;
1511     *dv = vf - vi;
1512
1513     for (i = -1; i < 3; i++) {
1514         for (j = -1; j < 3; j++) {
1515             us[i + 1][j + 1] = mod(ui + j, width);
1516             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1517         }
1518     }
1519 }
1520
1521 /**
1522  * Prepare data for processing equi-angular cubemap input format.
1523  *
1524  * @param ctx filter context
1525
1526  * @return error code
1527  */
1528 static int prepare_eac_in(AVFilterContext *ctx)
1529 {
1530     V360Context *s = ctx->priv;
1531
1532     if (s->ih_flip && s->iv_flip) {
1533         s->in_cubemap_face_order[RIGHT] = BOTTOM_LEFT;
1534         s->in_cubemap_face_order[LEFT]  = BOTTOM_RIGHT;
1535         s->in_cubemap_face_order[UP]    = TOP_LEFT;
1536         s->in_cubemap_face_order[DOWN]  = TOP_RIGHT;
1537         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
1538         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
1539     } else if (s->ih_flip) {
1540         s->in_cubemap_face_order[RIGHT] = TOP_LEFT;
1541         s->in_cubemap_face_order[LEFT]  = TOP_RIGHT;
1542         s->in_cubemap_face_order[UP]    = BOTTOM_LEFT;
1543         s->in_cubemap_face_order[DOWN]  = BOTTOM_RIGHT;
1544         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
1545         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
1546     } else if (s->iv_flip) {
1547         s->in_cubemap_face_order[RIGHT] = BOTTOM_RIGHT;
1548         s->in_cubemap_face_order[LEFT]  = BOTTOM_LEFT;
1549         s->in_cubemap_face_order[UP]    = TOP_RIGHT;
1550         s->in_cubemap_face_order[DOWN]  = TOP_LEFT;
1551         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
1552         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
1553     } else {
1554         s->in_cubemap_face_order[RIGHT] = TOP_RIGHT;
1555         s->in_cubemap_face_order[LEFT]  = TOP_LEFT;
1556         s->in_cubemap_face_order[UP]    = BOTTOM_RIGHT;
1557         s->in_cubemap_face_order[DOWN]  = BOTTOM_LEFT;
1558         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
1559         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
1560     }
1561
1562     if (s->iv_flip) {
1563         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_270;
1564         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_90;
1565         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_270;
1566         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_0;
1567         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_0;
1568         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_0;
1569     } else {
1570         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
1571         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
1572         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
1573         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
1574         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
1575         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
1576     }
1577
1578     return 0;
1579 }
1580
1581 /**
1582  * Prepare data for processing equi-angular cubemap output format.
1583  *
1584  * @param ctx filter context
1585  *
1586  * @return error code
1587  */
1588 static int prepare_eac_out(AVFilterContext *ctx)
1589 {
1590     V360Context *s = ctx->priv;
1591
1592     s->out_cubemap_direction_order[TOP_LEFT]      = LEFT;
1593     s->out_cubemap_direction_order[TOP_MIDDLE]    = FRONT;
1594     s->out_cubemap_direction_order[TOP_RIGHT]     = RIGHT;
1595     s->out_cubemap_direction_order[BOTTOM_LEFT]   = DOWN;
1596     s->out_cubemap_direction_order[BOTTOM_MIDDLE] = BACK;
1597     s->out_cubemap_direction_order[BOTTOM_RIGHT]  = UP;
1598
1599     s->out_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
1600     s->out_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
1601     s->out_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
1602     s->out_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
1603     s->out_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
1604     s->out_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
1605
1606     return 0;
1607 }
1608
1609 /**
1610  * Calculate 3D coordinates on sphere for corresponding frame position in equi-angular cubemap format.
1611  *
1612  * @param s filter context
1613  * @param i horizontal position on frame [0, width)
1614  * @param j vertical position on frame [0, height)
1615  * @param width frame width
1616  * @param height frame height
1617  * @param vec coordinates on sphere
1618  */
1619 static void eac_to_xyz(const V360Context *s,
1620                        int i, int j, int width, int height,
1621                        float *vec)
1622 {
1623     const float pixel_pad = 2;
1624     const float u_pad = pixel_pad / width;
1625     const float v_pad = pixel_pad / height;
1626
1627     int u_face, v_face, face;
1628
1629     float l_x, l_y, l_z;
1630
1631     float uf = (float)i / width;
1632     float vf = (float)j / height;
1633
1634     // EAC has 2-pixel padding on faces except between faces on the same row
1635     // Padding pixels seems not to be stretched with tangent as regular pixels
1636     // Formulas below approximate original padding as close as I could get experimentally
1637
1638     // Horizontal padding
1639     uf = 3.f * (uf - u_pad) / (1.f - 2.f * u_pad);
1640     if (uf < 0.f) {
1641         u_face = 0;
1642         uf -= 0.5f;
1643     } else if (uf >= 3.f) {
1644         u_face = 2;
1645         uf -= 2.5f;
1646     } else {
1647         u_face = floorf(uf);
1648         uf = fmodf(uf, 1.f) - 0.5f;
1649     }
1650
1651     // Vertical padding
1652     v_face = floorf(vf * 2.f);
1653     vf = (vf - v_pad - 0.5f * v_face) / (0.5f - 2.f * v_pad) - 0.5f;
1654
1655     if (uf >= -0.5f && uf < 0.5f) {
1656         uf = tanf(M_PI_2 * uf);
1657     } else {
1658         uf = 2.f * uf;
1659     }
1660     if (vf >= -0.5f && vf < 0.5f) {
1661         vf = tanf(M_PI_2 * vf);
1662     } else {
1663         vf = 2.f * vf;
1664     }
1665
1666     face = u_face + 3 * v_face;
1667
1668     switch (face) {
1669     case TOP_LEFT:
1670         l_x = -1.f;
1671         l_y = -vf;
1672         l_z = -uf;
1673         break;
1674     case TOP_MIDDLE:
1675         l_x =  uf;
1676         l_y = -vf;
1677         l_z = -1.f;
1678         break;
1679     case TOP_RIGHT:
1680         l_x =  1.f;
1681         l_y = -vf;
1682         l_z =  uf;
1683         break;
1684     case BOTTOM_LEFT:
1685         l_x = -vf;
1686         l_y = -1.f;
1687         l_z =  uf;
1688         break;
1689     case BOTTOM_MIDDLE:
1690         l_x = -vf;
1691         l_y =  uf;
1692         l_z =  1.f;
1693         break;
1694     case BOTTOM_RIGHT:
1695         l_x = -vf;
1696         l_y =  1.f;
1697         l_z = -uf;
1698         break;
1699     default:
1700         av_assert0(0);
1701     }
1702
1703     vec[0] = l_x;
1704     vec[1] = l_y;
1705     vec[2] = l_z;
1706
1707     normalize_vector(vec);
1708 }
1709
1710 /**
1711  * Calculate frame position in equi-angular cubemap format for corresponding 3D coordinates on sphere.
1712  *
1713  * @param s filter context
1714  * @param vec coordinates on sphere
1715  * @param width frame width
1716  * @param height frame height
1717  * @param us horizontal coordinates for interpolation window
1718  * @param vs vertical coordinates for interpolation window
1719  * @param du horizontal relative coordinate
1720  * @param dv vertical relative coordinate
1721  */
1722 static void xyz_to_eac(const V360Context *s,
1723                        const float *vec, int width, int height,
1724                        uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1725 {
1726     const float pixel_pad = 2;
1727     const float u_pad = pixel_pad / width;
1728     const float v_pad = pixel_pad / height;
1729
1730     float uf, vf;
1731     int ui, vi;
1732     int i, j;
1733     int direction, face;
1734     int u_face, v_face;
1735
1736     xyz_to_cube(s, vec, &uf, &vf, &direction);
1737
1738     face = s->in_cubemap_face_order[direction];
1739     u_face = face % 3;
1740     v_face = face / 3;
1741
1742     uf = M_2_PI * atanf(uf) + 0.5f;
1743     vf = M_2_PI * atanf(vf) + 0.5f;
1744
1745     // These formulas are inversed from eac_to_xyz ones
1746     uf = (uf + u_face) * (1.f - 2.f * u_pad) / 3.f + u_pad;
1747     vf = vf * (0.5f - 2.f * v_pad) + v_pad + 0.5f * v_face;
1748
1749     uf *= width;
1750     vf *= height;
1751
1752     ui = floorf(uf);
1753     vi = floorf(vf);
1754
1755     *du = uf - ui;
1756     *dv = vf - vi;
1757
1758     for (i = -1; i < 3; i++) {
1759         for (j = -1; j < 3; j++) {
1760             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
1761             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1762         }
1763     }
1764 }
1765
1766 /**
1767  * Prepare data for processing flat output format.
1768  *
1769  * @param ctx filter context
1770  *
1771  * @return error code
1772  */
1773 static int prepare_flat_out(AVFilterContext *ctx)
1774 {
1775     V360Context *s = ctx->priv;
1776
1777     const float h_angle = 0.5f * s->h_fov * M_PI / 180.f;
1778     const float v_angle = 0.5f * s->v_fov * M_PI / 180.f;
1779
1780     s->flat_range[0] =  tan(h_angle);
1781     s->flat_range[1] =  tan(v_angle);
1782     s->flat_range[2] = -1.f;
1783
1784     return 0;
1785 }
1786
1787 /**
1788  * Calculate 3D coordinates on sphere for corresponding frame position in flat format.
1789  *
1790  * @param s filter context
1791  * @param i horizontal position on frame [0, width)
1792  * @param j vertical position on frame [0, height)
1793  * @param width frame width
1794  * @param height frame height
1795  * @param vec coordinates on sphere
1796  */
1797 static void flat_to_xyz(const V360Context *s,
1798                         int i, int j, int width, int height,
1799                         float *vec)
1800 {
1801     const float l_x =  s->flat_range[0] * (2.f * i / width  - 1.f);
1802     const float l_y = -s->flat_range[1] * (2.f * j / height - 1.f);
1803     const float l_z =  s->flat_range[2];
1804
1805     vec[0] = l_x;
1806     vec[1] = l_y;
1807     vec[2] = l_z;
1808
1809     normalize_vector(vec);
1810 }
1811
1812 /**
1813  * Calculate 3D coordinates on sphere for corresponding frame position in dual fisheye format.
1814  *
1815  * @param s filter context
1816  * @param i horizontal position on frame [0, width)
1817  * @param j vertical position on frame [0, height)
1818  * @param width frame width
1819  * @param height frame height
1820  * @param vec coordinates on sphere
1821  */
1822 static void dfisheye_to_xyz(const V360Context *s,
1823                             int i, int j, int width, int height,
1824                             float *vec)
1825 {
1826     const float scale = 1.f + s->out_pad;
1827
1828     const float ew = width / 2.f;
1829     const float eh = height;
1830
1831     const int ei = i >= ew ? i - ew : i;
1832     const float m = i >= ew ? -1.f : 1.f;
1833
1834     const float uf = ((2.f * ei) / ew - 1.f) * scale;
1835     const float vf = ((2.f *  j) / eh - 1.f) * scale;
1836
1837     const float phi   = M_PI + atan2f(vf, uf * m);
1838     const float theta = m * M_PI_2 * (1.f - hypotf(uf, vf));
1839
1840     vec[0] = cosf(theta) * cosf(phi);
1841     vec[1] = cosf(theta) * sinf(phi);
1842     vec[2] = sinf(theta);
1843
1844     normalize_vector(vec);
1845 }
1846
1847 /**
1848  * Calculate frame position in dual fisheye format for corresponding 3D coordinates on sphere.
1849  *
1850  * @param s filter context
1851  * @param vec coordinates on sphere
1852  * @param width frame width
1853  * @param height frame height
1854  * @param us horizontal coordinates for interpolation window
1855  * @param vs vertical coordinates for interpolation window
1856  * @param du horizontal relative coordinate
1857  * @param dv vertical relative coordinate
1858  */
1859 static void xyz_to_dfisheye(const V360Context *s,
1860                             const float *vec, int width, int height,
1861                             uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1862 {
1863     const float scale = 1.f - s->in_pad;
1864
1865     const float ew = width / 2.f;
1866     const float eh = height;
1867
1868     const float phi   = atan2f(-vec[1], -vec[0]) * s->input_mirror_modifier[0];
1869     const float theta = acosf(fabsf(vec[2])) / M_PI * s->input_mirror_modifier[1];
1870
1871     float uf = (theta * cosf(phi) * scale + 0.5f) * ew;
1872     float vf = (theta * sinf(phi) * scale + 0.5f) * eh;
1873
1874     int ui, vi;
1875     int u_shift;
1876     int i, j;
1877
1878     if (vec[2] >= 0) {
1879         u_shift = 0;
1880     } else {
1881         u_shift = ceilf(ew);
1882         uf = ew - uf;
1883     }
1884
1885     ui = floorf(uf);
1886     vi = floorf(vf);
1887
1888     *du = uf - ui;
1889     *dv = vf - vi;
1890
1891     for (i = -1; i < 3; i++) {
1892         for (j = -1; j < 3; j++) {
1893             us[i + 1][j + 1] = av_clip(u_shift + ui + j, 0, width  - 1);
1894             vs[i + 1][j + 1] = av_clip(          vi + i, 0, height - 1);
1895         }
1896     }
1897 }
1898
1899 /**
1900  * Calculate 3D coordinates on sphere for corresponding frame position in barrel facebook's format.
1901  *
1902  * @param s filter context
1903  * @param i horizontal position on frame [0, width)
1904  * @param j vertical position on frame [0, height)
1905  * @param width frame width
1906  * @param height frame height
1907  * @param vec coordinates on sphere
1908  */
1909 static void barrel_to_xyz(const V360Context *s,
1910                           int i, int j, int width, int height,
1911                           float *vec)
1912 {
1913     const float scale = 0.99f;
1914     float l_x, l_y, l_z;
1915
1916     if (i < 4 * width / 5) {
1917         const float theta_range = M_PI_4;
1918
1919         const int ew = 4 * width / 5;
1920         const int eh = height;
1921
1922         const float phi   = ((2.f * i) / ew - 1.f) * M_PI        / scale;
1923         const float theta = ((2.f * j) / eh - 1.f) * theta_range / scale;
1924
1925         const float sin_phi   = sinf(phi);
1926         const float cos_phi   = cosf(phi);
1927         const float sin_theta = sinf(theta);
1928         const float cos_theta = cosf(theta);
1929
1930         l_x =  cos_theta * sin_phi;
1931         l_y = -sin_theta;
1932         l_z = -cos_theta * cos_phi;
1933     } else {
1934         const int ew = width  / 5;
1935         const int eh = height / 2;
1936
1937         float uf, vf;
1938
1939         if (j < eh) {   // UP
1940             uf = 2.f * (i - 4 * ew) / ew  - 1.f;
1941             vf = 2.f * (j         ) / eh - 1.f;
1942
1943             uf /= scale;
1944             vf /= scale;
1945
1946             l_x =  uf;
1947             l_y =  1.f;
1948             l_z = -vf;
1949         } else {            // DOWN
1950             uf = 2.f * (i - 4 * ew) / ew - 1.f;
1951             vf = 2.f * (j -     eh) / eh - 1.f;
1952
1953             uf /= scale;
1954             vf /= scale;
1955
1956             l_x =  uf;
1957             l_y = -1.f;
1958             l_z =  vf;
1959         }
1960     }
1961
1962     vec[0] = l_x;
1963     vec[1] = l_y;
1964     vec[2] = l_z;
1965
1966     normalize_vector(vec);
1967 }
1968
1969 /**
1970  * Calculate frame position in barrel facebook's format for corresponding 3D coordinates on sphere.
1971  *
1972  * @param s filter context
1973  * @param vec coordinates on sphere
1974  * @param width frame width
1975  * @param height frame height
1976  * @param us horizontal coordinates for interpolation window
1977  * @param vs vertical coordinates for interpolation window
1978  * @param du horizontal relative coordinate
1979  * @param dv vertical relative coordinate
1980  */
1981 static void xyz_to_barrel(const V360Context *s,
1982                           const float *vec, int width, int height,
1983                           uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1984 {
1985     const float scale = 0.99f;
1986
1987     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1988     const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
1989     const float theta_range = M_PI_4;
1990
1991     int ew, eh;
1992     int u_shift, v_shift;
1993     float uf, vf;
1994     int ui, vi;
1995     int i, j;
1996
1997     if (theta > -theta_range && theta < theta_range) {
1998         ew = 4 * width / 5;
1999         eh = height;
2000
2001         u_shift = s->ih_flip ? width / 5 : 0;
2002         v_shift = 0;
2003
2004         uf = (phi   / M_PI        * scale + 1.f) * ew / 2.f;
2005         vf = (theta / theta_range * scale + 1.f) * eh / 2.f;
2006     } else {
2007         ew = width  / 5;
2008         eh = height / 2;
2009
2010         u_shift = s->ih_flip ? 0 : 4 * ew;
2011
2012         if (theta < 0.f) {  // UP
2013             uf =  vec[0] / vec[1];
2014             vf = -vec[2] / vec[1];
2015             v_shift = 0;
2016         } else {            // DOWN
2017             uf = -vec[0] / vec[1];
2018             vf = -vec[2] / vec[1];
2019             v_shift = eh;
2020         }
2021
2022         uf *= s->input_mirror_modifier[0] * s->input_mirror_modifier[1];
2023         vf *= s->input_mirror_modifier[1];
2024
2025         uf = 0.5f * ew * (uf * scale + 1.f);
2026         vf = 0.5f * eh * (vf * scale + 1.f);
2027     }
2028
2029     ui = floorf(uf);
2030     vi = floorf(vf);
2031
2032     *du = uf - ui;
2033     *dv = vf - vi;
2034
2035     for (i = -1; i < 3; i++) {
2036         for (j = -1; j < 3; j++) {
2037             us[i + 1][j + 1] = u_shift + av_clip(ui + j, 0, ew - 1);
2038             vs[i + 1][j + 1] = v_shift + av_clip(vi + i, 0, eh - 1);
2039         }
2040     }
2041 }
2042
2043 static void multiply_matrix(float c[3][3], const float a[3][3], const float b[3][3])
2044 {
2045     for (int i = 0; i < 3; i++) {
2046         for (int j = 0; j < 3; j++) {
2047             float sum = 0;
2048
2049             for (int k = 0; k < 3; k++)
2050                 sum += a[i][k] * b[k][j];
2051
2052             c[i][j] = sum;
2053         }
2054     }
2055 }
2056
2057 /**
2058  * Calculate rotation matrix for yaw/pitch/roll angles.
2059  */
2060 static inline void calculate_rotation_matrix(float yaw, float pitch, float roll,
2061                                              float rot_mat[3][3],
2062                                              const int rotation_order[3])
2063 {
2064     const float yaw_rad   = yaw   * M_PI / 180.f;
2065     const float pitch_rad = pitch * M_PI / 180.f;
2066     const float roll_rad  = roll  * M_PI / 180.f;
2067
2068     const float sin_yaw   = sinf(-yaw_rad);
2069     const float cos_yaw   = cosf(-yaw_rad);
2070     const float sin_pitch = sinf(pitch_rad);
2071     const float cos_pitch = cosf(pitch_rad);
2072     const float sin_roll  = sinf(roll_rad);
2073     const float cos_roll  = cosf(roll_rad);
2074
2075     float m[3][3][3];
2076     float temp[3][3];
2077
2078     m[0][0][0] =  cos_yaw;  m[0][0][1] = 0;          m[0][0][2] =  sin_yaw;
2079     m[0][1][0] =  0;        m[0][1][1] = 1;          m[0][1][2] =  0;
2080     m[0][2][0] = -sin_yaw;  m[0][2][1] = 0;          m[0][2][2] =  cos_yaw;
2081
2082     m[1][0][0] = 1;         m[1][0][1] = 0;          m[1][0][2] =  0;
2083     m[1][1][0] = 0;         m[1][1][1] = cos_pitch;  m[1][1][2] = -sin_pitch;
2084     m[1][2][0] = 0;         m[1][2][1] = sin_pitch;  m[1][2][2] =  cos_pitch;
2085
2086     m[2][0][0] = cos_roll;  m[2][0][1] = -sin_roll;  m[2][0][2] =  0;
2087     m[2][1][0] = sin_roll;  m[2][1][1] =  cos_roll;  m[2][1][2] =  0;
2088     m[2][2][0] = 0;         m[2][2][1] =  0;         m[2][2][2] =  1;
2089
2090     multiply_matrix(temp, m[rotation_order[0]], m[rotation_order[1]]);
2091     multiply_matrix(rot_mat, temp, m[rotation_order[2]]);
2092 }
2093
2094 /**
2095  * Rotate vector with given rotation matrix.
2096  *
2097  * @param rot_mat rotation matrix
2098  * @param vec vector
2099  */
2100 static inline void rotate(const float rot_mat[3][3],
2101                           float *vec)
2102 {
2103     const float x_tmp = vec[0] * rot_mat[0][0] + vec[1] * rot_mat[0][1] + vec[2] * rot_mat[0][2];
2104     const float y_tmp = vec[0] * rot_mat[1][0] + vec[1] * rot_mat[1][1] + vec[2] * rot_mat[1][2];
2105     const float z_tmp = vec[0] * rot_mat[2][0] + vec[1] * rot_mat[2][1] + vec[2] * rot_mat[2][2];
2106
2107     vec[0] = x_tmp;
2108     vec[1] = y_tmp;
2109     vec[2] = z_tmp;
2110 }
2111
2112 static inline void set_mirror_modifier(int h_flip, int v_flip, int d_flip,
2113                                        float *modifier)
2114 {
2115     modifier[0] = h_flip ? -1.f : 1.f;
2116     modifier[1] = v_flip ? -1.f : 1.f;
2117     modifier[2] = d_flip ? -1.f : 1.f;
2118 }
2119
2120 static inline void mirror(const float *modifier, float *vec)
2121 {
2122     vec[0] *= modifier[0];
2123     vec[1] *= modifier[1];
2124     vec[2] *= modifier[2];
2125 }
2126
2127 static int allocate_plane(V360Context *s, int sizeof_uv, int sizeof_ker, int p)
2128 {
2129     s->u[p] = av_calloc(s->uv_linesize[p] * s->planeheight[p], sizeof_uv);
2130     s->v[p] = av_calloc(s->uv_linesize[p] * s->planeheight[p], sizeof_uv);
2131     if (!s->u[p] || !s->v[p])
2132         return AVERROR(ENOMEM);
2133     if (sizeof_ker) {
2134         s->ker[p] = av_calloc(s->uv_linesize[p] * s->planeheight[p], sizeof_ker);
2135         if (!s->ker[p])
2136             return AVERROR(ENOMEM);
2137     }
2138
2139     return 0;
2140 }
2141
2142 static int config_output(AVFilterLink *outlink)
2143 {
2144     AVFilterContext *ctx = outlink->src;
2145     AVFilterLink *inlink = ctx->inputs[0];
2146     V360Context *s = ctx->priv;
2147     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
2148     const int depth = desc->comp[0].depth;
2149     int sizeof_uv;
2150     int sizeof_ker;
2151     int elements;
2152     int err;
2153     int p, h, w;
2154     float hf, wf;
2155     float output_mirror_modifier[3];
2156     void (*in_transform)(const V360Context *s,
2157                          const float *vec, int width, int height,
2158                          uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv);
2159     void (*out_transform)(const V360Context *s,
2160                           int i, int j, int width, int height,
2161                           float *vec);
2162     void (*calculate_kernel)(float du, float dv, const XYRemap *r_tmp,
2163                              uint16_t *u, uint16_t *v, int16_t *ker);
2164     float rot_mat[3][3];
2165
2166     s->input_mirror_modifier[0] = s->ih_flip ? -1.f : 1.f;
2167     s->input_mirror_modifier[1] = s->iv_flip ? -1.f : 1.f;
2168
2169     switch (s->interp) {
2170     case NEAREST:
2171         calculate_kernel = nearest_kernel;
2172         s->remap_slice = depth <= 8 ? remap1_8bit_slice : remap1_16bit_slice;
2173         elements = 1;
2174         sizeof_uv = sizeof(uint16_t) * elements;
2175         sizeof_ker = 0;
2176         break;
2177     case BILINEAR:
2178         calculate_kernel = bilinear_kernel;
2179         s->remap_slice = depth <= 8 ? remap2_8bit_slice : remap2_16bit_slice;
2180         elements = 2 * 2;
2181         sizeof_uv = sizeof(uint16_t) * elements;
2182         sizeof_ker = sizeof(uint16_t) * elements;
2183         break;
2184     case BICUBIC:
2185         calculate_kernel = bicubic_kernel;
2186         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2187         elements = 4 * 4;
2188         sizeof_uv = sizeof(uint16_t) * elements;
2189         sizeof_ker = sizeof(uint16_t) * elements;
2190         break;
2191     case LANCZOS:
2192         calculate_kernel = lanczos_kernel;
2193         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2194         elements = 4 * 4;
2195         sizeof_uv = sizeof(uint16_t) * elements;
2196         sizeof_ker = sizeof(uint16_t) * elements;
2197         break;
2198     default:
2199         av_assert0(0);
2200     }
2201
2202     ff_v360_init(s, depth);
2203
2204     for (int order = 0; order < NB_RORDERS; order++) {
2205         const char c = s->rorder[order];
2206         int rorder;
2207
2208         if (c == '\0') {
2209             av_log(ctx, AV_LOG_ERROR,
2210                    "Incomplete rorder option. Direction for all 3 rotation orders should be specified.\n");
2211             return AVERROR(EINVAL);
2212         }
2213
2214         rorder = get_rorder(c);
2215         if (rorder == -1) {
2216             av_log(ctx, AV_LOG_ERROR,
2217                    "Incorrect rotation order symbol '%c' in rorder option.\n", c);
2218             return AVERROR(EINVAL);
2219         }
2220
2221         s->rotation_order[order] = rorder;
2222     }
2223
2224     switch (s->in) {
2225     case EQUIRECTANGULAR:
2226         in_transform = xyz_to_equirect;
2227         err = 0;
2228         wf = inlink->w;
2229         hf = inlink->h;
2230         break;
2231     case CUBEMAP_3_2:
2232         in_transform = xyz_to_cube3x2;
2233         err = prepare_cube_in(ctx);
2234         wf = inlink->w / 3.f * 4.f;
2235         hf = inlink->h;
2236         break;
2237     case CUBEMAP_1_6:
2238         in_transform = xyz_to_cube1x6;
2239         err = prepare_cube_in(ctx);
2240         wf = inlink->w * 4.f;
2241         hf = inlink->h / 3.f;
2242         break;
2243     case CUBEMAP_6_1:
2244         in_transform = xyz_to_cube6x1;
2245         err = prepare_cube_in(ctx);
2246         wf = inlink->w / 3.f * 2.f;
2247         hf = inlink->h * 2.f;
2248         break;
2249     case EQUIANGULAR:
2250         in_transform = xyz_to_eac;
2251         err = prepare_eac_in(ctx);
2252         wf = inlink->w;
2253         hf = inlink->h / 9.f * 8.f;
2254         break;
2255     case FLAT:
2256         av_log(ctx, AV_LOG_ERROR, "Flat format is not accepted as input.\n");
2257         return AVERROR(EINVAL);
2258     case DUAL_FISHEYE:
2259         in_transform = xyz_to_dfisheye;
2260         err = 0;
2261         wf = inlink->w;
2262         hf = inlink->h;
2263         break;
2264     case BARREL:
2265         in_transform = xyz_to_barrel;
2266         err = 0;
2267         wf = inlink->w / 5.f * 4.f;
2268         hf = inlink->h;
2269         break;
2270     case STEREOGRAPHIC:
2271         in_transform = xyz_to_stereographic;
2272         err = 0;
2273         wf = inlink->w;
2274         hf = inlink->h / 2.f;
2275         break;
2276     default:
2277         av_log(ctx, AV_LOG_ERROR, "Specified input format is not handled.\n");
2278         return AVERROR_BUG;
2279     }
2280
2281     if (err != 0) {
2282         return err;
2283     }
2284
2285     switch (s->out) {
2286     case EQUIRECTANGULAR:
2287         out_transform = equirect_to_xyz;
2288         err = 0;
2289         w = roundf(wf);
2290         h = roundf(hf);
2291         break;
2292     case CUBEMAP_3_2:
2293         out_transform = cube3x2_to_xyz;
2294         err = prepare_cube_out(ctx);
2295         w = roundf(wf / 4.f * 3.f);
2296         h = roundf(hf);
2297         break;
2298     case CUBEMAP_1_6:
2299         out_transform = cube1x6_to_xyz;
2300         err = prepare_cube_out(ctx);
2301         w = roundf(wf / 4.f);
2302         h = roundf(hf * 3.f);
2303         break;
2304     case CUBEMAP_6_1:
2305         out_transform = cube6x1_to_xyz;
2306         err = prepare_cube_out(ctx);
2307         w = roundf(wf / 2.f * 3.f);
2308         h = roundf(hf / 2.f);
2309         break;
2310     case EQUIANGULAR:
2311         out_transform = eac_to_xyz;
2312         err = prepare_eac_out(ctx);
2313         w = roundf(wf);
2314         h = roundf(hf / 8.f * 9.f);
2315         break;
2316     case FLAT:
2317         out_transform = flat_to_xyz;
2318         err = prepare_flat_out(ctx);
2319         w = roundf(wf);
2320         h = roundf(hf);
2321         break;
2322     case DUAL_FISHEYE:
2323         out_transform = dfisheye_to_xyz;
2324         err = 0;
2325         w = roundf(wf);
2326         h = roundf(hf);
2327         break;
2328     case BARREL:
2329         out_transform = barrel_to_xyz;
2330         err = 0;
2331         w = roundf(wf / 4.f * 5.f);
2332         h = roundf(hf);
2333         break;
2334     case STEREOGRAPHIC:
2335         out_transform = stereographic_to_xyz;
2336         err = prepare_stereographic_out(ctx);
2337         w = roundf(wf);
2338         h = roundf(hf * 2.f);
2339         break;
2340     default:
2341         av_log(ctx, AV_LOG_ERROR, "Specified output format is not handled.\n");
2342         return AVERROR_BUG;
2343     }
2344
2345     if (err != 0) {
2346         return err;
2347     }
2348
2349     // Override resolution with user values if specified
2350     if (s->width > 0 && s->height > 0) {
2351         w = s->width;
2352         h = s->height;
2353     } else if (s->width > 0 || s->height > 0) {
2354         av_log(ctx, AV_LOG_ERROR, "Both width and height values should be specified.\n");
2355         return AVERROR(EINVAL);
2356     } else {
2357         if (s->out_transpose)
2358             FFSWAP(int, w, h);
2359
2360         if (s->in_transpose)
2361             FFSWAP(int, w, h);
2362     }
2363
2364     s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
2365     s->planeheight[0] = s->planeheight[3] = h;
2366     s->planewidth[1]  = s->planewidth[2] = FF_CEIL_RSHIFT(w, desc->log2_chroma_w);
2367     s->planewidth[0]  = s->planewidth[3] = w;
2368
2369     for (int i = 0; i < 4; i++)
2370         s->uv_linesize[i] = FFALIGN(s->planewidth[i], 8);
2371
2372     outlink->h = h;
2373     outlink->w = w;
2374
2375     s->inplaneheight[1] = s->inplaneheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
2376     s->inplaneheight[0] = s->inplaneheight[3] = inlink->h;
2377     s->inplanewidth[1]  = s->inplanewidth[2]  = FF_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
2378     s->inplanewidth[0]  = s->inplanewidth[3]  = inlink->w;
2379     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
2380
2381     if (desc->log2_chroma_h == desc->log2_chroma_w && desc->log2_chroma_h == 0) {
2382         s->nb_allocated = 1;
2383         s->map[0] = s->map[1] = s->map[2] = s->map[3] = 0;
2384         allocate_plane(s, sizeof_uv, sizeof_ker, 0);
2385     } else {
2386         s->nb_allocated = 2;
2387         s->map[0] = 0;
2388         s->map[1] = s->map[2] = 1;
2389         s->map[3] = 0;
2390         allocate_plane(s, sizeof_uv, sizeof_ker, 0);
2391         allocate_plane(s, sizeof_uv, sizeof_ker, 1);
2392     }
2393
2394     calculate_rotation_matrix(s->yaw, s->pitch, s->roll, rot_mat, s->rotation_order);
2395     set_mirror_modifier(s->h_flip, s->v_flip, s->d_flip, output_mirror_modifier);
2396
2397     // Calculate remap data
2398     for (p = 0; p < s->nb_allocated; p++) {
2399         const int width = s->planewidth[p];
2400         const int uv_linesize = s->uv_linesize[p];
2401         const int height = s->planeheight[p];
2402         const int in_width = s->inplanewidth[p];
2403         const int in_height = s->inplaneheight[p];
2404         float du, dv;
2405         float vec[3];
2406         XYRemap r_tmp;
2407         int i, j;
2408
2409         for (i = 0; i < width; i++) {
2410             for (j = 0; j < height; j++) {
2411                 uint16_t *u = s->u[p] + (j * uv_linesize + i) * elements;
2412                 uint16_t *v = s->v[p] + (j * uv_linesize + i) * elements;
2413                 int16_t *ker = s->ker[p] + (j * uv_linesize + i) * elements;
2414
2415                 if (s->out_transpose)
2416                     out_transform(s, j, i, height, width, vec);
2417                 else
2418                     out_transform(s, i, j, width, height, vec);
2419                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
2420                 rotate(rot_mat, vec);
2421                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
2422                 normalize_vector(vec);
2423                 mirror(output_mirror_modifier, vec);
2424                 if (s->in_transpose)
2425                     in_transform(s, vec, in_height, in_width, r_tmp.v, r_tmp.u, &du, &dv);
2426                 else
2427                     in_transform(s, vec, in_width, in_height, r_tmp.u, r_tmp.v, &du, &dv);
2428                 av_assert1(!isnan(du) && !isnan(dv));
2429                 calculate_kernel(du, dv, &r_tmp, u, v, ker);
2430             }
2431         }
2432     }
2433
2434     return 0;
2435 }
2436
2437 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
2438 {
2439     AVFilterContext *ctx = inlink->dst;
2440     AVFilterLink *outlink = ctx->outputs[0];
2441     V360Context *s = ctx->priv;
2442     AVFrame *out;
2443     ThreadData td;
2444
2445     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
2446     if (!out) {
2447         av_frame_free(&in);
2448         return AVERROR(ENOMEM);
2449     }
2450     av_frame_copy_props(out, in);
2451
2452     td.in = in;
2453     td.out = out;
2454
2455     ctx->internal->execute(ctx, s->remap_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
2456
2457     av_frame_free(&in);
2458     return ff_filter_frame(outlink, out);
2459 }
2460
2461 static av_cold void uninit(AVFilterContext *ctx)
2462 {
2463     V360Context *s = ctx->priv;
2464     int p;
2465
2466     for (p = 0; p < s->nb_allocated; p++) {
2467         av_freep(&s->u[p]);
2468         av_freep(&s->v[p]);
2469         av_freep(&s->ker[p]);
2470     }
2471 }
2472
2473 static const AVFilterPad inputs[] = {
2474     {
2475         .name         = "default",
2476         .type         = AVMEDIA_TYPE_VIDEO,
2477         .filter_frame = filter_frame,
2478     },
2479     { NULL }
2480 };
2481
2482 static const AVFilterPad outputs[] = {
2483     {
2484         .name         = "default",
2485         .type         = AVMEDIA_TYPE_VIDEO,
2486         .config_props = config_output,
2487     },
2488     { NULL }
2489 };
2490
2491 AVFilter ff_vf_v360 = {
2492     .name          = "v360",
2493     .description   = NULL_IF_CONFIG_SMALL("Convert 360 projection of video."),
2494     .priv_size     = sizeof(V360Context),
2495     .uninit        = uninit,
2496     .query_formats = query_formats,
2497     .inputs        = inputs,
2498     .outputs       = outputs,
2499     .priv_class    = &v360_class,
2500     .flags         = AVFILTER_FLAG_SLICE_THREADS,
2501 };