> From: ffmpeg-devel [mailto:ffmpeg-devel-boun...@ffmpeg.org] On Behalf > Of Eugene Lyapustin > Sent: Wednesday, August 14, 2019 9:14 AM > To: ffmpeg-devel@ffmpeg.org > Subject: [FFmpeg-devel] [PATCH v2 1/3] avfilter: add v360 filter > > Signed-off-by: Eugene Lyapustin <unishi...@gmail.com> > --- > doc/filters.texi | 137 +++ > libavfilter/Makefile | 1 + > libavfilter/allfilters.c | 1 + > libavfilter/vf_v360.c | 1847 > ++++++++++++++++++++++++++++++++++++++
Probably you also want to update the Changelog? > 4 files changed, 1986 insertions(+) > create mode 100644 libavfilter/vf_v360.c > > diff --git a/doc/filters.texi b/doc/filters.texi > index e081cdc7bc..6168a3502a 100644 > --- a/doc/filters.texi > +++ b/doc/filters.texi > @@ -17879,6 +17879,143 @@ Force a constant quantization parameter. If > not set, the filter will use the QP > from the video stream (if available). > @end table > > +@section v360 > + > +Convert 360 videos between various formats. > + > +The filter accepts the following options: > + > +@table @option > + > +@item input > +@item output > +Set format of the input/output video. > + > +Available formats: > + > +@table @samp > + > +@item e > +Equirectangular projection. > + > +@item c3x2 > +@item c6x1 > +Cubemap with 3x2/6x1 layout. > + > +Format specific options: > + > +@table @option > +@item in_forder > +@item out_forder > +Set order of faces for the input/output cubemap. Choose one direction for > each position. > + > +Designation of directions: > +@table @samp > +@item r > +right > +@item l > +left > +@item u > +up > +@item d > +down > +@item f > +forward > +@item b > +back > +@end table > + > +Default value is @b{@samp{rludfb}}. > + > +@item in_frot > +@item out_frot > +Set rotation of faces for the input/output cubemap. Choose one angle for > each position. > + > +Designation of angles: > +@table @samp > +@item 0 > +0 degrees clockwise > +@item 1 > +90 degrees clockwise > +@item 2 > +180 degrees clockwise > +@item 4 > +270 degrees clockwise > +@end table > + > +Default value is @b{@samp{000000}}. > +@end table > + > +@item eac > +Equi-Angular Cubemap. > + > +@item flat > +Regular video. @i{(output only)} > + > +Format specific options: > +@table @option > +@item h_fov > +@item v_fov > +Set horizontal/vertical field of view. Values in degrees. > +@end table > +@end table > + > +@item interp > +Set interpolation method.@* > +@i{Note: more complex interpolation methods require much more memory > to run.} > + > +Available methods: > + > +@table @samp > +@item near > +@item nearest > +Nearest neighbour. > +@item line > +@item linear > +Bilinear interpolation. > +@item cube > +@item cubic > +Bicubic interpolation. > +@item lanc > +@item lanczos > +Lanczos interpolation. > +@end table > + > +Default value is @b{@samp{line}}. > + > +@item w > +@item h > +Set the output video resolution. > + > +Default resolution depends on formats. > + > +@item yaw > +@item pitch > +@item roll > +Set rotation for the output video. Values in degrees. > + > +@item hflip > +@item vflip > +@item dflip > +Flip the output video horizontally/vertically/in-depth. Boolean values. > + > +@end table > + > +@subsection Examples > + > +@itemize > +@item > +Convert equirectangular video to cubemap with 3x2 layout using bicubic > interpolation: > +@example > +ffmpeg -i input.mkv -vf v360=e:c3x2:cubic output.mkv > +@end example > +@item > +Extract back view of Equi-Angular Cubemap: > +@example > +ffmpeg -i input.mkv -vf v360=eac:flat:yaw=180 output.mkv > +@end example > +@end itemize > + > @section vaguedenoiser > > Apply a wavelet based denoiser. > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index efc7bbb153..345f7c95cd 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -410,6 +410,7 @@ OBJS-$(CONFIG_UNSHARP_FILTER) > += vf_unsharp.o > OBJS-$(CONFIG_UNSHARP_OPENCL_FILTER) += > vf_unsharp_opencl.o opencl.o \ > > opencl/unsharp.o > OBJS-$(CONFIG_USPP_FILTER) += vf_uspp.o > +OBJS-$(CONFIG_V360_FILTER) += vf_v360.o > OBJS-$(CONFIG_VAGUEDENOISER_FILTER) += > vf_vaguedenoiser.o > OBJS-$(CONFIG_VECTORSCOPE_FILTER) += vf_vectorscope.o > OBJS-$(CONFIG_VFLIP_FILTER) += vf_vflip.o > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index abd726d616..5799fb4b3c 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -390,6 +390,7 @@ extern AVFilter ff_vf_unpremultiply; > extern AVFilter ff_vf_unsharp; > extern AVFilter ff_vf_unsharp_opencl; > extern AVFilter ff_vf_uspp; > +extern AVFilter ff_vf_v360; > extern AVFilter ff_vf_vaguedenoiser; > extern AVFilter ff_vf_vectorscope; > extern AVFilter ff_vf_vflip; > diff --git a/libavfilter/vf_v360.c b/libavfilter/vf_v360.c > new file mode 100644 > index 0000000000..5c377827b0 > --- /dev/null > +++ b/libavfilter/vf_v360.c > @@ -0,0 +1,1847 @@ > +/* > + * Copyright (c) 2019 Eugene Lyapustin > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/** > + * @file > + * 360 video conversion filter. > + * Principle of operation: > + * > + * (for each pixel in output frame)\n > + * 1) Calculate OpenGL-like coordinates (x, y, z) for pixel position (i, j)\n > + * 2) Apply 360 operations (rotation, mirror) to (x, y, z)\n > + * 3) Calculate pixel position (u, v) in input frame\n > + * 4) Calculate interpolation window and weight for each pixel > + * > + * (for each frame)\n > + * 5) Remap input frame to output frame using precalculated data\n > + */ > + > +#include "libavutil/eval.h" > +#include "libavutil/imgutils.h" > +#include "libavutil/pixdesc.h" > +#include "libavutil/opt.h" > +#include "avfilter.h" > +#include "formats.h" > +#include "internal.h" > +#include "video.h" > + > +enum Projections { > + EQUIRECTANGULAR, > + CUBEMAP_3_2, > + CUBEMAP_6_1, > + EQUIANGULAR, > + FLAT, > + NB_PROJECTIONS, > +}; > + > +enum InterpMethod { > + NEAREST, > + BILINEAR, > + BICUBIC, > + LANCZOS, > + NB_INTERP_METHODS, > +}; > + > +enum Faces { > + TOP_LEFT, > + TOP_MIDDLE, > + TOP_RIGHT, > + BOTTOM_LEFT, > + BOTTOM_MIDDLE, > + BOTTOM_RIGHT, > + NB_FACES, > +}; > + > +enum Direction { > + RIGHT, ///< Axis +X > + LEFT, ///< Axis -X > + UP, ///< Axis +Y > + DOWN, ///< Axis -Y > + FRONT, ///< Axis -Z > + BACK, ///< Axis +Z > + NB_DIRECTIONS, > +}; > + > +enum Rotation { > + ROT_0, > + ROT_90, > + ROT_180, > + ROT_270, > + NB_ROTATIONS, > +}; > + > +typedef struct V360Context { > + const AVClass *class; > + int in, out; > + int interp; > + int width, height; > + char* in_forder; > + char* out_forder; > + char* in_frot; > + char* out_frot; > + > + int in_cubemap_face_order[6]; > + int out_cubemap_direction_order[6]; > + int in_cubemap_face_rotation[6]; > + int out_cubemap_face_rotation[6]; > + > + float yaw, pitch, roll; > + > + int h_flip, v_flip, d_flip; > + > + float h_fov, v_fov; > + float flat_range[3]; > + > + int planewidth[4], planeheight[4]; > + int inplanewidth[4], inplaneheight[4]; > + int nb_planes; > + > + void *remap[4]; > + > + int (*remap_slice)(AVFilterContext *ctx, void *arg, int jobnr, int > nb_jobs); > +} V360Context; > + > +typedef struct ThreadData { > + V360Context *s; > + AVFrame *in; > + AVFrame *out; > + int nb_planes; > +} ThreadData; > + > +#define OFFSET(x) offsetof(V360Context, x) > +#define FLAGS > AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM > + > +static const AVOption v360_options[] = { > + { "input", "set input projection", OFFSET(in), > AV_OPT_TYPE_INT, {.i64=EQUIRECTANGULAR}, 0, > NB_PROJECTIONS-1, FLAGS, "in" }, > + { "e", "equirectangular", 0, > AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, > 0, FLAGS, "in" }, > + { "c3x2", "cubemap3x2", > 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_3_2}, 0, > 0, FLAGS, "in" }, > + { "c6x1", "cubemap6x1", > 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_6_1}, 0, > 0, FLAGS, "in" }, > + { "eac", "equi-angular", > 0, AV_OPT_TYPE_CONST, {.i64=EQUIANGULAR}, 0, > 0, FLAGS, "in" }, > + { "output", "set output projection", OFFSET(out), > AV_OPT_TYPE_INT, {.i64=CUBEMAP_3_2}, 0, > NB_PROJECTIONS-1, FLAGS, "out" }, > + { "e", "equirectangular", 0, > AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, > 0, FLAGS, "out" }, > + { "c3x2", "cubemap3x2", > 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_3_2}, 0, > 0, FLAGS, "out" }, > + { "c6x1", "cubemap6x1", > 0, AV_OPT_TYPE_CONST, {.i64=CUBEMAP_6_1}, 0, > 0, FLAGS, "out" }, > + { "eac", "equi-angular", > 0, AV_OPT_TYPE_CONST, {.i64=EQUIANGULAR}, 0, > 0, FLAGS, "out" }, > + { "flat", "regular video", 0, > AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, > 0, FLAGS, "out" }, > + { "interp", "set interpolation method", OFFSET(interp), > AV_OPT_TYPE_INT, {.i64=BILINEAR}, 0, > NB_INTERP_METHODS-1, FLAGS, "interp" }, > + { "near", "nearest neighbour", 0, > AV_OPT_TYPE_CONST, {.i64=NEAREST}, 0, > 0, FLAGS, "interp" }, > + { "nearest", "nearest neighbour", 0, > AV_OPT_TYPE_CONST, {.i64=NEAREST}, 0, > 0, FLAGS, "interp" }, > + { "line", "bilinear interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=BILINEAR}, 0, > 0, FLAGS, "interp" }, > + { "linear", "bilinear interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=BILINEAR}, 0, > 0, FLAGS, "interp" }, > + { "cube", "bicubic interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=BICUBIC}, 0, > 0, FLAGS, "interp" }, > + { "cubic", "bicubic interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=BICUBIC}, 0, > 0, FLAGS, "interp" }, > + { "lanc", "lanczos interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=LANCZOS}, 0, > 0, FLAGS, "interp" }, > + { "lanczos", "lanczos interpolation", 0, > AV_OPT_TYPE_CONST, {.i64=LANCZOS}, 0, > 0, FLAGS, "interp" }, > + { "w", "output width", OFFSET(width), > AV_OPT_TYPE_INT, {.i64=0}, 0, > INT_MAX, FLAGS, "w"}, > + { "h", "output height", OFFSET(height), > AV_OPT_TYPE_INT, {.i64=0}, 0, > INT_MAX, FLAGS, "h"}, > + { "in_forder", "input cubemap face order", OFFSET(in_forder), > AV_OPT_TYPE_STRING, {.str="rludfb"}, 0, NB_DIRECTIONS-1, > FLAGS, "in_forder"}, > + {"out_forder", "output cubemap face order", OFFSET(out_forder), > AV_OPT_TYPE_STRING, {.str="rludfb"}, 0, NB_DIRECTIONS-1, > FLAGS, "out_forder"}, > + { "in_frot", "input cubemap face rotation", OFFSET(in_frot), > AV_OPT_TYPE_STRING, {.str="000000"}, 0, > NB_DIRECTIONS-1, FLAGS, "in_frot"}, > + { "out_frot", "output cubemap face rotation",OFFSET(out_frot), > AV_OPT_TYPE_STRING, {.str="000000"}, 0, > NB_DIRECTIONS-1, FLAGS, "out_frot"}, > + { "yaw", "yaw rotation", > OFFSET(yaw), AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, > 180.f, FLAGS, "yaw"}, > + { "pitch", "pitch rotation", OFFSET(pitch), > AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, 180.f, > FLAGS, "pitch"}, > + { "roll", "roll rotation", OFFSET(roll), > AV_OPT_TYPE_FLOAT, {.dbl=0.f}, -180.f, 180.f, > FLAGS, "roll"}, > + { "h_fov", "horizontal field of view", OFFSET(h_fov), > AV_OPT_TYPE_FLOAT, {.dbl=90.f}, 0.f, 180.f, > FLAGS, "h_fov"}, > + { "v_fov", "vertical field of view", OFFSET(v_fov), > AV_OPT_TYPE_FLOAT, {.dbl=45.f}, 0.f, 90.f, > FLAGS, "v_fov"}, > + { "h_flip", "flip video horizontally", OFFSET(h_flip), > AV_OPT_TYPE_BOOL, {.i64=0}, 0, > 1, FLAGS, "h_flip"}, > + { "v_flip", "flip video vertically", OFFSET(v_flip), > AV_OPT_TYPE_BOOL, {.i64=0}, 0, > 1, FLAGS, "v_flip"}, > + { "d_flip", "flip video indepth", OFFSET(d_flip), > AV_OPT_TYPE_BOOL, {.i64=0}, 0, > 1, FLAGS, "d_flip"}, > + { NULL } > +}; > + > +AVFILTER_DEFINE_CLASS(v360); > + > +static int query_formats(AVFilterContext *ctx) > +{ > + static const enum AVPixelFormat pix_fmts[] = { > + // YUVA444 > + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA444P9, > + AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12, > + AV_PIX_FMT_YUVA444P16, > + > + // YUVA422 > + AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA422P9, > + AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12, > + AV_PIX_FMT_YUVA422P16, > + > + // YUVA420 > + AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA420P9, > + AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16, > + > + // YUVJ > + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, > + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, > + AV_PIX_FMT_YUVJ411P, > + > + // YUV444 > + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P9, > + AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12, > + AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16, > + > + // YUV440 > + AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV440P10, > + AV_PIX_FMT_YUV440P12, > + > + // YUV422 > + AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P9, > + AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12, > + AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV422P16, > + > + // YUV420 > + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P9, > + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12, > + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV420P16, > + > + // YUV411 > + AV_PIX_FMT_YUV411P, > + > + // YUV410 > + AV_PIX_FMT_YUV410P, > + > + // GBR > + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, > + AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, > + AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, > + > + // GBRA > + AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, > + AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, > + > + // GRAY > + AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, > + AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, > + AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, > + > + AV_PIX_FMT_NONE > + }; > + > + AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); > + if (!fmts_list) > + return AVERROR(ENOMEM); > + return ff_set_common_formats(ctx, fmts_list); > +} > + > +typedef struct XYRemap1 { > + uint16_t u; > + uint16_t v; > +} XYRemap1; > + > +/** > + * Generate no-interpolation remapping function with a given pixel depth. > + * > + * @param bits number of bits per pixel > + * @param div number of bytes per pixel > + */ > +#define DEFINE_REMAP1(bits, div) > \ > +static int remap1_##bits##bit_slice(AVFilterContext *ctx, void *arg, int > jobnr, int nb_jobs) \ > +{ > \ > + ThreadData *td = (ThreadData*)arg; > \ > + const V360Context *s = td->s; > \ > + const AVFrame *in = td->in; > \ > + AVFrame *out = td->out; > \ > + > \ > + int plane, x, y; > \ > + > \ > + for (plane = 0; plane < td->nb_planes; plane++) > { \ > + const int in_linesize = in->linesize[plane] / div; > \ > + const int out_linesize = out->linesize[plane] / div; > \ > + const uint##bits##_t *src = (const uint##bits##_t > *)in->data[plane]; \ > + uint##bits##_t *dst = (uint##bits##_t *)out->data[plane]; > \ > + const XYRemap1 *remap = s->remap[plane]; > \ > + const int width = s->planewidth[plane]; > \ > + const int height = s->planeheight[plane]; > \ > + > \ > + const int slice_start = (height * jobnr ) / nb_jobs; > \ > + const int slice_end = (height * (jobnr + 1)) / nb_jobs; > \ > + > \ > + for (y = slice_start; y < slice_end; y++) > { \ > + uint##bits##_t *d = dst + y * out_linesize; > \ > + for (x = 0; x < width; x++) > { \ > + const XYRemap1 *r = &remap[y * width + x]; > \ > + > \ > + *d++ = src[r->v * in_linesize + r->u]; > \ > + } > \ > + } > \ > + } > \ > + > \ > + return 0; > \ > +} > + > +DEFINE_REMAP1( 8, 1) > +DEFINE_REMAP1(16, 2) > + > +typedef struct XYRemap2 { > + uint16_t u[2][2]; > + uint16_t v[2][2]; > + float ker[2][2]; > +} XYRemap2; > + > +typedef struct XYRemap4 { > + uint16_t u[4][4]; > + uint16_t v[4][4]; > + float ker[4][4]; > +} XYRemap4; > + > +/** > + * Generate remapping function with a given window size and pixel depth. > + * > + * @param window_size size of interpolation window > + * @param bits number of bits per pixel > + * @param div number of bytes per pixel > + */ > +#define DEFINE_REMAP(window_size, bits, div) > \ > +static int remap##window_size##_##bits##bit_slice(AVFilterContext *ctx, > void *arg, int jobnr, int nb_jobs) \ > +{ > \ > + ThreadData *td = (ThreadData*)arg; > \ > + const V360Context *s = td->s; > \ > + const AVFrame *in = td->in; > \ > + AVFrame *out = td->out; > \ > + > \ > + int plane, x, y, i, j; > \ > + > \ > + for (plane = 0; plane < td->nb_planes; plane++) > { \ > + const int in_linesize = in->linesize[plane] / div; > \ > + const int out_linesize = out->linesize[plane] / div; > \ > + const uint##bits##_t *src = (const uint##bits##_t > *)in->data[plane]; \ > + uint##bits##_t *dst = (uint##bits##_t *)out->data[plane]; > \ > + const XYRemap##window_size *remap = s->remap[plane]; > \ > + const int width = s->planewidth[plane]; > \ > + const int height = s->planeheight[plane]; > \ > + > \ > + const int slice_start = (height * jobnr ) / nb_jobs; > \ > + const int slice_end = (height * (jobnr + 1)) / nb_jobs; > \ > + > \ > + for (y = slice_start; y < slice_end; y++) > { \ > + uint##bits##_t *d = dst + y * out_linesize; > \ > + for (x = 0; x < width; x++) > { > \ > + const XYRemap##window_size *r = &remap[y * width + > x]; \ > + float tmp = 0.f; > \ > + > \ > + for (i = 0; i < window_size; i++) > { \ > + for (j = 0; j < window_size; j++) > { \ > + tmp += r->ker[i][j] * src[r->v[i][j] * in_linesize > + r->u[i][j]]; \ > + } > \ > + } > \ > + > \ > + *d++ = av_clip_uint##bits(roundf(tmp)); > \ > + } > \ > + } > \ > + } > \ > + > \ > + return 0; > \ > +} > + > +DEFINE_REMAP(2, 8, 1) > +DEFINE_REMAP(4, 8, 1) > +DEFINE_REMAP(2, 16, 2) > +DEFINE_REMAP(4, 16, 2) > + > +/** > + * Save nearest pixel coordinates for remapping. > + * > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + * @param shift shift for remap array > + * @param r_tmp calculated 4x4 window > + * @param r_void remap data > + */ > +static void nearest_kernel(float du, float dv, int shift, const XYRemap4 > *r_tmp, void *r_void) > +{ > + XYRemap1 *r = (XYRemap1*)r_void + shift; > + const int i = roundf(dv) + 1; > + const int j = roundf(du) + 1; > + > + r->u = r_tmp->u[i][j]; > + r->v = r_tmp->v[i][j]; > +} > + > +/** > + * Calculate kernel for bilinear interpolation. > + * > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + * @param shift shift for remap array > + * @param r_tmp calculated 4x4 window > + * @param r_void remap data > + */ > +static void bilinear_kernel(float du, float dv, int shift, const XYRemap4 > *r_tmp, void *r_void) > +{ > + XYRemap2 *r = (XYRemap2*)r_void + shift; > + int i, j; > + > + for (i = 0; i < 2; i++) { > + for (j = 0; j < 2; j++) { > + r->u[i][j] = r_tmp->u[i + 1][j + 1]; > + r->v[i][j] = r_tmp->v[i + 1][j + 1]; > + } > + } > + > + r->ker[0][0] = (1.f - du) * (1.f - dv); > + r->ker[0][1] = du * (1.f - dv); > + r->ker[1][0] = (1.f - du) * dv; > + r->ker[1][1] = du * dv; > +} > + > +/** > + * Calculate 1-dimensional cubic coefficients. > + * > + * @param t relative coordinate > + * @param coeffs coefficients > + */ > +static inline void calculate_bicubic_coeffs(float t, float *coeffs) > +{ > + const float tt = t * t; > + const float ttt = t * t * t; > + > + coeffs[0] = - t / 3.f + tt / 2.f - ttt / 6.f; > + coeffs[1] = 1.f - t / 2.f - tt + ttt / 2.f; > + coeffs[2] = t + tt / 2.f - ttt / 2.f; > + coeffs[3] = - t / 6.f + ttt / 6.f; > +} > + > +/** > + * Calculate kernel for bicubic interpolation. > + * > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + * @param shift shift for remap array > + * @param r_tmp calculated 4x4 window > + * @param r_void remap data > + */ > +static void bicubic_kernel(float du, float dv, int shift, const XYRemap4 > *r_tmp, void *r_void) > +{ > + XYRemap4 *r = (XYRemap4*)r_void + shift; > + int i, j; > + float du_coeffs[4]; > + float dv_coeffs[4]; > + > + calculate_bicubic_coeffs(du, du_coeffs); > + calculate_bicubic_coeffs(dv, dv_coeffs); > + > + for (i = 0; i < 4; i++) { > + for (j = 0; j < 4; j++) { > + r->u[i][j] = r_tmp->u[i][j]; > + r->v[i][j] = r_tmp->v[i][j]; > + r->ker[i][j] = du_coeffs[j] * dv_coeffs[i]; > + } > + } > +} > + > +/** > + * Calculate 1-dimensional lanczos coefficients. > + * > + * @param t relative coordinate > + * @param coeffs coefficients > + */ > +static inline void calculate_lanczos_coeffs(float t, float *coeffs) > +{ > + int i; > + float sum = 0.f; > + > + for (i = 0; i < 4; i++) { > + const float x = M_PI * (t - i + 1); > + if (x == 0.f) { > + coeffs[i] = 1.f; > + } else { > + coeffs[i] = sinf(x) * sinf(x / 2.f) / (x * x / 2.f); > + } > + sum += coeffs[i]; > + } > + > + for (i = 0; i < 4; i++) { > + coeffs[i] /= sum; > + } > +} > + > +/** > + * Calculate kernel for lanczos interpolation. > + * > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + * @param shift shift for remap array > + * @param r_tmp calculated 4x4 window > + * @param r_void remap data > + */ > +static void lanczos_kernel(float du, float dv, int shift, const XYRemap4 > *r_tmp, void *r_void) > +{ > + XYRemap4 *r = (XYRemap4*)r_void + shift; > + int i, j; > + float du_coeffs[4]; > + float dv_coeffs[4]; > + > + calculate_lanczos_coeffs(du, du_coeffs); > + calculate_lanczos_coeffs(dv, dv_coeffs); > + > + for (i = 0; i < 4; i++) { > + for (j = 0; j < 4; j++) { > + r->u[i][j] = r_tmp->u[i][j]; > + r->v[i][j] = r_tmp->v[i][j]; > + r->ker[i][j] = du_coeffs[j] * dv_coeffs[i]; > + } > + } > +} > + > +/** > + * Modulo operation with only positive remainders. > + * > + * @param a dividend > + * @param b divisor > + * > + * @return positive remainder of (a / b) > + */ > +static inline int mod(int a, int b) > +{ > + const int res = a % b; > + if (res < 0) { > + return res + b; > + } else { > + return res; > + } > +} > + > +/** > + * Convert char to corresponding direction. > + * Used for cubemap options. > + */ > +static int get_direction(char c) > +{ > + switch (c) { > + case 'r': > + return RIGHT; > + case 'l': > + return LEFT; > + case 'u': > + return UP; > + case 'd': > + return DOWN; > + case 'f': > + return FRONT; > + case 'b': > + return BACK; > + default: > + return -1; > + } > +} > + > +/** > + * Convert char to corresponding rotation angle. > + * Used for cubemap options. > + */ > +static int get_rotation(char c) > +{ > + switch (c) { > + case '0': > + return ROT_0; > + case '1': > + return ROT_90; > + case '2': > + return ROT_180; > + case '3': > + return ROT_270; > + default: > + return -1; > + } > +} "case” should be kept alignment as "swicth", remove the blanks. > +/** > + * Prepare data for processing cubemap input format. > + * > + * @param ctx filter context > + * > + * @return error code > + */ > +static int prepare_cube_in(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + > + for (int face = 0; face < NB_FACES; face++) { > + const char c = s->in_forder[face]; > + int direction; > + > + if (c == '\0') { > + av_log(ctx, AV_LOG_ERROR, > + "Incomplete in_forder option. Direction for all 6 > faces should be specified.\n"); > + return AVERROR(EINVAL); > + } > + > + direction = get_direction(c); > + if (direction == -1) { > + av_log(ctx, AV_LOG_ERROR, > + "Incorrect direction symbol '%c' in in_forder > option.\n", c); > + return AVERROR(EINVAL); > + } > + > + s->in_cubemap_face_order[direction] = face; > + } > + > + for (int face = 0; face < NB_FACES; face++) { Moving "int face" as the beginning of function can avoid int twice. > + const char c = s->in_frot[face]; > + int rotation; > + > + if (c == '\0') { > + av_log(ctx, AV_LOG_ERROR, > + "Incomplete in_frot option. Rotation for all 6 faces > should be specified.\n"); > + return AVERROR(EINVAL); > + } > + > + rotation = get_rotation(c); > + if (rotation == -1) { > + av_log(ctx, AV_LOG_ERROR, > + "Incorrect rotation symbol '%c' in in_frot option.\n", > c); > + return AVERROR(EINVAL); > + } > + > + s->in_cubemap_face_rotation[face] = rotation; > + } > + > + return 0; > +} > + > +/** > + * Prepare data for processing cubemap output format. > + * > + * @param ctx filter context > + * > + * @return error code > + */ > +static int prepare_cube_out(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + > + for (int face = 0; face < NB_FACES; face++) { > + const char c = s->out_forder[face]; > + int direction; > + > + if (c == '\0') { > + av_log(ctx, AV_LOG_ERROR, > + "Incomplete out_forder option. Direction for all 6 > faces should be specified.\n"); > + return AVERROR(EINVAL); > + } > + > + direction = get_direction(c); > + if (direction == -1) { > + av_log(ctx, AV_LOG_ERROR, > + "Incorrect direction symbol '%c' in out_forder > option.\n", c); > + return AVERROR(EINVAL); > + } > + > + s->out_cubemap_direction_order[face] = direction; > + } > + > + for (int face = 0; face < NB_FACES; face++) { Same. > + const char c = s->out_frot[face]; > + int rotation; > + > + if (c == '\0') { > + av_log(ctx, AV_LOG_ERROR, > + "Incomplete out_frot option. Rotation for all 6 > faces should be specified.\n"); > + return AVERROR(EINVAL); > + } > + > + rotation = get_rotation(c); > + if (rotation == -1) { > + av_log(ctx, AV_LOG_ERROR, > + "Incorrect rotation symbol '%c' in out_frot > option.\n", c); > + return AVERROR(EINVAL); > + } > + > + s->out_cubemap_face_rotation[face] = rotation; > + } > + > + return 0; > +} > + > +static inline void rotate_cube_face(float *uf, float *vf, int rotation) > +{ > + float tmp; > + > + switch (rotation) { > + case ROT_0: > + break; > + case ROT_90: > + tmp = *uf; > + *uf = -*vf; > + *vf = tmp; > + break; > + case ROT_180: > + *uf = -*uf; > + *vf = -*vf; > + break; > + case ROT_270: > + tmp = -*uf; > + *uf = *vf; > + *vf = tmp; > + break; > + } > +} > + > +static inline void rotate_cube_face_inverse(float *uf, float *vf, int > rotation) > +{ > + float tmp; > + > + switch (rotation) { > + case ROT_0: > + break; > + case ROT_90: > + tmp = -*uf; > + *uf = *vf; > + *vf = tmp; > + break; > + case ROT_180: > + *uf = -*uf; > + *vf = -*vf; > + break; > + case ROT_270: > + tmp = *uf; > + *uf = -*vf; > + *vf = tmp; > + break; > + } > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding cubemap position. > + * Common operation for every cubemap. > + * > + * @param s filter context > + * @param uf horizontal cubemap coordinate [0, 1) > + * @param vf vertical cubemap coordinate [0, 1) > + * @param face face of cubemap > + * @param vec coordinates on sphere > + */ > +static void cube_to_xyz(const V360Context *s, > + float uf, float vf, int face, > + float *vec) > +{ > + const int direction = s->out_cubemap_direction_order[face]; > + float norm; > + float l_x, l_y, l_z; > + > + rotate_cube_face_inverse(&uf, &vf, > s->out_cubemap_face_rotation[face]); > + > + switch (direction) { > + case RIGHT: > + l_x = 1.f; > + l_y = -vf; > + l_z = uf; > + break; > + case LEFT: > + l_x = -1.f; > + l_y = -vf; > + l_z = -uf; > + break; > + case UP: > + l_x = uf; > + l_y = 1.f; > + l_z = -vf; > + break; > + case DOWN: > + l_x = uf; > + l_y = -1.f; > + l_z = vf; > + break; > + case FRONT: > + l_x = uf; > + l_y = -vf; > + l_z = -1.f; > + break; > + case BACK: > + l_x = -uf; > + l_y = -vf; > + l_z = 1.f; > + break; > + } > + > + norm = sqrtf(l_x * l_x + l_y * l_y + l_z * l_z); > + vec[0] = l_x / norm; > + vec[1] = l_y / norm; > + vec[2] = l_z / norm; > +} > + > +/** > + * Calculate cubemap position for corresponding 3D coordinates on sphere. > + * Common operation for every cubemap. > + * > + * @param s filter context > + * @param vec coordinated on sphere > + * @param uf horizontal cubemap coordinate [0, 1) > + * @param vf vertical cubemap coordinate [0, 1) > + * @param direction direction of view > + */ > +static void xyz_to_cube(const V360Context *s, > + const float *vec, > + float *uf, float *vf, int *direction) > +{ > + const float phi = atan2f(vec[0], -vec[2]); > + const float theta = asinf(-vec[1]); > + float phi_norm, theta_threshold; > + int face; > + > + if (phi >= -M_PI_4 && phi < M_PI_4) { > + *direction = FRONT; > + phi_norm = phi; > + } else if (phi >= -(M_PI_2 + M_PI_4) && phi < -M_PI_4) { > + *direction = LEFT; > + phi_norm = phi + M_PI_2; > + } else if (phi >= M_PI_4 && phi < M_PI_2 + M_PI_4) { > + *direction = RIGHT; > + phi_norm = phi - M_PI_2; > + } else { > + *direction = BACK; > + phi_norm = phi + ((phi > 0.f) ? -M_PI : M_PI); > + } > + > + theta_threshold = atanf(cosf(phi_norm)); > + if (theta > theta_threshold) { > + *direction = DOWN; > + } else if (theta < -theta_threshold) { > + *direction = UP; > + } > + > + switch (*direction) { > + case RIGHT: > + *uf = vec[2] / vec[0]; > + *vf = -vec[1] / vec[0]; > + break; > + case LEFT: > + *uf = vec[2] / vec[0]; > + *vf = vec[1] / vec[0]; > + break; > + case UP: > + *uf = vec[0] / vec[1]; > + *vf = -vec[2] / vec[1]; > + break; > + case DOWN: > + *uf = -vec[0] / vec[1]; > + *vf = -vec[2] / vec[1]; > + break; > + case FRONT: > + *uf = -vec[0] / vec[2]; > + *vf = vec[1] / vec[2]; > + break; > + case BACK: > + *uf = -vec[0] / vec[2]; > + *vf = -vec[1] / vec[2]; > + break; > + } > + > + face = s->in_cubemap_face_order[*direction]; > + rotate_cube_face(uf, vf, s->in_cubemap_face_rotation[face]); > +} > + > +/** > + * Find position on another cube face in case of overflow/underflow. > + * Used for calculation of interpolation window. > + * > + * @param s filter context > + * @param uf horizontal cubemap coordinate > + * @param vf vertical cubemap coordinate > + * @param direction direction of view > + * @param new_uf new horizontal cubemap coordinate > + * @param new_vf new vertical cubemap coordinate > + * @param face face position on cubemap > + */ > +static void process_cube_coordinates(const V360Context *s, > + float uf, float vf, int direction, > + float *new_uf, float *new_vf, int > *face) > +{ > + /* > + * Cubemap orientation > + * > + * width > + * <-------> > + * +-------+ > + * | | U > + * | up | h -------> > + * +-------+-------+-------+-------+ ^ e | > + * | | | | | | i V | > + * | left | front | right | back | | g | > + * +-------+-------+-------+-------+ v h v > + * | | t > + * | down | > + * +-------+ > + */ > + > + *face = s->in_cubemap_face_order[direction]; > + rotate_cube_face_inverse(&uf, &vf, > s->in_cubemap_face_rotation[*face]); > + > + if ((uf < -1.f || uf >= 1.f) && (vf < -1.f || vf >= 1.f)) { > + // There are no pixels to use in this case > + *new_uf = uf; > + *new_vf = vf; > + } else if (uf < -1.f) { > + uf += 2.f; > + switch (direction) { > + case RIGHT: > + direction = FRONT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case LEFT: > + direction = BACK; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case UP: > + direction = LEFT; > + *new_uf = vf; > + *new_vf = -uf; > + break; > + case DOWN: > + direction = LEFT; > + *new_uf = -vf; > + *new_vf = uf; > + break; > + case FRONT: > + direction = LEFT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case BACK: > + direction = RIGHT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + } > + } else if (uf >= 1.f) { > + uf -= 2.f; > + switch (direction) { > + case RIGHT: > + direction = BACK; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case LEFT: > + direction = FRONT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case UP: > + direction = RIGHT; > + *new_uf = -vf; > + *new_vf = uf; > + break; > + case DOWN: > + direction = RIGHT; > + *new_uf = vf; > + *new_vf = -uf; > + break; > + case FRONT: > + direction = RIGHT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case BACK: > + direction = LEFT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + } > + } else if (vf < -1.f) { > + vf += 2.f; > + switch (direction) { > + case RIGHT: > + direction = UP; > + *new_uf = vf; > + *new_vf = -uf; > + break; > + case LEFT: > + direction = UP; > + *new_uf = -vf; > + *new_vf = uf; > + break; > + case UP: > + direction = BACK; > + *new_uf = -uf; > + *new_vf = -vf; > + break; > + case DOWN: > + direction = FRONT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case FRONT: > + direction = UP; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case BACK: > + direction = UP; > + *new_uf = -uf; > + *new_vf = -vf; > + break; > + } > + } else if (vf >= 1.f) { > + vf -= 2.f; > + switch (direction) { > + case RIGHT: > + direction = DOWN; > + *new_uf = -vf; > + *new_vf = uf; > + break; > + case LEFT: > + direction = DOWN; > + *new_uf = vf; > + *new_vf = -uf; > + break; > + case UP: > + direction = FRONT; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case DOWN: > + direction = BACK; > + *new_uf = -uf; > + *new_vf = -vf; > + break; > + case FRONT: > + direction = DOWN; > + *new_uf = uf; > + *new_vf = vf; > + break; > + case BACK: > + direction = DOWN; > + *new_uf = -uf; > + *new_vf = -vf; > + break; > + } > + } else { > + // Inside cube face > + *new_uf = uf; > + *new_vf = vf; > + } > + > + *face = s->in_cubemap_face_order[direction]; > + rotate_cube_face(new_uf, new_vf, > s->in_cubemap_face_rotation[*face]); > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding frame position in > cubemap3x2 format. > + * > + * @param s filter context > + * @param i horizontal position on frame [0, height) > + * @param j vertical position on frame [0, width) > + * @param width frame width > + * @param height frame height > + * @param vec coordinates on sphere > + */ > +static void cube3x2_to_xyz(const V360Context *s, > + int i, int j, int width, int height, > + float *vec) > +{ > + const float ew = width / 3.f; > + const float eh = height / 2.f; > + > + const int u_face = floorf(i / ew); > + const int v_face = floorf(j / eh); > + const int face = u_face + 3 * v_face; > + > + const int u_shift = ceilf(ew * u_face); > + const int v_shift = ceilf(eh * v_face); > + const int ewi = ceilf(ew * (u_face + 1)) - u_shift; > + const int ehi = ceilf(eh * (v_face + 1)) - v_shift; > + > + const float uf = 2.f * (i - u_shift) / ewi - 1.f; > + const float vf = 2.f * (j - v_shift) / ehi - 1.f; > + > + cube_to_xyz(s, uf, vf, face, vec); > +} > + > +/** > + * Calculate frame position in cubemap3x2 format for corresponding 3D > coordinates on sphere. > + * > + * @param s filter context > + * @param vec coordinates on sphere > + * @param width frame width > + * @param height frame height > + * @param us horizontal coordinates for interpolation window > + * @param vs vertical coordinates for interpolation window > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + */ > +static void xyz_to_cube3x2(const V360Context *s, > + const float *vec, int width, int height, > + uint16_t us[4][4], uint16_t vs[4][4], float > *du, float *dv) > +{ > + const float ew = width / 3.f; > + const float eh = height / 2.f; > + float uf, vf; > + int ui, vi; > + int ewi, ehi; > + int i, j; > + int direction, face; > + int u_face, v_face; > + > + xyz_to_cube(s, vec, &uf, &vf, &direction); > + > + face = s->in_cubemap_face_order[direction]; > + u_face = face % 3; > + v_face = face / 3; > + ewi = ceilf(ew * (u_face + 1)) - ceilf(ew * u_face); > + ehi = ceilf(eh * (v_face + 1)) - ceilf(eh * v_face); > + > + uf = 0.5f * ewi * (uf + 1.f); > + vf = 0.5f * ehi * (vf + 1.f); > + > + ui = floorf(uf); > + vi = floorf(vf); > + > + *du = uf - ui; > + *dv = vf - vi; > + > + for (i = -1; i < 3; i++) { > + for (j = -1; j < 3; j++) { > + float u, v; > + int u_shift, v_shift; > + int new_ewi, new_ehi; > + > + process_cube_coordinates(s, 2.f * (ui + j) / ewi - 1.f, > + 2.f * (vi + i) / ehi - 1.f, > + direction, &u, &v, &face); > + u_face = face % 3; > + v_face = face / 3; > + u_shift = ceilf(ew * u_face); > + v_shift = ceilf(eh * v_face); > + new_ewi = ceilf(ew * (u_face + 1)) - u_shift; > + new_ehi = ceilf(eh * (v_face + 1)) - v_shift; > + > + us[i + 1][j + 1] = u_shift + av_clip(roundf(0.5f * new_ewi * (u > + 1.f)), 0, new_ewi - 1); > + vs[i + 1][j + 1] = v_shift + av_clip(roundf(0.5f * new_ehi * (v + > 1.f)), 0, new_ehi - 1); > + } > + } > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding frame position in > cubemap6x1 format. > + * > + * @param s filter context > + * @param i horizontal position on frame [0, height) > + * @param j vertical position on frame [0, width) > + * @param width frame width > + * @param height frame height > + * @param vec coordinates on sphere > + */ > +static void cube6x1_to_xyz(const V360Context *s, > + int i, int j, int width, int height, > + float *vec) > +{ > + const float ew = width / 6.f; > + const float eh = height; > + > + const int face = floorf(i / ew); > + > + const int u_shift = ceilf(ew * face); > + const int ewi = ceilf(ew * (face + 1)) - u_shift; > + > + const float uf = 2.f * (i - u_shift) / ewi - 1.f; > + const float vf = 2.f * j / eh - 1.f; > + > + cube_to_xyz(s, uf, vf, face, vec); > +} > + > +/** > + * Calculate frame position in cubemap6x1 format for corresponding 3D > coordinates on sphere. > + * > + * @param s filter context > + * @param vec coordinates on sphere > + * @param width frame width > + * @param height frame height > + * @param us horizontal coordinates for interpolation window > + * @param vs vertical coordinates for interpolation window > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + */ > +static void xyz_to_cube6x1(const V360Context *s, > + const float *vec, int width, int height, > + uint16_t us[4][4], uint16_t vs[4][4], float > *du, float *dv) > +{ > + const float ew = width / 6.f; > + const float eh = height; > + float uf, vf; > + int ui, vi; > + int ewi; > + int i, j; > + int direction, face; > + > + xyz_to_cube(s, vec, &uf, &vf, &direction); > + > + face = s->in_cubemap_face_order[direction]; > + ewi = ceilf(ew * (face + 1)) - ceilf(ew * face); > + > + uf = 0.5f * ewi * (uf + 1.f); > + vf = 0.5f * eh * (vf + 1.f); > + > + ui = floorf(uf); > + vi = floorf(vf); > + > + *du = uf - ui; > + *dv = vf - vi; > + > + for (i = -1; i < 3; i++) { > + for (j = -1; j < 3; j++) { > + float u, v; > + int u_shift; > + int new_ewi; > + > + process_cube_coordinates(s, 2.f * (ui + j) / ewi - 1.f, > + 2.f * (vi + i) / eh - 1.f, > + direction, &u, &v, &face); > + u_shift = ceilf(ew * face); > + new_ewi = ceilf(ew * (face + 1)) - u_shift; > + > + us[i + 1][j + 1] = u_shift + av_clip(roundf(0.5f * new_ewi * (u > + 1.f)), 0, new_ewi - 1); > + vs[i + 1][j + 1] = av_clip(roundf(0.5f * eh > * (v + 1.f)), 0, eh - 1); > + } > + } > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding frame position in > equirectangular format. > + * > + * @param s filter context > + * @param i horizontal position on frame [0, height) > + * @param j vertical position on frame [0, width) > + * @param width frame width > + * @param height frame height > + * @param vec coordinates on sphere > + */ > +static void equirect_to_xyz(const V360Context *s, > + int i, int j, int width, int height, > + float *vec) > +{ > + const float phi = ((2.f * i) / width - 1.f) * M_PI; > + const float theta = ((2.f * j) / height - 1.f) * M_PI_2; > + > + const float sin_phi = sinf(phi); > + const float cos_phi = cosf(phi); > + const float sin_theta = sinf(theta); > + const float cos_theta = cosf(theta); > + > + vec[0] = cos_theta * sin_phi; > + vec[1] = -sin_theta; > + vec[2] = -cos_theta * cos_phi; > +} > + > +/** > + * Calculate frame position in equirectangular format for corresponding 3D > coordinates on sphere. > + * > + * @param s filter context > + * @param vec coordinates on sphere > + * @param width frame width > + * @param height frame height > + * @param us horizontal coordinates for interpolation window > + * @param vs vertical coordinates for interpolation window > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + */ > +static void xyz_to_equirect(const V360Context *s, > + const float *vec, int width, int height, > + uint16_t us[4][4], uint16_t vs[4][4], float > *du, float *dv) > +{ > + const float phi = atan2f(vec[0], -vec[2]); > + const float theta = asinf(-vec[1]); > + float uf, vf; > + int ui, vi; > + int i, j; > + > + uf = (phi / M_PI + 1.f) * width / 2.f; > + vf = (theta / M_PI_2 + 1.f) * height / 2.f; > + ui = floorf(uf); > + vi = floorf(vf); > + > + *du = uf - ui; > + *dv = vf - vi; > + > + for (i = -1; i < 3; i++) { > + for (j = -1; j < 3; j++) { > + us[i + 1][j + 1] = mod(ui + j, width); > + vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1); > + } > + } > +} > + > +/** > + * Prepare data for processing equi-angular cubemap input format. > + * > + * @param ctx filter context > + > + * @return error code > + */ > +static int prepare_eac_in(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + > + s->in_cubemap_face_order[RIGHT] = TOP_RIGHT; > + s->in_cubemap_face_order[LEFT] = TOP_LEFT; > + s->in_cubemap_face_order[UP] = BOTTOM_RIGHT; > + s->in_cubemap_face_order[DOWN] = BOTTOM_LEFT; > + s->in_cubemap_face_order[FRONT] = TOP_MIDDLE; > + s->in_cubemap_face_order[BACK] = BOTTOM_MIDDLE; > + > + s->in_cubemap_face_rotation[TOP_LEFT] = ROT_0; > + s->in_cubemap_face_rotation[TOP_MIDDLE] = ROT_0; > + s->in_cubemap_face_rotation[TOP_RIGHT] = ROT_0; > + s->in_cubemap_face_rotation[BOTTOM_LEFT] = ROT_270; > + s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90; > + s->in_cubemap_face_rotation[BOTTOM_RIGHT] = ROT_270; > + > + return 0; > +} > + > +/** > + * Prepare data for processing equi-angular cubemap output format. > + * > + * @param ctx filter context > + * > + * @return error code > + */ > +static int prepare_eac_out(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + > + s->out_cubemap_direction_order[TOP_LEFT] = LEFT; > + s->out_cubemap_direction_order[TOP_MIDDLE] = FRONT; > + s->out_cubemap_direction_order[TOP_RIGHT] = RIGHT; > + s->out_cubemap_direction_order[BOTTOM_LEFT] = DOWN; > + s->out_cubemap_direction_order[BOTTOM_MIDDLE] = BACK; > + s->out_cubemap_direction_order[BOTTOM_RIGHT] = UP; > + > + s->out_cubemap_face_rotation[TOP_LEFT] = ROT_0; > + s->out_cubemap_face_rotation[TOP_MIDDLE] = ROT_0; > + s->out_cubemap_face_rotation[TOP_RIGHT] = ROT_0; > + s->out_cubemap_face_rotation[BOTTOM_LEFT] = ROT_270; > + s->out_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90; > + s->out_cubemap_face_rotation[BOTTOM_RIGHT] = ROT_270; > + > + return 0; > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding frame position in > equi-angular cubemap format. > + * > + * @param s filter context > + * @param i horizontal position on frame [0, height) > + * @param j vertical position on frame [0, width) > + * @param width frame width > + * @param height frame height > + * @param vec coordinates on sphere > + */ > +static void eac_to_xyz(const V360Context *s, > + int i, int j, int width, int height, > + float *vec) > +{ > + const float pixel_pad = 2; > + const float u_pad = pixel_pad / width; > + const float v_pad = pixel_pad / height; > + > + int u_face, v_face, face; > + > + float l_x, l_y, l_z; > + float norm; > + > + float uf = (float)i / width; > + float vf = (float)j / height; > + > + // EAC has 2-pixel padding on faces except between faces on the same > row > + // Padding pixels seems not to be stretched with tangent as regular > pixels > + // Formulas below approximate original padding as close as I could get > experimentally > + > + // Horizontal padding > + uf = 3.f * (uf - u_pad) / (1.f - 2.f * u_pad); > + if (uf < 0.f) { > + u_face = 0; > + uf -= 0.5f; > + } else if (uf >= 3.f) { > + u_face = 2; > + uf -= 2.5f; > + } else { > + u_face = floorf(uf); > + uf = fmodf(uf, 1.f) - 0.5f; > + } > + > + // Vertical padding > + v_face = floorf(vf * 2.f); > + vf = (vf - v_pad - 0.5f * v_face) / (0.5f - 2.f * v_pad) - 0.5f; > + > + if (uf >= -0.5f && uf < 0.5f) { > + uf = tanf(M_PI_2 * uf); > + } else { > + uf = 2.f * uf; > + } > + if (vf >= -0.5f && vf < 0.5f) { > + vf = tanf(M_PI_2 * vf); > + } else { > + vf = 2.f * vf; > + } > + > + face = u_face + 3 * v_face; > + > + switch (face) { > + case TOP_LEFT: > + l_x = -1.f; > + l_y = -vf; > + l_z = -uf; > + break; > + case TOP_MIDDLE: > + l_x = uf; > + l_y = -vf; > + l_z = -1.f; > + break; > + case TOP_RIGHT: > + l_x = 1.f; > + l_y = -vf; > + l_z = uf; > + break; > + case BOTTOM_LEFT: > + l_x = -vf; > + l_y = -1.f; > + l_z = uf; > + break; > + case BOTTOM_MIDDLE: > + l_x = -vf; > + l_y = uf; > + l_z = 1.f; > + break; > + case BOTTOM_RIGHT: > + l_x = -vf; > + l_y = 1.f; > + l_z = -uf; > + break; > + } > + > + norm = sqrtf(l_x * l_x + l_y * l_y + l_z * l_z); > + vec[0] = l_x / norm; > + vec[1] = l_y / norm; > + vec[2] = l_z / norm; > +} > + > +/** > + * Calculate frame position in equi-angular cubemap format for > corresponding 3D coordinates on sphere. > + * > + * @param s filter context > + * @param vec coordinates on sphere > + * @param width frame width > + * @param height frame height > + * @param us horizontal coordinates for interpolation window > + * @param vs vertical coordinates for interpolation window > + * @param du horizontal relative coordinate > + * @param dv vertical relative coordinate > + */ > +static void xyz_to_eac(const V360Context *s, > + const float *vec, int width, int height, > + uint16_t us[4][4], uint16_t vs[4][4], float *du, > float *dv) > +{ > + const float pixel_pad = 2; > + const float u_pad = pixel_pad / width; > + const float v_pad = pixel_pad / height; > + > + float uf, vf; > + int ui, vi; > + int i, j; > + int direction, face; > + int u_face, v_face; > + > + xyz_to_cube(s, vec, &uf, &vf, &direction); > + > + face = s->in_cubemap_face_order[direction]; > + u_face = face % 3; > + v_face = face / 3; > + > + uf = M_2_PI * atanf(uf) + 0.5f; > + vf = M_2_PI * atanf(vf) + 0.5f; > + > + // These formulas are inversed from eac_to_xyz ones > + uf = (uf + u_face) * (1.f - 2.f * u_pad) / 3.f + u_pad; > + vf = vf * (0.5f - 2.f * v_pad) + v_pad + 0.5f * v_face; > + > + uf *= width; > + vf *= height; > + > + ui = floorf(uf); > + vi = floorf(vf); > + > + *du = uf - ui; > + *dv = vf - vi; > + > + for (i = -1; i < 3; i++) { > + for (j = -1; j < 3; j++) { > + us[i + 1][j + 1] = av_clip(ui + j, 0, width - 1); > + vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1); > + } > + } > +} > + > +/** > + * Prepare data for processing flat output format. > + * > + * @param ctx filter context > + * > + * @return error code > + */ > +static int prepare_flat_out(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + > + const float h_angle = 0.5f * s->h_fov * M_PI / 180.f; > + const float v_angle = 0.5f * s->v_fov * M_PI / 180.f; > + > + const float sin_phi = sinf(h_angle); > + const float cos_phi = cosf(h_angle); > + const float sin_theta = sinf(v_angle); > + const float cos_theta = cosf(v_angle); > + > + s->flat_range[0] = cos_theta * sin_phi; > + s->flat_range[1] = sin_theta; > + s->flat_range[2] = -cos_theta * cos_phi; > + > + return 0; > +} > + > +/** > + * Calculate 3D coordinates on sphere for corresponding frame position in > flat format. > + * > + * @param s filter context > + * @param i horizontal position on frame [0, height) > + * @param j vertical position on frame [0, width) > + * @param width frame width > + * @param height frame height > + * @param vec coordinates on sphere > + */ > +static void flat_to_xyz(const V360Context *s, > + int i, int j, int width, int height, > + float *vec) > +{ > + const float l_x = s->flat_range[0] * (2.f * i / width - 1.f); > + const float l_y = -s->flat_range[1] * (2.f * j / height - 1.f); > + const float l_z = s->flat_range[2]; > + > + const float norm = sqrtf(l_x * l_x + l_y * l_y + l_z * l_z); > + > + vec[0] = l_x / norm; > + vec[1] = l_y / norm; > + vec[2] = l_z / norm; > +} > + > +/** > + * Calculate rotation matrix for yaw/pitch/roll angles. > + */ > +static inline void calculate_rotation_matrix(float yaw, float pitch, float > roll, > + float rot_mat[3][3]) > +{ > + const float yaw_rad = yaw * M_PI / 180.f; > + const float pitch_rad = pitch * M_PI / 180.f; > + const float roll_rad = roll * M_PI / 180.f; > + > + const float sin_yaw = sinf(-yaw_rad); > + const float cos_yaw = cosf(-yaw_rad); > + const float sin_pitch = sinf(pitch_rad); > + const float cos_pitch = cosf(pitch_rad); > + const float sin_roll = sinf(roll_rad); > + const float cos_roll = cosf(roll_rad); > + > + rot_mat[0][0] = sin_yaw * sin_pitch * sin_roll + cos_yaw * cos_roll; > + rot_mat[0][1] = sin_yaw * sin_pitch * cos_roll - cos_yaw * sin_roll; > + rot_mat[0][2] = sin_yaw * cos_pitch; > + > + rot_mat[1][0] = cos_pitch * sin_roll; > + rot_mat[1][1] = cos_pitch * cos_roll; > + rot_mat[1][2] = -sin_pitch; > + > + rot_mat[2][0] = cos_yaw * sin_pitch * sin_roll - sin_yaw * cos_roll; > + rot_mat[2][1] = cos_yaw * sin_pitch * cos_roll + sin_yaw * sin_roll; > + rot_mat[2][2] = cos_yaw * cos_pitch; > +} > + > +/** > + * Rotate vector with given rotation matrix. > + * > + * @param rot_mat rotation matrix > + * @param vec vector > + */ > +static inline void rotate(const float rot_mat[3][3], > + float *vec) > +{ > + const float x_tmp = vec[0] * rot_mat[0][0] + vec[1] * rot_mat[0][1] + > vec[2] * rot_mat[0][2]; > + const float y_tmp = vec[0] * rot_mat[1][0] + vec[1] * rot_mat[1][1] + > vec[2] * rot_mat[1][2]; > + const float z_tmp = vec[0] * rot_mat[2][0] + vec[1] * rot_mat[2][1] + > vec[2] * rot_mat[2][2]; > + > + vec[0] = x_tmp; > + vec[1] = y_tmp; > + vec[2] = z_tmp; > +} > + > +static inline void set_mirror_modifier(int h_flip, int v_flip, int d_flip, > + float *modifier) > +{ > + modifier[0] = h_flip ? -1.f : 1.f; > + modifier[1] = v_flip ? -1.f : 1.f; > + modifier[2] = d_flip ? -1.f : 1.f; > +} > + > +static inline void mirror(const float *modifier, > + float *vec) > +{ > + vec[0] *= modifier[0]; > + vec[1] *= modifier[1]; > + vec[2] *= modifier[2]; > +} > + > +static int config_output(AVFilterLink *outlink) > +{ > + AVFilterContext *ctx = outlink->src; > + AVFilterLink *inlink = ctx->inputs[0]; > + V360Context *s = ctx->priv; > + const AVPixFmtDescriptor *desc = > av_pix_fmt_desc_get(inlink->format); > + const int depth = desc->comp[0].depth; > + float remap_data_size = 0.f; > + int sizeof_remap; > + int err; > + int p, h, w; > + float hf, wf; > + float mirror_modifier[3]; > + void (*in_transform)(const V360Context *s, > + const float *vec, int width, int height, > + uint16_t us[4][4], uint16_t vs[4][4], float > *du, float *dv); > + void (*out_transform)(const V360Context *s, > + int i, int j, int width, int height, > + float *vec); > + void (*calculate_kernel)(float du, float dv, int shift, const XYRemap4 > *r_tmp, void *r); > + float rot_mat[3][3]; > + > + switch (s->interp) { > + case NEAREST: > + calculate_kernel = nearest_kernel; > + s->remap_slice = depth <= 8 ? remap1_8bit_slice : > remap1_16bit_slice; > + sizeof_remap = sizeof(XYRemap1); > + break; > + case BILINEAR: > + calculate_kernel = bilinear_kernel; > + s->remap_slice = depth <= 8 ? remap2_8bit_slice : > remap2_16bit_slice; > + sizeof_remap = sizeof(XYRemap2); > + break; > + case BICUBIC: > + calculate_kernel = bicubic_kernel; > + s->remap_slice = depth <= 8 ? remap4_8bit_slice : > remap4_16bit_slice; > + sizeof_remap = sizeof(XYRemap4); > + break; > + case LANCZOS: > + calculate_kernel = lanczos_kernel; > + s->remap_slice = depth <= 8 ? remap4_8bit_slice : > remap4_16bit_slice; > + sizeof_remap = sizeof(XYRemap4); > + break; > + } > + > + switch (s->in) { > + case EQUIRECTANGULAR: > + in_transform = xyz_to_equirect; > + err = 0; > + wf = inlink->w; > + hf = inlink->h; > + break; > + case CUBEMAP_3_2: > + in_transform = xyz_to_cube3x2; > + err = prepare_cube_in(ctx); > + wf = inlink->w / 3.f * 4.f; > + hf = inlink->h; > + break; > + case CUBEMAP_6_1: > + in_transform = xyz_to_cube6x1; > + err = prepare_cube_in(ctx); > + wf = inlink->w / 3.f * 2.f; > + hf = inlink->h * 2.f; > + break; > + case EQUIANGULAR: > + in_transform = xyz_to_eac; > + err = prepare_eac_in(ctx); > + wf = inlink->w; > + hf = inlink->h / 9.f * 8.f; > + break; > + case FLAT: > + av_log(ctx, AV_LOG_ERROR, "Flat format is not accepted as > input.\n"); > + return AVERROR(EINVAL); > + } > + > + if (err != 0) { > + return err; > + } > + > + switch (s->out) { > + case EQUIRECTANGULAR: > + out_transform = equirect_to_xyz; > + err = 0; > + w = roundf(wf); > + h = roundf(hf); > + break; > + case CUBEMAP_3_2: > + out_transform = cube3x2_to_xyz; > + err = prepare_cube_out(ctx); > + w = roundf(wf / 4.f * 3.f); > + h = roundf(hf); > + break; > + case CUBEMAP_6_1: > + out_transform = cube6x1_to_xyz; > + err = prepare_cube_out(ctx); > + w = roundf(wf / 2.f * 3.f); > + h = roundf(hf / 2.f); > + break; > + case EQUIANGULAR: > + out_transform = eac_to_xyz; > + err = prepare_eac_out(ctx); > + w = roundf(wf); > + h = roundf(hf / 8.f * 9.f); > + break; > + case FLAT: > + out_transform = flat_to_xyz; > + err = prepare_flat_out(ctx); > + w = roundf(wf * s->flat_range[0] / s->flat_range[1] / 2.f); > + h = roundf(hf); > + break; > + } > + > + if (err != 0) { > + return err; > + } > + > + if (s->width > 0 && s->height > 0) { > + w = s->width; > + h = s->height; > + } If s->width/height are checked, should handle the case of no ture, Else w/h may be used but not initialized. > + s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(h, > desc->log2_chroma_h); > + s->planeheight[0] = s->planeheight[3] = h; > + s->planewidth[1] = s->planewidth[2] = FF_CEIL_RSHIFT(w, > desc->log2_chroma_w); > + s->planewidth[0] = s->planewidth[3] = w; > + > + outlink->h = h; > + outlink->w = w; > + > + s->inplaneheight[1] = s->inplaneheight[2] = FF_CEIL_RSHIFT(inlink->h, > desc->log2_chroma_h); > + s->inplaneheight[0] = s->inplaneheight[3] = inlink->h; > + s->inplanewidth[1] = s->inplanewidth[2] = > FF_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); > + s->inplanewidth[0] = s->inplanewidth[3] = inlink->w; > + s->nb_planes = av_pix_fmt_count_planes(inlink->format); > + > + for (p = 0; p < s->nb_planes; p++) { > + remap_data_size += (float)s->planewidth[p] * s->planeheight[p] * > sizeof_remap; > + } > + > + for (p = 0; p < s->nb_planes; p++) { > + s->remap[p] = av_calloc(s->planewidth[p] * s->planeheight[p], > sizeof_remap); > + if (!s->remap[p]) { > + av_log(ctx, AV_LOG_ERROR, > + "Not enough memory to allocate remap data. Need > at least %.3f GiB.\n", > + remap_data_size / (1024 * 1024 * 1024)); > + return AVERROR(ENOMEM); > + } > + } > + > + calculate_rotation_matrix(s->yaw, s->pitch, s->roll, rot_mat); > + set_mirror_modifier(s->h_flip, s->v_flip, s->d_flip, mirror_modifier); > + > + // Calculate remap data > + for (p = 0; p < s->nb_planes; p++) { > + const int width = s->planewidth[p]; > + const int height = s->planeheight[p]; > + const int in_width = s->inplanewidth[p]; > + const int in_height = s->inplaneheight[p]; > + void *r = s->remap[p]; > + float du, dv; > + float vec[3]; > + XYRemap4 r_tmp; > + int i, j; > + > + for (i = 0; i < width; i++) { > + for (j = 0; j < height; j++) { > + out_transform(s, i, j, width, height, vec); > + rotate(rot_mat, vec); > + mirror(mirror_modifier, vec); > + in_transform(s, vec, in_width, in_height, r_tmp.u, > r_tmp.v, &du, &dv); > + calculate_kernel(du, dv, j * width + i, &r_tmp, r); > + } > + } > + } > + > + return 0; > +} > + > +static int filter_frame(AVFilterLink *inlink, AVFrame *in) > +{ > + AVFilterContext *ctx = inlink->dst; > + AVFilterLink *outlink = ctx->outputs[0]; > + V360Context *s = ctx->priv; > + AVFrame *out; > + ThreadData td; > + > + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); > + if (!out) { > + av_frame_free(&in); > + return AVERROR(ENOMEM); > + } > + av_frame_copy_props(out, in); > + > + td.s = s; > + td.in = in; > + td.out = out; > + td.nb_planes = s->nb_planes; > + > + ctx->internal->execute(ctx, s->remap_slice, &td, NULL, > FFMIN(outlink->h, ff_filter_get_nb_threads(ctx))); > + > + av_frame_free(&in); > + return ff_filter_frame(outlink, out); > +} > + > +static av_cold void uninit(AVFilterContext *ctx) > +{ > + V360Context *s = ctx->priv; > + int p; > + > + for (p = 0; p < s->nb_planes; p++) > + av_freep(&s->remap[p]); > +} > + > +static const AVFilterPad inputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .filter_frame = filter_frame, > + }, > + { NULL } > +}; > + > +static const AVFilterPad outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .config_props = config_output, > + }, > + { NULL } > +}; > + > +AVFilter ff_vf_v360 = { > + .name = "v360", > + .description = NULL_IF_CONFIG_SMALL("Convert 360 projection of > video."), > + .priv_size = sizeof(V360Context), > + .uninit = uninit, > + .query_formats = query_formats, > + .inputs = inputs, > + .outputs = outputs, > + .priv_class = &v360_class, > + .flags = AVFILTER_FLAG_SLICE_THREADS, > +}; > -- > 2.22.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".