2 * Copyright (c) 2019 Eugene Lyapustin
4 * This file is part of FFmpeg.
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.
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.
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
23 * 360 video conversion filter.
24 * Principle of operation:
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
33 * 5) Remap input frame to output frame using precalculated data
38 #include "libavutil/avassert.h"
39 #include "libavutil/imgutils.h"
40 #include "libavutil/pixdesc.h"
41 #include "libavutil/opt.h"
48 typedef struct ThreadData {
53 #define OFFSET(x) offsetof(V360Context, x)
54 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
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 { "mercator", "mercator", 0, AV_OPT_TYPE_CONST, {.i64=MERCATOR}, 0, 0, FLAGS, "in" },
69 { "ball", "ball", 0, AV_OPT_TYPE_CONST, {.i64=BALL}, 0, 0, FLAGS, "in" },
70 { "hammer", "hammer", 0, AV_OPT_TYPE_CONST, {.i64=HAMMER}, 0, 0, FLAGS, "in" },
71 {"sinusoidal", "sinusoidal", 0, AV_OPT_TYPE_CONST, {.i64=SINUSOIDAL}, 0, 0, FLAGS, "in" },
72 { "output", "set output projection", OFFSET(out), AV_OPT_TYPE_INT, {.i64=CUBEMAP_3_2}, 0, NB_PROJECTIONS-1, FLAGS, "out" },
73 { "e", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "out" },
74 { "equirect", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "out" },
75 { "c3x2", "cubemap 3x2", 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_3_2}, 0, 0, FLAGS, "out" },
76 { "c6x1", "cubemap 6x1", 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_6_1}, 0, 0, FLAGS, "out" },
77 { "eac", "equi-angular cubemap", 0, AV_OPT_TYPE_CONST, {.i64=EQUIANGULAR}, 0, 0, FLAGS, "out" },
78 { "dfisheye", "dual fisheye", 0, AV_OPT_TYPE_CONST, {.i64=DUAL_FISHEYE}, 0, 0, FLAGS, "out" },
79 { "flat", "regular video", 0, AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, 0, FLAGS, "out" },
80 {"rectilinear", "regular video", 0, AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, 0, FLAGS, "out" },
81 { "gnomonic", "regular video", 0, AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, 0, FLAGS, "out" },
82 { "barrel", "barrel facebook's 360 format", 0, AV_OPT_TYPE_CONST, {.i64=BARREL}, 0, 0, FLAGS, "out" },
83 { "fb", "barrel facebook's 360 format", 0, AV_OPT_TYPE_CONST, {.i64=BARREL}, 0, 0, FLAGS, "out" },
84 { "c1x6", "cubemap 1x6", 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_1_6}, 0, 0, FLAGS, "out" },
85 { "sg", "stereographic", 0, AV_OPT_TYPE_CONST, {.i64=STEREOGRAPHIC}, 0, 0, FLAGS, "out" },
86 { "mercator", "mercator", 0, AV_OPT_TYPE_CONST, {.i64=MERCATOR}, 0, 0, FLAGS, "out" },
87 { "ball", "ball", 0, AV_OPT_TYPE_CONST, {.i64=BALL}, 0, 0, FLAGS, "out" },
88 { "hammer", "hammer", 0, AV_OPT_TYPE_CONST, {.i64=HAMMER}, 0, 0, FLAGS, "out" },
89 {"sinusoidal", "sinusoidal", 0, AV_OPT_TYPE_CONST, {.i64=SINUSOIDAL}, 0, 0, FLAGS, "out" },
90 { "fisheye", "fisheye", 0, AV_OPT_TYPE_CONST, {.i64=FISHEYE}, 0, 0, FLAGS, "out" },
91 { "pannini", "pannini", 0, AV_OPT_TYPE_CONST, {.i64=PANNINI}, 0, 0, FLAGS, "out" },
92 {"cylindrical", "cylindrical", 0, AV_OPT_TYPE_CONST, {.i64=CYLINDRICAL}, 0, 0, FLAGS, "out" },
93 {"perspective", "perspective", 0, AV_OPT_TYPE_CONST, {.i64=PERSPECTIVE}, 0, 0, FLAGS, "out" },
94 { "interp", "set interpolation method", OFFSET(interp), AV_OPT_TYPE_INT, {.i64=BILINEAR}, 0, NB_INTERP_METHODS-1, FLAGS, "interp" },
95 { "near", "nearest neighbour", 0, AV_OPT_TYPE_CONST, {.i64=NEAREST}, 0, 0, FLAGS, "interp" },
96 { "nearest", "nearest neighbour", 0, AV_OPT_TYPE_CONST, {.i64=NEAREST}, 0, 0, FLAGS, "interp" },
97 { "line", "bilinear interpolation", 0, AV_OPT_TYPE_CONST, {.i64=BILINEAR}, 0, 0, FLAGS, "interp" },
98 { "linear", "bilinear interpolation", 0, AV_OPT_TYPE_CONST, {.i64=BILINEAR}, 0, 0, FLAGS, "interp" },
99 { "cube", "bicubic interpolation", 0, AV_OPT_TYPE_CONST, {.i64=BICUBIC}, 0, 0, FLAGS, "interp" },
100 { "cubic", "bicubic interpolation", 0, AV_OPT_TYPE_CONST, {.i64=BICUBIC}, 0, 0, FLAGS, "interp" },
101 { "lanc", "lanczos interpolation", 0, AV_OPT_TYPE_CONST, {.i64=LANCZOS}, 0, 0, FLAGS, "interp" },
102 { "lanczos", "lanczos interpolation", 0, AV_OPT_TYPE_CONST, {.i64=LANCZOS}, 0, 0, FLAGS, "interp" },
103 { "sp16", "spline16 interpolation", 0, AV_OPT_TYPE_CONST, {.i64=SPLINE16}, 0, 0, FLAGS, "interp" },
104 { "spline16", "spline16 interpolation", 0, AV_OPT_TYPE_CONST, {.i64=SPLINE16}, 0, 0, FLAGS, "interp" },
105 { "gauss", "gaussian interpolation", 0, AV_OPT_TYPE_CONST, {.i64=GAUSSIAN}, 0, 0, FLAGS, "interp" },
106 { "gaussian", "gaussian interpolation", 0, AV_OPT_TYPE_CONST, {.i64=GAUSSIAN}, 0, 0, FLAGS, "interp" },
107 { "w", "output width", OFFSET(width), AV_OPT_TYPE_INT, {.i64=0}, 0, INT16_MAX, FLAGS, "w"},
108 { "h", "output height", OFFSET(height), AV_OPT_TYPE_INT, {.i64=0}, 0, INT16_MAX, FLAGS, "h"},
109 { "in_stereo", "input stereo format", OFFSET(in_stereo), AV_OPT_TYPE_INT, {.i64=STEREO_2D}, 0, NB_STEREO_FMTS-1, FLAGS, "stereo" },
110 {"out_stereo", "output stereo format", OFFSET(out_stereo), AV_OPT_TYPE_INT, {.i64=STEREO_2D}, 0, NB_STEREO_FMTS-1, FLAGS, "stereo" },
111 { "2d", "2d mono", 0, AV_OPT_TYPE_CONST, {.i64=STEREO_2D}, 0, 0, FLAGS, "stereo" },
112 { "sbs", "side by side", 0, AV_OPT_TYPE_CONST, {.i64=STEREO_SBS}, 0, 0, FLAGS, "stereo" },
113 { "tb", "top bottom", 0, AV_OPT_TYPE_CONST, {.i64=STEREO_TB}, 0, 0, FLAGS, "stereo" },
114 { "in_forder", "input cubemap face order", OFFSET(in_forder), AV_OPT_TYPE_STRING, {.str="rludfb"}, 0, NB_DIRECTIONS-1, FLAGS, "in_forder"},
115 {"out_forder", "output cubemap face order", OFFSET(out_forder), AV_OPT_TYPE_STRING, {.str="rludfb"}, 0, NB_DIRECTIONS-1, FLAGS, "out_forder"},
116 { "in_frot", "input cubemap face rotation", OFFSET(in_frot), AV_OPT_TYPE_STRING, {.str="000000"}, 0, NB_DIRECTIONS-1, FLAGS, "in_frot"},
117 { "out_frot", "output cubemap face rotation",OFFSET(out_frot), AV_OPT_TYPE_STRING, {.str="000000"}, 0, NB_DIRECTIONS-1, FLAGS, "out_frot"},
118 { "in_pad", "percent input cubemap pads", OFFSET(in_pad), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, 0.f, 1.f, FLAGS, "in_pad"},
119 { "out_pad", "percent output cubemap pads", OFFSET(out_pad), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, 0.f, 1.f, FLAGS, "out_pad"},
120 { "fin_pad", "fixed input cubemap pads", OFFSET(fin_pad), AV_OPT_TYPE_INT, {.i64=0}, 0, 100, FLAGS, "fin_pad"},
121 { "fout_pad", "fixed output cubemap pads", OFFSET(fout_pad), AV_OPT_TYPE_INT, {.i64=0}, 0, 100, FLAGS, "fout_pad"},
122 { "yaw", "yaw rotation", OFFSET(yaw), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, 180.f, FLAGS, "yaw"},
123 { "pitch", "pitch rotation", OFFSET(pitch), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, 180.f, FLAGS, "pitch"},
124 { "roll", "roll rotation", OFFSET(roll), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, 180.f, FLAGS, "roll"},
125 { "rorder", "rotation order", OFFSET(rorder), AV_OPT_TYPE_STRING, {.str="ypr"}, 0, 0, FLAGS, "rorder"},
126 { "h_fov", "horizontal field of view", OFFSET(h_fov), AV_OPT_TYPE_FLOAT, {.dbl=90.f}, 0.00001f, 360.f, FLAGS, "h_fov"},
127 { "v_fov", "vertical field of view", OFFSET(v_fov), AV_OPT_TYPE_FLOAT, {.dbl=45.f}, 0.00001f, 360.f, FLAGS, "v_fov"},
128 { "d_fov", "diagonal field of view", OFFSET(d_fov), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, 0.f, 360.f, FLAGS, "d_fov"},
129 { "h_flip", "flip out video horizontally", OFFSET(h_flip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "h_flip"},
130 { "v_flip", "flip out video vertically", OFFSET(v_flip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "v_flip"},
131 { "d_flip", "flip out video indepth", OFFSET(d_flip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "d_flip"},
132 { "ih_flip", "flip in video horizontally", OFFSET(ih_flip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "ih_flip"},
133 { "iv_flip", "flip in video vertically", OFFSET(iv_flip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "iv_flip"},
134 { "in_trans", "transpose video input", OFFSET(in_transpose), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "in_transpose"},
135 { "out_trans", "transpose video output", OFFSET(out_transpose), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS, "out_transpose"},
139 AVFILTER_DEFINE_CLASS(v360);
141 static int query_formats(AVFilterContext *ctx)
143 static const enum AVPixelFormat pix_fmts[] = {
145 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P9,
146 AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
147 AV_PIX_FMT_YUVA444P16,
150 AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA422P9,
151 AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
152 AV_PIX_FMT_YUVA422P16,
155 AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P9,
156 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
159 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
160 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
164 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P9,
165 AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12,
166 AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16,
169 AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV440P10,
170 AV_PIX_FMT_YUV440P12,
173 AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P9,
174 AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12,
175 AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV422P16,
178 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P9,
179 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12,
180 AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV420P16,
189 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9,
190 AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12,
191 AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
194 AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10,
195 AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
198 AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9,
199 AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12,
200 AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
205 AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
207 return AVERROR(ENOMEM);
208 return ff_set_common_formats(ctx, fmts_list);
211 #define DEFINE_REMAP1_LINE(bits, div) \
212 static void remap1_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
213 ptrdiff_t in_linesize, \
214 const uint16_t *const u, const uint16_t *const v, \
215 const int16_t *const ker) \
217 const uint##bits##_t *const s = (const uint##bits##_t *const)src; \
218 uint##bits##_t *d = (uint##bits##_t *)dst; \
220 in_linesize /= div; \
222 for (int x = 0; x < width; x++) \
223 d[x] = s[v[x] * in_linesize + u[x]]; \
226 DEFINE_REMAP1_LINE( 8, 1)
227 DEFINE_REMAP1_LINE(16, 2)
230 * Generate remapping function with a given window size and pixel depth.
232 * @param ws size of interpolation window
233 * @param bits number of bits per pixel
235 #define DEFINE_REMAP(ws, bits) \
236 static int remap##ws##_##bits##bit_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
238 ThreadData *td = arg; \
239 const V360Context *s = ctx->priv; \
240 const AVFrame *in = td->in; \
241 AVFrame *out = td->out; \
243 for (int stereo = 0; stereo < 1 + s->out_stereo > STEREO_2D; stereo++) { \
244 for (int plane = 0; plane < s->nb_planes; plane++) { \
245 const unsigned map = s->map[plane]; \
246 const int in_linesize = in->linesize[plane]; \
247 const int out_linesize = out->linesize[plane]; \
248 const int uv_linesize = s->uv_linesize[plane]; \
249 const int in_offset_w = stereo ? s->in_offset_w[plane] : 0; \
250 const int in_offset_h = stereo ? s->in_offset_h[plane] : 0; \
251 const int out_offset_w = stereo ? s->out_offset_w[plane] : 0; \
252 const int out_offset_h = stereo ? s->out_offset_h[plane] : 0; \
253 const uint8_t *const src = in->data[plane] + \
254 in_offset_h * in_linesize + in_offset_w * (bits >> 3); \
255 uint8_t *dst = out->data[plane] + out_offset_h * out_linesize + out_offset_w * (bits >> 3); \
256 const int width = s->pr_width[plane]; \
257 const int height = s->pr_height[plane]; \
259 const int slice_start = (height * jobnr ) / nb_jobs; \
260 const int slice_end = (height * (jobnr + 1)) / nb_jobs; \
262 for (int y = slice_start; y < slice_end; y++) { \
263 const uint16_t *const u = s->u[map] + y * uv_linesize * ws * ws; \
264 const uint16_t *const v = s->v[map] + y * uv_linesize * ws * ws; \
265 const int16_t *const ker = s->ker[map] + y * uv_linesize * ws * ws; \
267 s->remap_line(dst + y * out_linesize, width, src, in_linesize, u, v, ker); \
282 #define DEFINE_REMAP_LINE(ws, bits, div) \
283 static void remap##ws##_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
284 ptrdiff_t in_linesize, \
285 const uint16_t *const u, const uint16_t *const v, \
286 const int16_t *const ker) \
288 const uint##bits##_t *const s = (const uint##bits##_t *const)src; \
289 uint##bits##_t *d = (uint##bits##_t *)dst; \
291 in_linesize /= div; \
293 for (int x = 0; x < width; x++) { \
294 const uint16_t *const uu = u + x * ws * ws; \
295 const uint16_t *const vv = v + x * ws * ws; \
296 const int16_t *const kker = ker + x * ws * ws; \
299 for (int i = 0; i < ws; i++) { \
300 for (int j = 0; j < ws; j++) { \
301 tmp += kker[i * ws + j] * s[vv[i * ws + j] * in_linesize + uu[i * ws + j]]; \
305 d[x] = av_clip_uint##bits(tmp >> 14); \
309 DEFINE_REMAP_LINE(2, 8, 1)
310 DEFINE_REMAP_LINE(4, 8, 1)
311 DEFINE_REMAP_LINE(2, 16, 2)
312 DEFINE_REMAP_LINE(4, 16, 2)
314 void ff_v360_init(V360Context *s, int depth)
318 s->remap_line = depth <= 8 ? remap1_8bit_line_c : remap1_16bit_line_c;
321 s->remap_line = depth <= 8 ? remap2_8bit_line_c : remap2_16bit_line_c;
327 s->remap_line = depth <= 8 ? remap4_8bit_line_c : remap4_16bit_line_c;
332 ff_v360_init_x86(s, depth);
336 * Save nearest pixel coordinates for remapping.
338 * @param du horizontal relative coordinate
339 * @param dv vertical relative coordinate
340 * @param rmap calculated 4x4 window
341 * @param u u remap data
342 * @param v v remap data
343 * @param ker ker remap data
345 static void nearest_kernel(float du, float dv, const XYRemap *rmap,
346 uint16_t *u, uint16_t *v, int16_t *ker)
348 const int i = roundf(dv) + 1;
349 const int j = roundf(du) + 1;
351 u[0] = rmap->u[i][j];
352 v[0] = rmap->v[i][j];
356 * Calculate kernel for bilinear interpolation.
358 * @param du horizontal relative coordinate
359 * @param dv vertical relative coordinate
360 * @param rmap calculated 4x4 window
361 * @param u u remap data
362 * @param v v remap data
363 * @param ker ker remap data
365 static void bilinear_kernel(float du, float dv, const XYRemap *rmap,
366 uint16_t *u, uint16_t *v, int16_t *ker)
368 for (int i = 0; i < 2; i++) {
369 for (int j = 0; j < 2; j++) {
370 u[i * 2 + j] = rmap->u[i + 1][j + 1];
371 v[i * 2 + j] = rmap->v[i + 1][j + 1];
375 ker[0] = lrintf((1.f - du) * (1.f - dv) * 16385.f);
376 ker[1] = lrintf( du * (1.f - dv) * 16385.f);
377 ker[2] = lrintf((1.f - du) * dv * 16385.f);
378 ker[3] = lrintf( du * dv * 16385.f);
382 * Calculate 1-dimensional cubic coefficients.
384 * @param t relative coordinate
385 * @param coeffs coefficients
387 static inline void calculate_bicubic_coeffs(float t, float *coeffs)
389 const float tt = t * t;
390 const float ttt = t * t * t;
392 coeffs[0] = - t / 3.f + tt / 2.f - ttt / 6.f;
393 coeffs[1] = 1.f - t / 2.f - tt + ttt / 2.f;
394 coeffs[2] = t + tt / 2.f - ttt / 2.f;
395 coeffs[3] = - t / 6.f + ttt / 6.f;
399 * Calculate kernel for bicubic interpolation.
401 * @param du horizontal relative coordinate
402 * @param dv vertical relative coordinate
403 * @param rmap calculated 4x4 window
404 * @param u u remap data
405 * @param v v remap data
406 * @param ker ker remap data
408 static void bicubic_kernel(float du, float dv, const XYRemap *rmap,
409 uint16_t *u, uint16_t *v, int16_t *ker)
414 calculate_bicubic_coeffs(du, du_coeffs);
415 calculate_bicubic_coeffs(dv, dv_coeffs);
417 for (int i = 0; i < 4; i++) {
418 for (int j = 0; j < 4; j++) {
419 u[i * 4 + j] = rmap->u[i][j];
420 v[i * 4 + j] = rmap->v[i][j];
421 ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
427 * Calculate 1-dimensional lanczos coefficients.
429 * @param t relative coordinate
430 * @param coeffs coefficients
432 static inline void calculate_lanczos_coeffs(float t, float *coeffs)
436 for (int i = 0; i < 4; i++) {
437 const float x = M_PI * (t - i + 1);
441 coeffs[i] = sinf(x) * sinf(x / 2.f) / (x * x / 2.f);
446 for (int i = 0; i < 4; i++) {
452 * Calculate kernel for lanczos interpolation.
454 * @param du horizontal relative coordinate
455 * @param dv vertical relative coordinate
456 * @param rmap calculated 4x4 window
457 * @param u u remap data
458 * @param v v remap data
459 * @param ker ker remap data
461 static void lanczos_kernel(float du, float dv, const XYRemap *rmap,
462 uint16_t *u, uint16_t *v, int16_t *ker)
467 calculate_lanczos_coeffs(du, du_coeffs);
468 calculate_lanczos_coeffs(dv, dv_coeffs);
470 for (int i = 0; i < 4; i++) {
471 for (int j = 0; j < 4; j++) {
472 u[i * 4 + j] = rmap->u[i][j];
473 v[i * 4 + j] = rmap->v[i][j];
474 ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
480 * Calculate 1-dimensional spline16 coefficients.
482 * @param t relative coordinate
483 * @param coeffs coefficients
485 static void calculate_spline16_coeffs(float t, float *coeffs)
487 coeffs[0] = ((-1.f / 3.f * t + 0.8f) * t - 7.f / 15.f) * t;
488 coeffs[1] = ((t - 9.f / 5.f) * t - 0.2f) * t + 1.f;
489 coeffs[2] = ((6.f / 5.f - t) * t + 0.8f) * t;
490 coeffs[3] = ((1.f / 3.f * t - 0.2f) * t - 2.f / 15.f) * t;
494 * Calculate kernel for spline16 interpolation.
496 * @param du horizontal relative coordinate
497 * @param dv vertical relative coordinate
498 * @param rmap calculated 4x4 window
499 * @param u u remap data
500 * @param v v remap data
501 * @param ker ker remap data
503 static void spline16_kernel(float du, float dv, const XYRemap *rmap,
504 uint16_t *u, uint16_t *v, int16_t *ker)
509 calculate_spline16_coeffs(du, du_coeffs);
510 calculate_spline16_coeffs(dv, dv_coeffs);
512 for (int i = 0; i < 4; i++) {
513 for (int j = 0; j < 4; j++) {
514 u[i * 4 + j] = rmap->u[i][j];
515 v[i * 4 + j] = rmap->v[i][j];
516 ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
522 * Calculate 1-dimensional gaussian coefficients.
524 * @param t relative coordinate
525 * @param coeffs coefficients
527 static void calculate_gaussian_coeffs(float t, float *coeffs)
531 for (int i = 0; i < 4; i++) {
532 const float x = t - (i - 1);
536 coeffs[i] = expf(-2.f * x * x) * expf(-x * x / 2.f);
541 for (int i = 0; i < 4; i++) {
547 * Calculate kernel for gaussian interpolation.
549 * @param du horizontal relative coordinate
550 * @param dv vertical relative coordinate
551 * @param rmap calculated 4x4 window
552 * @param u u remap data
553 * @param v v remap data
554 * @param ker ker remap data
556 static void gaussian_kernel(float du, float dv, const XYRemap *rmap,
557 uint16_t *u, uint16_t *v, int16_t *ker)
562 calculate_gaussian_coeffs(du, du_coeffs);
563 calculate_gaussian_coeffs(dv, dv_coeffs);
565 for (int i = 0; i < 4; i++) {
566 for (int j = 0; j < 4; j++) {
567 u[i * 4 + j] = rmap->u[i][j];
568 v[i * 4 + j] = rmap->v[i][j];
569 ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
575 * Modulo operation with only positive remainders.
580 * @return positive remainder of (a / b)
582 static inline int mod(int a, int b)
584 const int res = a % b;
593 * Convert char to corresponding direction.
594 * Used for cubemap options.
596 static int get_direction(char c)
617 * Convert char to corresponding rotation angle.
618 * Used for cubemap options.
620 static int get_rotation(char c)
637 * Convert char to corresponding rotation order.
639 static int get_rorder(char c)
657 * Prepare data for processing cubemap input format.
659 * @param ctx filter context
663 static int prepare_cube_in(AVFilterContext *ctx)
665 V360Context *s = ctx->priv;
667 for (int face = 0; face < NB_FACES; face++) {
668 const char c = s->in_forder[face];
672 av_log(ctx, AV_LOG_ERROR,
673 "Incomplete in_forder option. Direction for all 6 faces should be specified.\n");
674 return AVERROR(EINVAL);
677 direction = get_direction(c);
678 if (direction == -1) {
679 av_log(ctx, AV_LOG_ERROR,
680 "Incorrect direction symbol '%c' in in_forder option.\n", c);
681 return AVERROR(EINVAL);
684 s->in_cubemap_face_order[direction] = face;
687 for (int face = 0; face < NB_FACES; face++) {
688 const char c = s->in_frot[face];
692 av_log(ctx, AV_LOG_ERROR,
693 "Incomplete in_frot option. Rotation for all 6 faces should be specified.\n");
694 return AVERROR(EINVAL);
697 rotation = get_rotation(c);
698 if (rotation == -1) {
699 av_log(ctx, AV_LOG_ERROR,
700 "Incorrect rotation symbol '%c' in in_frot option.\n", c);
701 return AVERROR(EINVAL);
704 s->in_cubemap_face_rotation[face] = rotation;
711 * Prepare data for processing cubemap output format.
713 * @param ctx filter context
717 static int prepare_cube_out(AVFilterContext *ctx)
719 V360Context *s = ctx->priv;
721 for (int face = 0; face < NB_FACES; face++) {
722 const char c = s->out_forder[face];
726 av_log(ctx, AV_LOG_ERROR,
727 "Incomplete out_forder option. Direction for all 6 faces should be specified.\n");
728 return AVERROR(EINVAL);
731 direction = get_direction(c);
732 if (direction == -1) {
733 av_log(ctx, AV_LOG_ERROR,
734 "Incorrect direction symbol '%c' in out_forder option.\n", c);
735 return AVERROR(EINVAL);
738 s->out_cubemap_direction_order[face] = direction;
741 for (int face = 0; face < NB_FACES; face++) {
742 const char c = s->out_frot[face];
746 av_log(ctx, AV_LOG_ERROR,
747 "Incomplete out_frot option. Rotation for all 6 faces should be specified.\n");
748 return AVERROR(EINVAL);
751 rotation = get_rotation(c);
752 if (rotation == -1) {
753 av_log(ctx, AV_LOG_ERROR,
754 "Incorrect rotation symbol '%c' in out_frot option.\n", c);
755 return AVERROR(EINVAL);
758 s->out_cubemap_face_rotation[face] = rotation;
764 static inline void rotate_cube_face(float *uf, float *vf, int rotation)
790 static inline void rotate_cube_face_inverse(float *uf, float *vf, int rotation)
821 static void normalize_vector(float *vec)
823 const float norm = sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
831 * Calculate 3D coordinates on sphere for corresponding cubemap position.
832 * Common operation for every cubemap.
834 * @param s filter private context
835 * @param uf horizontal cubemap coordinate [0, 1)
836 * @param vf vertical cubemap coordinate [0, 1)
837 * @param face face of cubemap
838 * @param vec coordinates on sphere
839 * @param scalew scale for uf
840 * @param scaleh scale for vf
842 static void cube_to_xyz(const V360Context *s,
843 float uf, float vf, int face,
844 float *vec, float scalew, float scaleh)
846 const int direction = s->out_cubemap_direction_order[face];
852 rotate_cube_face_inverse(&uf, &vf, s->out_cubemap_face_rotation[face]);
893 normalize_vector(vec);
897 * Calculate cubemap position for corresponding 3D coordinates on sphere.
898 * Common operation for every cubemap.
900 * @param s filter private context
901 * @param vec coordinated on sphere
902 * @param uf horizontal cubemap coordinate [0, 1)
903 * @param vf vertical cubemap coordinate [0, 1)
904 * @param direction direction of view
906 static void xyz_to_cube(const V360Context *s,
908 float *uf, float *vf, int *direction)
910 const float phi = atan2f(vec[0], -vec[2]);
911 const float theta = asinf(-vec[1]);
912 float phi_norm, theta_threshold;
915 if (phi >= -M_PI_4 && phi < M_PI_4) {
918 } else if (phi >= -(M_PI_2 + M_PI_4) && phi < -M_PI_4) {
920 phi_norm = phi + M_PI_2;
921 } else if (phi >= M_PI_4 && phi < M_PI_2 + M_PI_4) {
923 phi_norm = phi - M_PI_2;
926 phi_norm = phi + ((phi > 0.f) ? -M_PI : M_PI);
929 theta_threshold = atanf(cosf(phi_norm));
930 if (theta > theta_threshold) {
932 } else if (theta < -theta_threshold) {
936 switch (*direction) {
938 *uf = vec[2] / vec[0];
939 *vf = -vec[1] / vec[0];
942 *uf = vec[2] / vec[0];
943 *vf = vec[1] / vec[0];
946 *uf = vec[0] / vec[1];
947 *vf = -vec[2] / vec[1];
950 *uf = -vec[0] / vec[1];
951 *vf = -vec[2] / vec[1];
954 *uf = -vec[0] / vec[2];
955 *vf = vec[1] / vec[2];
958 *uf = -vec[0] / vec[2];
959 *vf = -vec[1] / vec[2];
965 face = s->in_cubemap_face_order[*direction];
966 rotate_cube_face(uf, vf, s->in_cubemap_face_rotation[face]);
968 (*uf) *= s->input_mirror_modifier[0];
969 (*vf) *= s->input_mirror_modifier[1];
973 * Find position on another cube face in case of overflow/underflow.
974 * Used for calculation of interpolation window.
976 * @param s filter private context
977 * @param uf horizontal cubemap coordinate
978 * @param vf vertical cubemap coordinate
979 * @param direction direction of view
980 * @param new_uf new horizontal cubemap coordinate
981 * @param new_vf new vertical cubemap coordinate
982 * @param face face position on cubemap
984 static void process_cube_coordinates(const V360Context *s,
985 float uf, float vf, int direction,
986 float *new_uf, float *new_vf, int *face)
989 * Cubemap orientation
996 * +-------+-------+-------+-------+ ^ e |
998 * | left | front | right | back | | g |
999 * +-------+-------+-------+-------+ v h v
1005 *face = s->in_cubemap_face_order[direction];
1006 rotate_cube_face_inverse(&uf, &vf, s->in_cubemap_face_rotation[*face]);
1008 if ((uf < -1.f || uf >= 1.f) && (vf < -1.f || vf >= 1.f)) {
1009 // There are no pixels to use in this case
1012 } else if (uf < -1.f) {
1014 switch (direction) {
1048 } else if (uf >= 1.f) {
1050 switch (direction) {
1084 } else if (vf < -1.f) {
1086 switch (direction) {
1120 } else if (vf >= 1.f) {
1122 switch (direction) {
1162 *face = s->in_cubemap_face_order[direction];
1163 rotate_cube_face(new_uf, new_vf, s->in_cubemap_face_rotation[*face]);
1167 * Calculate 3D coordinates on sphere for corresponding frame position in cubemap3x2 format.
1169 * @param s filter private context
1170 * @param i horizontal position on frame [0, width)
1171 * @param j vertical position on frame [0, height)
1172 * @param width frame width
1173 * @param height frame height
1174 * @param vec coordinates on sphere
1176 static void cube3x2_to_xyz(const V360Context *s,
1177 int i, int j, int width, int height,
1180 const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_width / 3.f) : 1.f - s->out_pad;
1181 const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_height / 2.f) : 1.f - s->out_pad;
1183 const float ew = width / 3.f;
1184 const float eh = height / 2.f;
1186 const int u_face = floorf(i / ew);
1187 const int v_face = floorf(j / eh);
1188 const int face = u_face + 3 * v_face;
1190 const int u_shift = ceilf(ew * u_face);
1191 const int v_shift = ceilf(eh * v_face);
1192 const int ewi = ceilf(ew * (u_face + 1)) - u_shift;
1193 const int ehi = ceilf(eh * (v_face + 1)) - v_shift;
1195 const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1196 const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1198 cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1202 * Calculate frame position in cubemap3x2 format for corresponding 3D coordinates on sphere.
1204 * @param s filter private context
1205 * @param vec coordinates on sphere
1206 * @param width frame width
1207 * @param height frame height
1208 * @param us horizontal coordinates for interpolation window
1209 * @param vs vertical coordinates for interpolation window
1210 * @param du horizontal relative coordinate
1211 * @param dv vertical relative coordinate
1213 static void xyz_to_cube3x2(const V360Context *s,
1214 const float *vec, int width, int height,
1215 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1217 const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_width / 3.f) : 1.f - s->in_pad;
1218 const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_height / 2.f) : 1.f - s->in_pad;
1219 const float ew = width / 3.f;
1220 const float eh = height / 2.f;
1224 int direction, face;
1227 xyz_to_cube(s, vec, &uf, &vf, &direction);
1232 face = s->in_cubemap_face_order[direction];
1235 ewi = ceilf(ew * (u_face + 1)) - ceilf(ew * u_face);
1236 ehi = ceilf(eh * (v_face + 1)) - ceilf(eh * v_face);
1238 uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1239 vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1247 for (int i = -1; i < 3; i++) {
1248 for (int j = -1; j < 3; j++) {
1249 int new_ui = ui + j;
1250 int new_vi = vi + i;
1251 int u_shift, v_shift;
1252 int new_ewi, new_ehi;
1254 if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1255 face = s->in_cubemap_face_order[direction];
1259 u_shift = ceilf(ew * u_face);
1260 v_shift = ceilf(eh * v_face);
1262 uf = 2.f * new_ui / ewi - 1.f;
1263 vf = 2.f * new_vi / ehi - 1.f;
1268 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1275 u_shift = ceilf(ew * u_face);
1276 v_shift = ceilf(eh * v_face);
1277 new_ewi = ceilf(ew * (u_face + 1)) - u_shift;
1278 new_ehi = ceilf(eh * (v_face + 1)) - v_shift;
1280 new_ui = av_clip(roundf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1281 new_vi = av_clip(roundf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1284 us[i + 1][j + 1] = u_shift + new_ui;
1285 vs[i + 1][j + 1] = v_shift + new_vi;
1291 * Calculate 3D coordinates on sphere for corresponding frame position in cubemap1x6 format.
1293 * @param s filter private context
1294 * @param i horizontal position on frame [0, width)
1295 * @param j vertical position on frame [0, height)
1296 * @param width frame width
1297 * @param height frame height
1298 * @param vec coordinates on sphere
1300 static void cube1x6_to_xyz(const V360Context *s,
1301 int i, int j, int width, int height,
1304 const float scalew = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / s->out_width : 1.f - s->out_pad;
1305 const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_height / 6.f) : 1.f - s->out_pad;
1307 const float ew = width;
1308 const float eh = height / 6.f;
1310 const int face = floorf(j / eh);
1312 const int v_shift = ceilf(eh * face);
1313 const int ehi = ceilf(eh * (face + 1)) - v_shift;
1315 const float uf = 2.f * (i + 0.5f) / ew - 1.f;
1316 const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1318 cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1322 * Calculate 3D coordinates on sphere for corresponding frame position in cubemap6x1 format.
1324 * @param s filter private context
1325 * @param i horizontal position on frame [0, width)
1326 * @param j vertical position on frame [0, height)
1327 * @param width frame width
1328 * @param height frame height
1329 * @param vec coordinates on sphere
1331 static void cube6x1_to_xyz(const V360Context *s,
1332 int i, int j, int width, int height,
1335 const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_width / 6.f) : 1.f - s->out_pad;
1336 const float scaleh = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / s->out_height : 1.f - s->out_pad;
1338 const float ew = width / 6.f;
1339 const float eh = height;
1341 const int face = floorf(i / ew);
1343 const int u_shift = ceilf(ew * face);
1344 const int ewi = ceilf(ew * (face + 1)) - u_shift;
1346 const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1347 const float vf = 2.f * (j + 0.5f) / eh - 1.f;
1349 cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1353 * Calculate frame position in cubemap1x6 format for corresponding 3D coordinates on sphere.
1355 * @param s filter private context
1356 * @param vec coordinates on sphere
1357 * @param width frame width
1358 * @param height frame height
1359 * @param us horizontal coordinates for interpolation window
1360 * @param vs vertical coordinates for interpolation window
1361 * @param du horizontal relative coordinate
1362 * @param dv vertical relative coordinate
1364 static void xyz_to_cube1x6(const V360Context *s,
1365 const float *vec, int width, int height,
1366 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1368 const float scalew = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / s->in_width : 1.f - s->in_pad;
1369 const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_height / 6.f) : 1.f - s->in_pad;
1370 const float eh = height / 6.f;
1371 const int ewi = width;
1375 int direction, face;
1377 xyz_to_cube(s, vec, &uf, &vf, &direction);
1382 face = s->in_cubemap_face_order[direction];
1383 ehi = ceilf(eh * (face + 1)) - ceilf(eh * face);
1385 uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1386 vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1394 for (int i = -1; i < 3; i++) {
1395 for (int j = -1; j < 3; j++) {
1396 int new_ui = ui + j;
1397 int new_vi = vi + i;
1401 if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1402 face = s->in_cubemap_face_order[direction];
1404 v_shift = ceilf(eh * face);
1406 uf = 2.f * new_ui / ewi - 1.f;
1407 vf = 2.f * new_vi / ehi - 1.f;
1412 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1417 v_shift = ceilf(eh * face);
1418 new_ehi = ceilf(eh * (face + 1)) - v_shift;
1420 new_ui = av_clip(roundf(0.5f * ewi * (uf + 1.f)), 0, ewi - 1);
1421 new_vi = av_clip(roundf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1424 us[i + 1][j + 1] = new_ui;
1425 vs[i + 1][j + 1] = v_shift + new_vi;
1431 * Calculate frame position in cubemap6x1 format for corresponding 3D coordinates on sphere.
1433 * @param s filter private context
1434 * @param vec coordinates on sphere
1435 * @param width frame width
1436 * @param height frame height
1437 * @param us horizontal coordinates for interpolation window
1438 * @param vs vertical coordinates for interpolation window
1439 * @param du horizontal relative coordinate
1440 * @param dv vertical relative coordinate
1442 static void xyz_to_cube6x1(const V360Context *s,
1443 const float *vec, int width, int height,
1444 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1446 const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_width / 6.f) : 1.f - s->in_pad;
1447 const float scaleh = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / s->in_height : 1.f - s->in_pad;
1448 const float ew = width / 6.f;
1449 const int ehi = height;
1453 int direction, face;
1455 xyz_to_cube(s, vec, &uf, &vf, &direction);
1460 face = s->in_cubemap_face_order[direction];
1461 ewi = ceilf(ew * (face + 1)) - ceilf(ew * face);
1463 uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1464 vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1472 for (int i = -1; i < 3; i++) {
1473 for (int j = -1; j < 3; j++) {
1474 int new_ui = ui + j;
1475 int new_vi = vi + i;
1479 if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1480 face = s->in_cubemap_face_order[direction];
1482 u_shift = ceilf(ew * face);
1484 uf = 2.f * new_ui / ewi - 1.f;
1485 vf = 2.f * new_vi / ehi - 1.f;
1490 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1495 u_shift = ceilf(ew * face);
1496 new_ewi = ceilf(ew * (face + 1)) - u_shift;
1498 new_ui = av_clip(roundf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1499 new_vi = av_clip(roundf(0.5f * ehi * (vf + 1.f)), 0, ehi - 1);
1502 us[i + 1][j + 1] = u_shift + new_ui;
1503 vs[i + 1][j + 1] = new_vi;
1509 * Calculate 3D coordinates on sphere for corresponding frame position in equirectangular format.
1511 * @param s filter private context
1512 * @param i horizontal position on frame [0, width)
1513 * @param j vertical position on frame [0, height)
1514 * @param width frame width
1515 * @param height frame height
1516 * @param vec coordinates on sphere
1518 static void equirect_to_xyz(const V360Context *s,
1519 int i, int j, int width, int height,
1522 const float phi = ((2.f * i) / width - 1.f) * M_PI;
1523 const float theta = ((2.f * j) / height - 1.f) * M_PI_2;
1525 const float sin_phi = sinf(phi);
1526 const float cos_phi = cosf(phi);
1527 const float sin_theta = sinf(theta);
1528 const float cos_theta = cosf(theta);
1530 vec[0] = cos_theta * sin_phi;
1531 vec[1] = -sin_theta;
1532 vec[2] = -cos_theta * cos_phi;
1536 * Prepare data for processing stereographic output format.
1538 * @param ctx filter context
1540 * @return error code
1542 static int prepare_stereographic_out(AVFilterContext *ctx)
1544 V360Context *s = ctx->priv;
1546 s->flat_range[0] = tanf(FFMIN(s->h_fov, 359.f) * M_PI / 720.f);
1547 s->flat_range[1] = tanf(FFMIN(s->v_fov, 359.f) * M_PI / 720.f);
1553 * Calculate 3D coordinates on sphere for corresponding frame position in stereographic format.
1555 * @param s filter private context
1556 * @param i horizontal position on frame [0, width)
1557 * @param j vertical position on frame [0, height)
1558 * @param width frame width
1559 * @param height frame height
1560 * @param vec coordinates on sphere
1562 static void stereographic_to_xyz(const V360Context *s,
1563 int i, int j, int width, int height,
1566 const float x = ((2.f * i) / width - 1.f) * s->flat_range[0];
1567 const float y = ((2.f * j) / height - 1.f) * s->flat_range[1];
1568 const float xy = x * x + y * y;
1570 vec[0] = 2.f * x / (1.f + xy);
1571 vec[1] = (-1.f + xy) / (1.f + xy);
1572 vec[2] = 2.f * y / (1.f + xy);
1574 normalize_vector(vec);
1578 * Calculate frame position in stereographic format for corresponding 3D coordinates on sphere.
1580 * @param s filter private context
1581 * @param vec coordinates on sphere
1582 * @param width frame width
1583 * @param height frame height
1584 * @param us horizontal coordinates for interpolation window
1585 * @param vs vertical coordinates for interpolation window
1586 * @param du horizontal relative coordinate
1587 * @param dv vertical relative coordinate
1589 static void xyz_to_stereographic(const V360Context *s,
1590 const float *vec, int width, int height,
1591 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1593 const float x = av_clipf(vec[0] / (1.f - vec[1]), -1.f, 1.f) * s->input_mirror_modifier[0];
1594 const float y = av_clipf(vec[2] / (1.f - vec[1]), -1.f, 1.f) * s->input_mirror_modifier[1];
1598 uf = (x + 1.f) * width / 2.f;
1599 vf = (y + 1.f) * height / 2.f;
1606 for (int i = -1; i < 3; i++) {
1607 for (int j = -1; j < 3; j++) {
1608 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1609 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1615 * Calculate frame position in equirectangular format for corresponding 3D coordinates on sphere.
1617 * @param s filter private context
1618 * @param vec coordinates on sphere
1619 * @param width frame width
1620 * @param height frame height
1621 * @param us horizontal coordinates for interpolation window
1622 * @param vs vertical coordinates for interpolation window
1623 * @param du horizontal relative coordinate
1624 * @param dv vertical relative coordinate
1626 static void xyz_to_equirect(const V360Context *s,
1627 const float *vec, int width, int height,
1628 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1630 const float phi = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1631 const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
1635 uf = (phi / M_PI + 1.f) * width / 2.f;
1636 vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1643 for (int i = -1; i < 3; i++) {
1644 for (int j = -1; j < 3; j++) {
1645 us[i + 1][j + 1] = mod(ui + j, width);
1646 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1652 * Calculate frame position in mercator format for corresponding 3D coordinates on sphere.
1654 * @param s filter private context
1655 * @param vec coordinates on sphere
1656 * @param width frame width
1657 * @param height frame height
1658 * @param us horizontal coordinates for interpolation window
1659 * @param vs vertical coordinates for interpolation window
1660 * @param du horizontal relative coordinate
1661 * @param dv vertical relative coordinate
1663 static void xyz_to_mercator(const V360Context *s,
1664 const float *vec, int width, int height,
1665 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1667 const float phi = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1668 const float theta = -vec[1] * s->input_mirror_modifier[1];
1672 uf = (phi / M_PI + 1.f) * width / 2.f;
1673 vf = (av_clipf(logf((1.f + theta) / (1.f - theta)) / (2.f * M_PI), -1.f, 1.f) + 1.f) * height / 2.f;
1680 for (int i = -1; i < 3; i++) {
1681 for (int j = -1; j < 3; j++) {
1682 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1683 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1689 * Calculate 3D coordinates on sphere for corresponding frame position in mercator format.
1691 * @param s filter private context
1692 * @param i horizontal position on frame [0, width)
1693 * @param j vertical position on frame [0, height)
1694 * @param width frame width
1695 * @param height frame height
1696 * @param vec coordinates on sphere
1698 static void mercator_to_xyz(const V360Context *s,
1699 int i, int j, int width, int height,
1702 const float phi = ((2.f * i) / width - 1.f) * M_PI + M_PI_2;
1703 const float y = ((2.f * j) / height - 1.f) * M_PI;
1704 const float div = expf(2.f * y) + 1.f;
1706 const float sin_phi = sinf(phi);
1707 const float cos_phi = cosf(phi);
1708 const float sin_theta = -2.f * expf(y) / div;
1709 const float cos_theta = -(expf(2.f * y) - 1.f) / div;
1711 vec[0] = sin_theta * cos_phi;
1713 vec[2] = sin_theta * sin_phi;
1717 * Calculate frame position in ball format for corresponding 3D coordinates on sphere.
1719 * @param s filter private context
1720 * @param vec coordinates on sphere
1721 * @param width frame width
1722 * @param height frame height
1723 * @param us horizontal coordinates for interpolation window
1724 * @param vs vertical coordinates for interpolation window
1725 * @param du horizontal relative coordinate
1726 * @param dv vertical relative coordinate
1728 static void xyz_to_ball(const V360Context *s,
1729 const float *vec, int width, int height,
1730 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1732 const float l = hypotf(vec[0], vec[1]);
1733 const float r = sqrtf(1.f + vec[2]) / M_SQRT2;
1737 uf = (1.f + r * vec[0] * s->input_mirror_modifier[0] / (l > 0.f ? l : 1.f)) * width * 0.5f;
1738 vf = (1.f - r * vec[1] * s->input_mirror_modifier[1] / (l > 0.f ? l : 1.f)) * height * 0.5f;
1746 for (int i = -1; i < 3; i++) {
1747 for (int j = -1; j < 3; j++) {
1748 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1749 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1755 * Calculate 3D coordinates on sphere for corresponding frame position in ball format.
1757 * @param s filter private context
1758 * @param i horizontal position on frame [0, width)
1759 * @param j vertical position on frame [0, height)
1760 * @param width frame width
1761 * @param height frame height
1762 * @param vec coordinates on sphere
1764 static void ball_to_xyz(const V360Context *s,
1765 int i, int j, int width, int height,
1768 const float x = (2.f * i) / width - 1.f;
1769 const float y = (2.f * j) / height - 1.f;
1770 const float l = hypotf(x, y);
1773 const float z = 2.f * l * sqrtf(1.f - l * l);
1775 vec[0] = z * x / (l > 0.f ? l : 1.f);
1776 vec[1] = -z * y / (l > 0.f ? l : 1.f);
1777 vec[2] = -1.f + 2.f * l * l;
1786 * Calculate 3D coordinates on sphere for corresponding frame position in hammer format.
1788 * @param s filter private context
1789 * @param i horizontal position on frame [0, width)
1790 * @param j vertical position on frame [0, height)
1791 * @param width frame width
1792 * @param height frame height
1793 * @param vec coordinates on sphere
1795 static void hammer_to_xyz(const V360Context *s,
1796 int i, int j, int width, int height,
1799 const float x = ((2.f * i) / width - 1.f);
1800 const float y = ((2.f * j) / height - 1.f);
1802 const float xx = x * x;
1803 const float yy = y * y;
1805 const float z = sqrtf(1.f - xx * 0.5f - yy * 0.5f);
1807 const float a = M_SQRT2 * x * z;
1808 const float b = 2.f * z * z - 1.f;
1810 const float aa = a * a;
1811 const float bb = b * b;
1813 const float w = sqrtf(1.f - 2.f * yy * z * z);
1815 vec[0] = w * 2.f * a * b / (aa + bb);
1816 vec[1] = -M_SQRT2 * y * z;
1817 vec[2] = -w * (bb - aa) / (aa + bb);
1819 normalize_vector(vec);
1823 * Calculate frame position in hammer format for corresponding 3D coordinates on sphere.
1825 * @param s filter private context
1826 * @param vec coordinates on sphere
1827 * @param width frame width
1828 * @param height frame height
1829 * @param us horizontal coordinates for interpolation window
1830 * @param vs vertical coordinates for interpolation window
1831 * @param du horizontal relative coordinate
1832 * @param dv vertical relative coordinate
1834 static void xyz_to_hammer(const V360Context *s,
1835 const float *vec, int width, int height,
1836 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1838 const float theta = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1840 const float z = sqrtf(1.f + sqrtf(1.f - vec[1] * vec[1]) * cosf(theta * 0.5f));
1841 const float x = sqrtf(1.f - vec[1] * vec[1]) * sinf(theta * 0.5f) / z;
1842 const float y = -vec[1] / z * s->input_mirror_modifier[1];
1846 uf = (x + 1.f) * width / 2.f;
1847 vf = (y + 1.f) * height / 2.f;
1854 for (int i = -1; i < 3; i++) {
1855 for (int j = -1; j < 3; j++) {
1856 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1857 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1863 * Calculate 3D coordinates on sphere for corresponding frame position in sinusoidal format.
1865 * @param s filter private context
1866 * @param i horizontal position on frame [0, width)
1867 * @param j vertical position on frame [0, height)
1868 * @param width frame width
1869 * @param height frame height
1870 * @param vec coordinates on sphere
1872 static void sinusoidal_to_xyz(const V360Context *s,
1873 int i, int j, int width, int height,
1876 const float theta = ((2.f * j) / height - 1.f) * M_PI_2;
1877 const float phi = ((2.f * i) / width - 1.f) * M_PI / cosf(theta);
1879 const float sin_phi = sinf(phi);
1880 const float cos_phi = cosf(phi);
1881 const float sin_theta = sinf(theta);
1882 const float cos_theta = cosf(theta);
1884 vec[0] = cos_theta * sin_phi;
1885 vec[1] = -sin_theta;
1886 vec[2] = -cos_theta * cos_phi;
1888 normalize_vector(vec);
1892 * Calculate frame position in sinusoidal format for corresponding 3D coordinates on sphere.
1894 * @param s filter private context
1895 * @param vec coordinates on sphere
1896 * @param width frame width
1897 * @param height frame height
1898 * @param us horizontal coordinates for interpolation window
1899 * @param vs vertical coordinates for interpolation window
1900 * @param du horizontal relative coordinate
1901 * @param dv vertical relative coordinate
1903 static void xyz_to_sinusoidal(const V360Context *s,
1904 const float *vec, int width, int height,
1905 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
1907 const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
1908 const float phi = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0] * cosf(theta);
1912 uf = (phi / M_PI + 1.f) * width / 2.f;
1913 vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1920 for (int i = -1; i < 3; i++) {
1921 for (int j = -1; j < 3; j++) {
1922 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
1923 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1929 * Prepare data for processing equi-angular cubemap input format.
1931 * @param ctx filter context
1933 * @return error code
1935 static int prepare_eac_in(AVFilterContext *ctx)
1937 V360Context *s = ctx->priv;
1939 if (s->ih_flip && s->iv_flip) {
1940 s->in_cubemap_face_order[RIGHT] = BOTTOM_LEFT;
1941 s->in_cubemap_face_order[LEFT] = BOTTOM_RIGHT;
1942 s->in_cubemap_face_order[UP] = TOP_LEFT;
1943 s->in_cubemap_face_order[DOWN] = TOP_RIGHT;
1944 s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
1945 s->in_cubemap_face_order[BACK] = TOP_MIDDLE;
1946 } else if (s->ih_flip) {
1947 s->in_cubemap_face_order[RIGHT] = TOP_LEFT;
1948 s->in_cubemap_face_order[LEFT] = TOP_RIGHT;
1949 s->in_cubemap_face_order[UP] = BOTTOM_LEFT;
1950 s->in_cubemap_face_order[DOWN] = BOTTOM_RIGHT;
1951 s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
1952 s->in_cubemap_face_order[BACK] = BOTTOM_MIDDLE;
1953 } else if (s->iv_flip) {
1954 s->in_cubemap_face_order[RIGHT] = BOTTOM_RIGHT;
1955 s->in_cubemap_face_order[LEFT] = BOTTOM_LEFT;
1956 s->in_cubemap_face_order[UP] = TOP_RIGHT;
1957 s->in_cubemap_face_order[DOWN] = TOP_LEFT;
1958 s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
1959 s->in_cubemap_face_order[BACK] = TOP_MIDDLE;
1961 s->in_cubemap_face_order[RIGHT] = TOP_RIGHT;
1962 s->in_cubemap_face_order[LEFT] = TOP_LEFT;
1963 s->in_cubemap_face_order[UP] = BOTTOM_RIGHT;
1964 s->in_cubemap_face_order[DOWN] = BOTTOM_LEFT;
1965 s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
1966 s->in_cubemap_face_order[BACK] = BOTTOM_MIDDLE;
1970 s->in_cubemap_face_rotation[TOP_LEFT] = ROT_270;
1971 s->in_cubemap_face_rotation[TOP_MIDDLE] = ROT_90;
1972 s->in_cubemap_face_rotation[TOP_RIGHT] = ROT_270;
1973 s->in_cubemap_face_rotation[BOTTOM_LEFT] = ROT_0;
1974 s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_0;
1975 s->in_cubemap_face_rotation[BOTTOM_RIGHT] = ROT_0;
1977 s->in_cubemap_face_rotation[TOP_LEFT] = ROT_0;
1978 s->in_cubemap_face_rotation[TOP_MIDDLE] = ROT_0;
1979 s->in_cubemap_face_rotation[TOP_RIGHT] = ROT_0;
1980 s->in_cubemap_face_rotation[BOTTOM_LEFT] = ROT_270;
1981 s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
1982 s->in_cubemap_face_rotation[BOTTOM_RIGHT] = ROT_270;
1989 * Prepare data for processing equi-angular cubemap output format.
1991 * @param ctx filter context
1993 * @return error code
1995 static int prepare_eac_out(AVFilterContext *ctx)
1997 V360Context *s = ctx->priv;
1999 s->out_cubemap_direction_order[TOP_LEFT] = LEFT;
2000 s->out_cubemap_direction_order[TOP_MIDDLE] = FRONT;
2001 s->out_cubemap_direction_order[TOP_RIGHT] = RIGHT;
2002 s->out_cubemap_direction_order[BOTTOM_LEFT] = DOWN;
2003 s->out_cubemap_direction_order[BOTTOM_MIDDLE] = BACK;
2004 s->out_cubemap_direction_order[BOTTOM_RIGHT] = UP;
2006 s->out_cubemap_face_rotation[TOP_LEFT] = ROT_0;
2007 s->out_cubemap_face_rotation[TOP_MIDDLE] = ROT_0;
2008 s->out_cubemap_face_rotation[TOP_RIGHT] = ROT_0;
2009 s->out_cubemap_face_rotation[BOTTOM_LEFT] = ROT_270;
2010 s->out_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
2011 s->out_cubemap_face_rotation[BOTTOM_RIGHT] = ROT_270;
2017 * Calculate 3D coordinates on sphere for corresponding frame position in equi-angular cubemap format.
2019 * @param s filter private context
2020 * @param i horizontal position on frame [0, width)
2021 * @param j vertical position on frame [0, height)
2022 * @param width frame width
2023 * @param height frame height
2024 * @param vec coordinates on sphere
2026 static void eac_to_xyz(const V360Context *s,
2027 int i, int j, int width, int height,
2030 const float pixel_pad = 2;
2031 const float u_pad = pixel_pad / width;
2032 const float v_pad = pixel_pad / height;
2034 int u_face, v_face, face;
2036 float l_x, l_y, l_z;
2038 float uf = (i + 0.5f) / width;
2039 float vf = (j + 0.5f) / height;
2041 // EAC has 2-pixel padding on faces except between faces on the same row
2042 // Padding pixels seems not to be stretched with tangent as regular pixels
2043 // Formulas below approximate original padding as close as I could get experimentally
2045 // Horizontal padding
2046 uf = 3.f * (uf - u_pad) / (1.f - 2.f * u_pad);
2050 } else if (uf >= 3.f) {
2054 u_face = floorf(uf);
2055 uf = fmodf(uf, 1.f) - 0.5f;
2059 v_face = floorf(vf * 2.f);
2060 vf = (vf - v_pad - 0.5f * v_face) / (0.5f - 2.f * v_pad) - 0.5f;
2062 if (uf >= -0.5f && uf < 0.5f) {
2063 uf = tanf(M_PI_2 * uf);
2067 if (vf >= -0.5f && vf < 0.5f) {
2068 vf = tanf(M_PI_2 * vf);
2073 face = u_face + 3 * v_face;
2114 normalize_vector(vec);
2118 * Calculate frame position in equi-angular cubemap format for corresponding 3D coordinates on sphere.
2120 * @param s filter private context
2121 * @param vec coordinates on sphere
2122 * @param width frame width
2123 * @param height frame height
2124 * @param us horizontal coordinates for interpolation window
2125 * @param vs vertical coordinates for interpolation window
2126 * @param du horizontal relative coordinate
2127 * @param dv vertical relative coordinate
2129 static void xyz_to_eac(const V360Context *s,
2130 const float *vec, int width, int height,
2131 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
2133 const float pixel_pad = 2;
2134 const float u_pad = pixel_pad / width;
2135 const float v_pad = pixel_pad / height;
2139 int direction, face;
2142 xyz_to_cube(s, vec, &uf, &vf, &direction);
2144 face = s->in_cubemap_face_order[direction];
2148 uf = M_2_PI * atanf(uf) + 0.5f;
2149 vf = M_2_PI * atanf(vf) + 0.5f;
2151 // These formulas are inversed from eac_to_xyz ones
2152 uf = (uf + u_face) * (1.f - 2.f * u_pad) / 3.f + u_pad;
2153 vf = vf * (0.5f - 2.f * v_pad) + v_pad + 0.5f * v_face;
2167 for (int i = -1; i < 3; i++) {
2168 for (int j = -1; j < 3; j++) {
2169 us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1);
2170 vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
2176 * Prepare data for processing flat output format.
2178 * @param ctx filter context
2180 * @return error code
2182 static int prepare_flat_out(AVFilterContext *ctx)
2184 V360Context *s = ctx->priv;
2186 s->flat_range[0] = tanf(0.5f * s->h_fov * M_PI / 180.f);
2187 s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2193 * Calculate 3D coordinates on sphere for corresponding frame position in flat format.
2195 * @param s filter private context
2196 * @param i horizontal position on frame [0, width)
2197 * @param j vertical position on frame [0, height)
2198 * @param width frame width
2199 * @param height frame height
2200 * @param vec coordinates on sphere
2202 static void flat_to_xyz(const V360Context *s,
2203 int i, int j, int width, int height,
2206 const float l_x = s->flat_range[0] * (2.f * i / width - 1.f);
2207 const float l_y = -s->flat_range[1] * (2.f * j / height - 1.f);
2213 normalize_vector(vec);
2217 * Prepare data for processing fisheye output format.
2219 * @param ctx filter context
2221 * @return error code
2223 static int prepare_fisheye_out(AVFilterContext *ctx)
2225 V360Context *s = ctx->priv;
2227 s->flat_range[0] = s->h_fov / 180.f;
2228 s->flat_range[1] = s->v_fov / 180.f;
2234 * Calculate 3D coordinates on sphere for corresponding frame position in fisheye format.
2236 * @param s filter private context
2237 * @param i horizontal position on frame [0, width)
2238 * @param j vertical position on frame [0, height)
2239 * @param width frame width
2240 * @param height frame height
2241 * @param vec coordinates on sphere
2243 static void fisheye_to_xyz(const V360Context *s,
2244 int i, int j, int width, int height,
2247 const float uf = s->flat_range[0] * ((2.f * i) / width - 1.f);
2248 const float vf = s->flat_range[1] * ((2.f * j) / height - 1.f);
2250 const float phi = -atan2f(vf, uf);
2251 const float theta = -M_PI_2 * (1.f - hypotf(uf, vf));
2253 vec[0] = cosf(theta) * cosf(phi);
2254 vec[1] = cosf(theta) * sinf(phi);
2255 vec[2] = sinf(theta);
2257 normalize_vector(vec);
2261 * Calculate 3D coordinates on sphere for corresponding frame position in pannini format.
2263 * @param s filter private context
2264 * @param i horizontal position on frame [0, width)
2265 * @param j vertical position on frame [0, height)
2266 * @param width frame width
2267 * @param height frame height
2268 * @param vec coordinates on sphere
2270 static void pannini_to_xyz(const V360Context *s,
2271 int i, int j, int width, int height,
2274 const float uf = ((2.f * i) / width - 1.f);
2275 const float vf = ((2.f * j) / height - 1.f);
2277 const float d = s->h_fov;
2278 const float k = uf * uf / ((d + 1.f) * (d + 1.f));
2279 const float dscr = k * k * d * d - (k + 1.f) * (k * d * d - 1.f);
2280 const float clon = (-k * d + sqrtf(dscr)) / (k + 1.f);
2281 const float S = (d + 1.f) / (d + clon);
2282 const float lon = -(M_PI + atan2f(uf, S * clon));
2283 const float lat = -atan2f(vf, S);
2285 vec[0] = sinf(lon) * cosf(lat);
2287 vec[2] = cosf(lon) * cosf(lat);
2289 normalize_vector(vec);
2293 * Prepare data for processing cylindrical output format.
2295 * @param ctx filter context
2297 * @return error code
2299 static int prepare_cylindrical_out(AVFilterContext *ctx)
2301 V360Context *s = ctx->priv;
2303 s->flat_range[0] = M_PI * s->h_fov / 360.f;
2304 s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2310 * Calculate 3D coordinates on sphere for corresponding frame position in cylindrical format.
2312 * @param s filter private context
2313 * @param i horizontal position on frame [0, width)
2314 * @param j vertical position on frame [0, height)
2315 * @param width frame width
2316 * @param height frame height
2317 * @param vec coordinates on sphere
2319 static void cylindrical_to_xyz(const V360Context *s,
2320 int i, int j, int width, int height,
2323 const float uf = s->flat_range[0] * ((2.f * i) / width - 1.f);
2324 const float vf = s->flat_range[1] * ((2.f * j) / height - 1.f);
2326 const float phi = uf;
2327 const float theta = atanf(vf);
2329 const float sin_phi = sinf(phi);
2330 const float cos_phi = cosf(phi);
2331 const float sin_theta = sinf(theta);
2332 const float cos_theta = cosf(theta);
2334 vec[0] = cos_theta * sin_phi;
2335 vec[1] = -sin_theta;
2336 vec[2] = -cos_theta * cos_phi;
2338 normalize_vector(vec);
2342 * Calculate 3D coordinates on sphere for corresponding frame position in perspective format.
2344 * @param s filter private context
2345 * @param i horizontal position on frame [0, width)
2346 * @param j vertical position on frame [0, height)
2347 * @param width frame width
2348 * @param height frame height
2349 * @param vec coordinates on sphere
2351 static void perspective_to_xyz(const V360Context *s,
2352 int i, int j, int width, int height,
2355 const float uf = ((2.f * i) / width - 1.f);
2356 const float vf = ((2.f * j) / height - 1.f);
2357 const float rh = hypotf(uf, vf);
2358 const float sinzz = 1.f - rh * rh;
2359 const float h = 1.f + s->v_fov;
2360 const float sinz = (h - sqrtf(sinzz)) / (h / rh + rh / h);
2361 const float sinz2 = sinz * sinz;
2364 const float cosz = sqrtf(1.f - sinz2);
2366 const float theta = asinf(cosz);
2367 const float phi = atan2f(uf, vf);
2369 const float sin_phi = sinf(phi);
2370 const float cos_phi = cosf(phi);
2371 const float sin_theta = sinf(theta);
2372 const float cos_theta = cosf(theta);
2374 vec[0] = cos_theta * sin_phi;
2376 vec[2] = -cos_theta * cos_phi;
2383 normalize_vector(vec);
2387 * Calculate 3D coordinates on sphere for corresponding frame position in dual fisheye format.
2389 * @param s filter private context
2390 * @param i horizontal position on frame [0, width)
2391 * @param j vertical position on frame [0, height)
2392 * @param width frame width
2393 * @param height frame height
2394 * @param vec coordinates on sphere
2396 static void dfisheye_to_xyz(const V360Context *s,
2397 int i, int j, int width, int height,
2400 const float scale = 1.f + s->out_pad;
2402 const float ew = width / 2.f;
2403 const float eh = height;
2405 const int ei = i >= ew ? i - ew : i;
2406 const float m = i >= ew ? -1.f : 1.f;
2408 const float uf = ((2.f * ei) / ew - 1.f) * scale;
2409 const float vf = ((2.f * j) / eh - 1.f) * scale;
2411 const float h = hypotf(uf, vf);
2412 const float lh = h > 0.f ? h : 1.f;
2413 const float theta = m * M_PI_2 * (1.f - h);
2415 const float sin_theta = sinf(theta);
2416 const float cos_theta = cosf(theta);
2418 vec[0] = cos_theta * m * -uf / lh;
2419 vec[1] = cos_theta * -vf / lh;
2422 normalize_vector(vec);
2426 * Calculate frame position in dual fisheye format for corresponding 3D coordinates on sphere.
2428 * @param s filter private context
2429 * @param vec coordinates on sphere
2430 * @param width frame width
2431 * @param height frame height
2432 * @param us horizontal coordinates for interpolation window
2433 * @param vs vertical coordinates for interpolation window
2434 * @param du horizontal relative coordinate
2435 * @param dv vertical relative coordinate
2437 static void xyz_to_dfisheye(const V360Context *s,
2438 const float *vec, int width, int height,
2439 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
2441 const float scale = 1.f - s->in_pad;
2443 const float ew = width / 2.f;
2444 const float eh = height;
2446 const float h = hypotf(vec[0], vec[1]);
2447 const float lh = h > 0.f ? h : 1.f;
2448 const float theta = acosf(fabsf(vec[2])) / M_PI;
2450 float uf = (theta * (-vec[0] / lh) * s->input_mirror_modifier[0] * scale + 0.5f) * ew;
2451 float vf = (theta * (-vec[1] / lh) * s->input_mirror_modifier[1] * scale + 0.5f) * eh;
2456 if (vec[2] >= 0.f) {
2459 u_shift = ceilf(ew);
2469 for (int i = -1; i < 3; i++) {
2470 for (int j = -1; j < 3; j++) {
2471 us[i + 1][j + 1] = av_clip(u_shift + ui + j, 0, width - 1);
2472 vs[i + 1][j + 1] = av_clip( vi + i, 0, height - 1);
2478 * Calculate 3D coordinates on sphere for corresponding frame position in barrel facebook's format.
2480 * @param s filter private context
2481 * @param i horizontal position on frame [0, width)
2482 * @param j vertical position on frame [0, height)
2483 * @param width frame width
2484 * @param height frame height
2485 * @param vec coordinates on sphere
2487 static void barrel_to_xyz(const V360Context *s,
2488 int i, int j, int width, int height,
2491 const float scale = 0.99f;
2492 float l_x, l_y, l_z;
2494 if (i < 4 * width / 5) {
2495 const float theta_range = M_PI_4;
2497 const int ew = 4 * width / 5;
2498 const int eh = height;
2500 const float phi = ((2.f * i) / ew - 1.f) * M_PI / scale;
2501 const float theta = ((2.f * j) / eh - 1.f) * theta_range / scale;
2503 const float sin_phi = sinf(phi);
2504 const float cos_phi = cosf(phi);
2505 const float sin_theta = sinf(theta);
2506 const float cos_theta = cosf(theta);
2508 l_x = cos_theta * sin_phi;
2510 l_z = -cos_theta * cos_phi;
2512 const int ew = width / 5;
2513 const int eh = height / 2;
2518 uf = 2.f * (i - 4 * ew) / ew - 1.f;
2519 vf = 2.f * (j ) / eh - 1.f;
2528 uf = 2.f * (i - 4 * ew) / ew - 1.f;
2529 vf = 2.f * (j - eh) / eh - 1.f;
2544 normalize_vector(vec);
2548 * Calculate frame position in barrel facebook's format for corresponding 3D coordinates on sphere.
2550 * @param s filter private context
2551 * @param vec coordinates on sphere
2552 * @param width frame width
2553 * @param height frame height
2554 * @param us horizontal coordinates for interpolation window
2555 * @param vs vertical coordinates for interpolation window
2556 * @param du horizontal relative coordinate
2557 * @param dv vertical relative coordinate
2559 static void xyz_to_barrel(const V360Context *s,
2560 const float *vec, int width, int height,
2561 uint16_t us[4][4], uint16_t vs[4][4], float *du, float *dv)
2563 const float scale = 0.99f;
2565 const float phi = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
2566 const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
2567 const float theta_range = M_PI_4;
2570 int u_shift, v_shift;
2574 if (theta > -theta_range && theta < theta_range) {
2578 u_shift = s->ih_flip ? width / 5 : 0;
2581 uf = (phi / M_PI * scale + 1.f) * ew / 2.f;
2582 vf = (theta / theta_range * scale + 1.f) * eh / 2.f;
2587 u_shift = s->ih_flip ? 0 : 4 * ew;
2589 if (theta < 0.f) { // UP
2590 uf = vec[0] / vec[1];
2591 vf = -vec[2] / vec[1];
2594 uf = -vec[0] / vec[1];
2595 vf = -vec[2] / vec[1];
2599 uf *= s->input_mirror_modifier[0] * s->input_mirror_modifier[1];
2600 vf *= s->input_mirror_modifier[1];
2602 uf = 0.5f * ew * (uf * scale + 1.f);
2603 vf = 0.5f * eh * (vf * scale + 1.f);
2612 for (int i = -1; i < 3; i++) {
2613 for (int j = -1; j < 3; j++) {
2614 us[i + 1][j + 1] = u_shift + av_clip(ui + j, 0, ew - 1);
2615 vs[i + 1][j + 1] = v_shift + av_clip(vi + i, 0, eh - 1);
2620 static void multiply_matrix(float c[3][3], const float a[3][3], const float b[3][3])
2622 for (int i = 0; i < 3; i++) {
2623 for (int j = 0; j < 3; j++) {
2626 for (int k = 0; k < 3; k++)
2627 sum += a[i][k] * b[k][j];
2635 * Calculate rotation matrix for yaw/pitch/roll angles.
2637 static inline void calculate_rotation_matrix(float yaw, float pitch, float roll,
2638 float rot_mat[3][3],
2639 const int rotation_order[3])
2641 const float yaw_rad = yaw * M_PI / 180.f;
2642 const float pitch_rad = pitch * M_PI / 180.f;
2643 const float roll_rad = roll * M_PI / 180.f;
2645 const float sin_yaw = sinf(-yaw_rad);
2646 const float cos_yaw = cosf(-yaw_rad);
2647 const float sin_pitch = sinf(pitch_rad);
2648 const float cos_pitch = cosf(pitch_rad);
2649 const float sin_roll = sinf(roll_rad);
2650 const float cos_roll = cosf(roll_rad);
2655 m[0][0][0] = cos_yaw; m[0][0][1] = 0; m[0][0][2] = sin_yaw;
2656 m[0][1][0] = 0; m[0][1][1] = 1; m[0][1][2] = 0;
2657 m[0][2][0] = -sin_yaw; m[0][2][1] = 0; m[0][2][2] = cos_yaw;
2659 m[1][0][0] = 1; m[1][0][1] = 0; m[1][0][2] = 0;
2660 m[1][1][0] = 0; m[1][1][1] = cos_pitch; m[1][1][2] = -sin_pitch;
2661 m[1][2][0] = 0; m[1][2][1] = sin_pitch; m[1][2][2] = cos_pitch;
2663 m[2][0][0] = cos_roll; m[2][0][1] = -sin_roll; m[2][0][2] = 0;
2664 m[2][1][0] = sin_roll; m[2][1][1] = cos_roll; m[2][1][2] = 0;
2665 m[2][2][0] = 0; m[2][2][1] = 0; m[2][2][2] = 1;
2667 multiply_matrix(temp, m[rotation_order[0]], m[rotation_order[1]]);
2668 multiply_matrix(rot_mat, temp, m[rotation_order[2]]);
2672 * Rotate vector with given rotation matrix.
2674 * @param rot_mat rotation matrix
2677 static inline void rotate(const float rot_mat[3][3],
2680 const float x_tmp = vec[0] * rot_mat[0][0] + vec[1] * rot_mat[0][1] + vec[2] * rot_mat[0][2];
2681 const float y_tmp = vec[0] * rot_mat[1][0] + vec[1] * rot_mat[1][1] + vec[2] * rot_mat[1][2];
2682 const float z_tmp = vec[0] * rot_mat[2][0] + vec[1] * rot_mat[2][1] + vec[2] * rot_mat[2][2];
2689 static inline void set_mirror_modifier(int h_flip, int v_flip, int d_flip,
2692 modifier[0] = h_flip ? -1.f : 1.f;
2693 modifier[1] = v_flip ? -1.f : 1.f;
2694 modifier[2] = d_flip ? -1.f : 1.f;
2697 static inline void mirror(const float *modifier, float *vec)
2699 vec[0] *= modifier[0];
2700 vec[1] *= modifier[1];
2701 vec[2] *= modifier[2];
2704 static int allocate_plane(V360Context *s, int sizeof_uv, int sizeof_ker, int p)
2706 s->u[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
2707 s->v[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
2708 if (!s->u[p] || !s->v[p])
2709 return AVERROR(ENOMEM);
2711 s->ker[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_ker);
2713 return AVERROR(ENOMEM);
2719 static void fov_from_dfov(V360Context *s, float w, float h)
2721 const float da = tanf(0.5 * FFMIN(s->d_fov, 359.f) * M_PI / 180.f);
2722 const float d = hypotf(w, h);
2724 s->h_fov = atan2f(da * w, d) * 360.f / M_PI;
2725 s->v_fov = atan2f(da * h, d) * 360.f / M_PI;
2733 static void set_dimensions(int *outw, int *outh, int w, int h, const AVPixFmtDescriptor *desc)
2735 outw[1] = outw[2] = FF_CEIL_RSHIFT(w, desc->log2_chroma_w);
2736 outw[0] = outw[3] = w;
2737 outh[1] = outh[2] = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
2738 outh[0] = outh[3] = h;
2741 // Calculate remap data
2742 static av_always_inline int v360_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
2744 V360Context *s = ctx->priv;
2746 for (int p = 0; p < s->nb_allocated; p++) {
2747 const int width = s->pr_width[p];
2748 const int uv_linesize = s->uv_linesize[p];
2749 const int height = s->pr_height[p];
2750 const int in_width = s->inplanewidth[p];
2751 const int in_height = s->inplaneheight[p];
2752 const int slice_start = (height * jobnr ) / nb_jobs;
2753 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
2758 for (int j = slice_start; j < slice_end; j++) {
2759 for (int i = 0; i < width; i++) {
2760 uint16_t *u = s->u[p] + (j * uv_linesize + i) * s->elements;
2761 uint16_t *v = s->v[p] + (j * uv_linesize + i) * s->elements;
2762 int16_t *ker = s->ker[p] + (j * uv_linesize + i) * s->elements;
2764 if (s->out_transpose)
2765 s->out_transform(s, j, i, height, width, vec);
2767 s->out_transform(s, i, j, width, height, vec);
2768 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
2769 rotate(s->rot_mat, vec);
2770 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
2771 normalize_vector(vec);
2772 mirror(s->output_mirror_modifier, vec);
2773 if (s->in_transpose)
2774 s->in_transform(s, vec, in_height, in_width, rmap.v, rmap.u, &du, &dv);
2776 s->in_transform(s, vec, in_width, in_height, rmap.u, rmap.v, &du, &dv);
2777 av_assert1(!isnan(du) && !isnan(dv));
2778 s->calculate_kernel(du, dv, &rmap, u, v, ker);
2786 static int config_output(AVFilterLink *outlink)
2788 AVFilterContext *ctx = outlink->src;
2789 AVFilterLink *inlink = ctx->inputs[0];
2790 V360Context *s = ctx->priv;
2791 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
2792 const int depth = desc->comp[0].depth;
2797 int in_offset_h, in_offset_w;
2798 int out_offset_h, out_offset_w;
2800 int (*prepare_out)(AVFilterContext *ctx);
2802 s->input_mirror_modifier[0] = s->ih_flip ? -1.f : 1.f;
2803 s->input_mirror_modifier[1] = s->iv_flip ? -1.f : 1.f;
2805 switch (s->interp) {
2807 s->calculate_kernel = nearest_kernel;
2808 s->remap_slice = depth <= 8 ? remap1_8bit_slice : remap1_16bit_slice;
2810 sizeof_uv = sizeof(uint16_t) * s->elements;
2814 s->calculate_kernel = bilinear_kernel;
2815 s->remap_slice = depth <= 8 ? remap2_8bit_slice : remap2_16bit_slice;
2816 s->elements = 2 * 2;
2817 sizeof_uv = sizeof(uint16_t) * s->elements;
2818 sizeof_ker = sizeof(uint16_t) * s->elements;
2821 s->calculate_kernel = bicubic_kernel;
2822 s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2823 s->elements = 4 * 4;
2824 sizeof_uv = sizeof(uint16_t) * s->elements;
2825 sizeof_ker = sizeof(uint16_t) * s->elements;
2828 s->calculate_kernel = lanczos_kernel;
2829 s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2830 s->elements = 4 * 4;
2831 sizeof_uv = sizeof(uint16_t) * s->elements;
2832 sizeof_ker = sizeof(uint16_t) * s->elements;
2835 s->calculate_kernel = spline16_kernel;
2836 s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2837 s->elements = 4 * 4;
2838 sizeof_uv = sizeof(uint16_t) * s->elements;
2839 sizeof_ker = sizeof(uint16_t) * s->elements;
2842 s->calculate_kernel = gaussian_kernel;
2843 s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
2844 s->elements = 4 * 4;
2845 sizeof_uv = sizeof(uint16_t) * s->elements;
2846 sizeof_ker = sizeof(uint16_t) * s->elements;
2852 ff_v360_init(s, depth);
2854 for (int order = 0; order < NB_RORDERS; order++) {
2855 const char c = s->rorder[order];
2859 av_log(ctx, AV_LOG_ERROR,
2860 "Incomplete rorder option. Direction for all 3 rotation orders should be specified.\n");
2861 return AVERROR(EINVAL);
2864 rorder = get_rorder(c);
2866 av_log(ctx, AV_LOG_ERROR,
2867 "Incorrect rotation order symbol '%c' in rorder option.\n", c);
2868 return AVERROR(EINVAL);
2871 s->rotation_order[order] = rorder;
2874 switch (s->in_stereo) {
2878 in_offset_w = in_offset_h = 0;
2896 set_dimensions(s->inplanewidth, s->inplaneheight, w, h, desc);
2897 set_dimensions(s->in_offset_w, s->in_offset_h, in_offset_w, in_offset_h, desc);
2899 s->in_width = s->inplanewidth[0];
2900 s->in_height = s->inplaneheight[0];
2902 if (s->in_transpose)
2903 FFSWAP(int, s->in_width, s->in_height);
2906 case EQUIRECTANGULAR:
2907 s->in_transform = xyz_to_equirect;
2913 s->in_transform = xyz_to_cube3x2;
2914 err = prepare_cube_in(ctx);
2919 s->in_transform = xyz_to_cube1x6;
2920 err = prepare_cube_in(ctx);
2925 s->in_transform = xyz_to_cube6x1;
2926 err = prepare_cube_in(ctx);
2931 s->in_transform = xyz_to_eac;
2932 err = prepare_eac_in(ctx);
2941 av_log(ctx, AV_LOG_ERROR, "Supplied format is not accepted as input.\n");
2942 return AVERROR(EINVAL);
2944 s->in_transform = xyz_to_dfisheye;
2950 s->in_transform = xyz_to_barrel;
2956 s->in_transform = xyz_to_stereographic;
2962 s->in_transform = xyz_to_mercator;
2968 s->in_transform = xyz_to_ball;
2974 s->in_transform = xyz_to_hammer;
2980 s->in_transform = xyz_to_sinusoidal;
2986 av_log(ctx, AV_LOG_ERROR, "Specified input format is not handled.\n");
2995 case EQUIRECTANGULAR:
2996 s->out_transform = equirect_to_xyz;
3002 s->out_transform = cube3x2_to_xyz;
3003 prepare_out = prepare_cube_out;
3004 w = roundf(wf / 4.f * 3.f);
3008 s->out_transform = cube1x6_to_xyz;
3009 prepare_out = prepare_cube_out;
3010 w = roundf(wf / 4.f);
3011 h = roundf(hf * 3.f);
3014 s->out_transform = cube6x1_to_xyz;
3015 prepare_out = prepare_cube_out;
3016 w = roundf(wf / 2.f * 3.f);
3017 h = roundf(hf / 2.f);
3020 s->out_transform = eac_to_xyz;
3021 prepare_out = prepare_eac_out;
3023 h = roundf(hf / 8.f * 9.f);
3026 s->out_transform = flat_to_xyz;
3027 prepare_out = prepare_flat_out;
3032 s->out_transform = dfisheye_to_xyz;
3038 s->out_transform = barrel_to_xyz;
3040 w = roundf(wf / 4.f * 5.f);
3044 s->out_transform = stereographic_to_xyz;
3045 prepare_out = prepare_stereographic_out;
3047 h = roundf(hf * 2.f);
3050 s->out_transform = mercator_to_xyz;
3053 h = roundf(hf * 2.f);
3056 s->out_transform = ball_to_xyz;
3059 h = roundf(hf * 2.f);
3062 s->out_transform = hammer_to_xyz;
3068 s->out_transform = sinusoidal_to_xyz;
3074 s->out_transform = fisheye_to_xyz;
3075 prepare_out = prepare_fisheye_out;
3076 w = roundf(wf * 0.5f);
3080 s->out_transform = pannini_to_xyz;
3086 s->out_transform = cylindrical_to_xyz;
3087 prepare_out = prepare_cylindrical_out;
3089 h = roundf(hf * 0.5f);
3092 s->out_transform = perspective_to_xyz;
3094 w = roundf(wf / 2.f);
3098 av_log(ctx, AV_LOG_ERROR, "Specified output format is not handled.\n");
3102 // Override resolution with user values if specified
3103 if (s->width > 0 && s->height > 0) {
3106 } else if (s->width > 0 || s->height > 0) {
3107 av_log(ctx, AV_LOG_ERROR, "Both width and height values should be specified.\n");
3108 return AVERROR(EINVAL);
3110 if (s->out_transpose)
3113 if (s->in_transpose)
3118 fov_from_dfov(s, w, h);
3121 err = prepare_out(ctx);
3126 set_dimensions(s->pr_width, s->pr_height, w, h, desc);
3128 s->out_width = s->pr_width[0];
3129 s->out_height = s->pr_height[0];
3131 if (s->out_transpose)
3132 FFSWAP(int, s->out_width, s->out_height);
3134 switch (s->out_stereo) {
3136 out_offset_w = out_offset_h = 0;
3152 set_dimensions(s->out_offset_w, s->out_offset_h, out_offset_w, out_offset_h, desc);
3153 set_dimensions(s->planewidth, s->planeheight, w, h, desc);
3155 for (int i = 0; i < 4; i++)
3156 s->uv_linesize[i] = FFALIGN(s->pr_width[i], 8);
3161 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
3163 if (desc->log2_chroma_h == desc->log2_chroma_w && desc->log2_chroma_h == 0) {
3164 s->nb_allocated = 1;
3165 s->map[0] = s->map[1] = s->map[2] = s->map[3] = 0;
3167 s->nb_allocated = 2;
3168 s->map[0] = s->map[3] = 0;
3169 s->map[1] = s->map[2] = 1;
3172 for (int i = 0; i < s->nb_allocated; i++)
3173 allocate_plane(s, sizeof_uv, sizeof_ker, i);
3175 calculate_rotation_matrix(s->yaw, s->pitch, s->roll, s->rot_mat, s->rotation_order);
3176 set_mirror_modifier(s->h_flip, s->v_flip, s->d_flip, s->output_mirror_modifier);
3178 ctx->internal->execute(ctx, v360_slice, NULL, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
3183 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
3185 AVFilterContext *ctx = inlink->dst;
3186 AVFilterLink *outlink = ctx->outputs[0];
3187 V360Context *s = ctx->priv;
3191 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
3194 return AVERROR(ENOMEM);
3196 av_frame_copy_props(out, in);
3201 ctx->internal->execute(ctx, s->remap_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
3204 return ff_filter_frame(outlink, out);
3207 static av_cold void uninit(AVFilterContext *ctx)
3209 V360Context *s = ctx->priv;
3211 for (int p = 0; p < s->nb_allocated; p++) {
3214 av_freep(&s->ker[p]);
3218 static const AVFilterPad inputs[] = {
3221 .type = AVMEDIA_TYPE_VIDEO,
3222 .filter_frame = filter_frame,
3227 static const AVFilterPad outputs[] = {
3230 .type = AVMEDIA_TYPE_VIDEO,
3231 .config_props = config_output,
3236 AVFilter ff_vf_v360 = {
3238 .description = NULL_IF_CONFIG_SMALL("Convert 360 projection of video."),
3239 .priv_size = sizeof(V360Context),
3241 .query_formats = query_formats,
3244 .priv_class = &v360_class,
3245 .flags = AVFILTER_FLAG_SLICE_THREADS,