You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2468 lines
79KB

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