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