Transform360/vf_transform360.c (407 lines of code) (raw):
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @file
* transform360 video filter
*/
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/eval.h"
#include "libavutil/imgutils.h"
#include "libavutil/internal.h"
#include "libavutil/opt.h"
#include "libavutil/mem.h"
#include "libavutil/parseutils.h"
#include "avfilter.h"
#include "internal.h"
#include "video.h"
#include <stdio.h>
#include "transform360/VideoFrameTransformHandler.h"
#include "transform360/VideoFrameTransformHelper.h"
static const char *const var_names[] = {
"out_w", "ow",
"out_h", "oh",
NULL
};
enum var_name {
VAR_OUT_W, VAR_OW,
VAR_OUT_H, VAR_OH,
VARS_NB
};
/*
MMMMT
MMMMB
*/
typedef struct TransformContext {
const AVClass *class;
int w, h;
int out_map_planes;
AVDictionary *opts;
char *w_expr; ///< width expression string
char *h_expr; ///< height expression string
char *size_str;
int cube_edge_length;
int max_cube_edge_length;
int max_output_h;
int max_output_w;
int input_layout;
int output_layout;
int input_stereo_format;
int output_stereo_format;
int vflip;
int planes;
float input_expand_coef;
float expand_coef;
int is_horizontal_offset;
float fixed_yaw; ///< Yaw (asimuth) angle, degrees
float fixed_pitch; ///< Pitch (elevation) angle, degrees
float fixed_roll; ///< Roll (tilt) angle, degrees
float fixed_hfov; ///< Horizontal field of view, degrees
float fixed_vfov; ///< Vertical field of view, degrees
float fixed_cube_offcenter_x; // offcenter projection x
float fixed_cube_offcenter_y; // offcenter projection y
float fixed_cube_offcenter_z; // offcenter projection z
// openCV-based transform parameters
VideoFrameTransform* transform;
int interpolation_alg;
float width_scale_factor;
float height_scale_factor;
int enable_low_pass_filter;
float kernel_height_scale_factor;
float min_kernel_half_height;
float max_kernel_half_height;
int enable_multi_threading;
int num_vertical_segments;
int num_horizontal_segments;
int adjust_kernel;
float kernel_adjust_factor;
} TransformContext;
static inline void update_plane_sizes(
AVPixFmtDescriptor* desc,
int* in_w, int* in_h, int* out_w, int* out_h) {
*in_w = FF_CEIL_RSHIFT(*in_w, desc->log2_chroma_w);
*in_h = FF_CEIL_RSHIFT(*in_h, desc->log2_chroma_h);
*out_w = FF_CEIL_RSHIFT(*out_w, desc->log2_chroma_w);
*out_h = FF_CEIL_RSHIFT(*out_h, desc->log2_chroma_h);
}
static inline int generate_map(
TransformContext *s, AVFilterLink *inlink,
AVFilterLink *outlink, AVFrame *in) {
AVFilterContext *ctx = outlink->src;
int ret = 0;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format);
s->planes = av_pix_fmt_count_planes(outlink->format);
s->out_map_planes = 2;
FrameTransformContext frame_transform_ctx = (FrameTransformContext) {
.input_layout = s->input_layout,
.output_layout = s->output_layout,
.input_stereo_format = s->input_stereo_format,
.output_stereo_format = s->output_stereo_format,
.vflip = s->vflip,
.input_expand_coef = s->input_expand_coef,
.expand_coef = s->expand_coef,
.interpolation_alg = s->interpolation_alg,
.width_scale_factor = s->width_scale_factor,
.height_scale_factor = s->height_scale_factor,
.fixed_yaw = s->fixed_yaw,
.fixed_pitch = s->fixed_pitch,
.fixed_roll = s->fixed_roll,
.fixed_hfov = s->fixed_hfov,
.fixed_vfov = s->fixed_vfov,
.fixed_cube_offcenter_x = s->fixed_cube_offcenter_x,
.fixed_cube_offcenter_y = s->fixed_cube_offcenter_y,
.fixed_cube_offcenter_z = s->fixed_cube_offcenter_z,
.is_horizontal_offset = s->is_horizontal_offset,
.enable_low_pass_filter = s->enable_low_pass_filter,
.kernel_height_scale_factor = s->kernel_height_scale_factor,
.min_kernel_half_height = s->min_kernel_half_height,
.max_kernel_half_height = s->max_kernel_half_height,
.enable_multi_threading = s->enable_multi_threading,
.num_vertical_segments = s->num_vertical_segments,
.num_horizontal_segments = s->num_horizontal_segments,
.adjust_kernel = s->adjust_kernel,
.kernel_adjust_factor = s->kernel_adjust_factor};
s->transform = VideoFrameTransform_new(&frame_transform_ctx);
if (!s->transform) {
return AVERROR(ENOMEM);
}
int in_w, in_h, out_w, out_h;
for (int plane = 0; plane < s->out_map_planes; ++plane) {
out_w = outlink->w;
out_h = outlink->h;
in_w = inlink->w;
in_h = inlink->h;
if (plane == 1) {
update_plane_sizes(desc, &in_w, &in_h, &out_w, &out_h);
}
if (!VideoFrameTransform_generateMapForPlane(
s->transform, in_w, in_h, out_w, out_h, plane)) {
av_log(ctx, AV_LOG_INFO, "Failed to generate map for plane %d\n", plane);
return AVERROR(EINVAL);
}
}
return 0;
}
static int config_output(AVFilterLink *outlink) {
AVFilterContext *ctx = outlink->src;
AVFilterLink *inlink = outlink->src->inputs[0];
TransformContext *s = ctx->priv;
double var_values[VARS_NB], res;
char *expr;
int ret;
var_values[VAR_OUT_W] = var_values[VAR_OW] = NAN;
var_values[VAR_OUT_H] = var_values[VAR_OH] = NAN;
if (s->input_stereo_format == STEREO_FORMAT_GUESS) {
int aspect_ratio = inlink->w / inlink->h;
if (aspect_ratio == 1)
s->input_stereo_format = STEREO_FORMAT_TB;
else if (aspect_ratio == 4)
s->input_stereo_format = STEREO_FORMAT_LR;
else
s->input_stereo_format = STEREO_FORMAT_MONO;
}
if (s->output_stereo_format == STEREO_FORMAT_GUESS) {
if (s->input_stereo_format == STEREO_FORMAT_MONO) {
s->output_stereo_format = STEREO_FORMAT_MONO;
} else {
s->output_stereo_format =
(s->output_layout == LAYOUT_CUBEMAP_23_OFFCENTER)
? STEREO_FORMAT_LR
: STEREO_FORMAT_TB;
}
}
if (s->max_cube_edge_length > 0) {
if (s->input_stereo_format == STEREO_FORMAT_LR) {
s->cube_edge_length = inlink->w / 8;
} else {
s->cube_edge_length = inlink->w / 4;
}
// do not exceed the max length supplied
if (s->cube_edge_length > s->max_cube_edge_length) {
s->cube_edge_length = s->max_cube_edge_length;
}
}
// ensure cube edge length is a multiple of 16 by rounding down
// so that macroblocks do not cross cube edge boundaries
s->cube_edge_length = s->cube_edge_length - (s->cube_edge_length % 16);
if (s->cube_edge_length > 0) {
if (s->output_layout == LAYOUT_CUBEMAP_32) {
outlink->w = s->cube_edge_length * 3;
outlink->h = s->cube_edge_length * 2;
} else if (s->output_layout == LAYOUT_CUBEMAP_23_OFFCENTER) {
outlink->w = s->cube_edge_length * 2;
outlink->h = s->cube_edge_length * 3;
}
} else {
var_values[VAR_OUT_W] = var_values[VAR_OW] = NAN;
var_values[VAR_OUT_H] = var_values[VAR_OH] = NAN;
av_expr_parse_and_eval(&res, (expr = s->w_expr),
var_names, var_values,
NULL, NULL, NULL, NULL, NULL, 0, ctx);
s->w = var_values[VAR_OUT_W] = var_values[VAR_OW] = res;
if ((ret = av_expr_parse_and_eval(&res, (expr = s->h_expr),
var_names, var_values,
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
av_log(NULL, AV_LOG_ERROR,
"Error when evaluating the expression '%s'.\n"
"Maybe the expression for out_w:'%s' or for out_h:'%s' is self-referencing.\n",
expr, s->w_expr, s->h_expr);
return ret;
}
s->h = var_values[VAR_OUT_H] = var_values[VAR_OH] = res;
/* evaluate again the width, as it may depend on the output height */
if ((ret = av_expr_parse_and_eval(&res, (expr = s->w_expr),
var_names, var_values,
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
av_log(NULL, AV_LOG_ERROR,
"Error when evaluating the expression '%s'.\n"
"Maybe the expression for out_w:'%s' or for out_h:'%s' is self-referencing.\n",
expr, s->w_expr, s->h_expr);
return ret;
}
s->w = res;
outlink->w = s->w;
outlink->h = s->h;
}
if (s->output_stereo_format == STEREO_FORMAT_TB) {
outlink->h *= 2;
s->h *= 2;
} else if (s->output_stereo_format == STEREO_FORMAT_LR) {
outlink->w *= 2;
s->w *= 2;
}
av_log(ctx, AV_LOG_VERBOSE, "out_w:%d out_h:%d\n",
outlink->w, outlink->h);
return 0;
}
static av_cold int init_dict(AVFilterContext *ctx, AVDictionary **opts) {
TransformContext *s = ctx->priv;
if (s->size_str && (s->w_expr || s->h_expr)) {
av_log(ctx, AV_LOG_ERROR,
"Size and width/height expressions cannot be set at the same time.\n");
return AVERROR(EINVAL);
}
if (s->w_expr && !s->h_expr)
FFSWAP(char *, s->w_expr, s->size_str);
av_log(ctx, AV_LOG_VERBOSE, "w:%s h:%s\n",
s->w_expr, s->h_expr);
s->opts = *opts;
*opts = NULL;
return 0;
}
static av_cold void uninit(AVFilterContext *ctx) {
TransformContext *s = ctx->priv;
av_dict_free(&s->opts);
s->opts = NULL;
VideoFrameTransform_delete(s->transform);
s->transform = NULL;
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in) {
AVFilterContext *ctx = inlink->dst;
TransformContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
AVFrame *out;
av_log(ctx, AV_LOG_VERBOSE, "Frame\n");
// map not yet set
if (s->out_map_planes != 2) {
int result = generate_map(s, inlink, outlink, in);
if (result != 0) {
av_frame_free(&in);
return result;
}
}
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
av_log(ctx, AV_LOG_VERBOSE, "Got Frame %dx%d\n", outlink->w, outlink->h);
if (!out) {
av_frame_free(&in);
return AVERROR(ENOMEM);
}
av_frame_copy_props(out, in);
av_log(ctx, AV_LOG_VERBOSE, "Copied props \n");
uint8_t *in_data, *out_data;
int out_map_plane;
int in_w, in_h, out_w, out_h;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format);
for (int plane = 0; plane < s->planes; ++plane) {
in_data = in->data[plane];
av_assert1(in_data);
out_data = out->data[plane];
out_map_plane = (plane == 1 || plane == 2) ? 1 : 0;
out_w = outlink->w;
out_h = outlink->h;
in_w = inlink->w;
in_h = inlink->h;
if (plane >= 1) {
update_plane_sizes(desc, &in_w, &in_h, &out_w, &out_h);
}
if (!VideoFrameTransform_transformFramePlane(
s->transform,
in_data,
out_data,
in_w,
in_h,
in->linesize[plane],
out_w,
out_h,
out->linesize[plane],
out_map_plane,
plane)) {
return AVERROR(EINVAL);
}
}
av_frame_free(&in);
av_log(ctx, AV_LOG_VERBOSE, "Done freeing in \n");
return ff_filter_frame(outlink, out);
}
#define OFFSET(x) offsetof(TransformContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption transform360_options[] = {
{ "w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = FLAGS },
{ "width", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = FLAGS },
{ "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, .flags = FLAGS },
{ "height", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, .flags = FLAGS },
{ "size", "set video size", OFFSET(size_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, FLAGS },
{ "s", "set video size", OFFSET(size_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, FLAGS },
{ "is_horizontal_offset", "Whether to use offset on the horizontal plane only. It only affects yaw.", OFFSET(is_horizontal_offset), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = FLAGS },
{ "cube_edge_length", "Length of a cube edge (for cubic transform, overrides w and h, default 0 for off)", OFFSET(cube_edge_length), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 16384, .flags = FLAGS },
{ "max_cube_edge_length", "Max length of a cube edge (for cubic transform, overrides w, h, and cube_edge_length, default 0 for off)", OFFSET(max_cube_edge_length), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 16384, .flags = FLAGS },
{ "max_output_h", "Max height of the output video (for pyramid/cone transform, overrides pyramid_height, default 0 for off)", OFFSET(max_output_h), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 16384, .flags = FLAGS },
{ "max_output_w", "Max width of the output video (for pyramid/cone transform, overrides pyramid_height, default 0 for off)", OFFSET(max_output_w), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 16384, .flags = FLAGS },
{ "input_stereo_format", "Input video stereo format", OFFSET(input_stereo_format), AV_OPT_TYPE_INT, {.i64 = STEREO_FORMAT_GUESS }, 0, STEREO_FORMAT_N - 1, .flags = FLAGS, "stereo_format" },
{ "output_stereo_format", "Output video stereo format", OFFSET(output_stereo_format), AV_OPT_TYPE_INT, {.i64 = STEREO_FORMAT_GUESS }, 0, STEREO_FORMAT_N - 1, .flags = FLAGS, "stereo_format" },
{ "TB", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_TB }, 0, 0, FLAGS, "stereo_format" },
{ "LR", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_LR }, 0, 0, FLAGS, "stereo_format" },
{ "MONO", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_MONO }, 0, 0, FLAGS, "stereo_format" },
{ "GUESS", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_GUESS }, 0, 0, FLAGS, "stereo_format" },
{ "tb", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_TB }, 0, 0, FLAGS, "stereo_format" },
{ "lr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_LR }, 0, 0, FLAGS, "stereo_format" },
{ "mono", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_MONO }, 0, 0, FLAGS, "stereo_format" },
{ "guess", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = STEREO_FORMAT_GUESS }, 0, 0, FLAGS, "stereo_format" },
{ "input_layout", "Input video layout format", OFFSET(input_layout), AV_OPT_TYPE_INT, {.i64 = LAYOUT_EQUIRECT }, 0, LAYOUT_N - 1, .flags = FLAGS, "layout" },
{ "output_layout", "Output video layout format", OFFSET(output_layout), AV_OPT_TYPE_INT, {.i64 = LAYOUT_CUBEMAP_32 }, 0, LAYOUT_N - 1, .flags = FLAGS, "layout" },
{ "CUBEMAP_32", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_CUBEMAP_32 }, 0, 0, FLAGS, "layout" },
{ "CUBEMAP_23_OFFCENTER",NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_CUBEMAP_23_OFFCENTER },0, 0, FLAGS, "layout" },
{ "EQUIRECT",NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_EQUIRECT },0, 0, FLAGS, "layout" },
{ "BARREL", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_BARREL }, 0, 0, FLAGS, "layout" },
{ "BARREL_SPLIT", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_BARREL_SPLIT }, 0, 0, FLAGS, "layout" },
{ "EAC_32", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_EAC_32 }, 0, 0, FLAGS, "layout" },
{ "FLAT_FIXED", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_FLAT_FIXED }, 0, 0, FLAGS, "layout" },
{ "cubemap_32", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_CUBEMAP_32 }, 0, 0, FLAGS, "layout" },
{ "cubemap_23_offcenter",NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_CUBEMAP_23_OFFCENTER },0, 0, FLAGS, "layout" },
{ "equirect",NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_EQUIRECT },0, 0, FLAGS, "layout" },
{ "flat_fixed", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_FLAT_FIXED }, 0, 0, FLAGS, "layout" },
{ "barrel", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_BARREL }, 0, 0, FLAGS, "layout" },
{ "barrel_split", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_BARREL_SPLIT }, 0, 0, FLAGS, "layout" },
{ "eac_32", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LAYOUT_EAC_32 }, 0, 0, FLAGS, "layout" },
{ "vflip", "Output video 2nd eye vertical flip (true, false)", OFFSET(vflip), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, .flags = FLAGS, "vflip" },
{ "false", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, FLAGS, "vflip" },
{ "true", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, FLAGS, "vflip" },
{ "input_expand_coef", "Expansion coeffiecient of the input", OFFSET(input_expand_coef), AV_OPT_TYPE_FLOAT, {.dbl=1.01f}, 0, 10, .flags = FLAGS },
{ "expand_coef", "Expansion coeffiecient for each face in cubemap (default 1.01)", OFFSET(expand_coef), AV_OPT_TYPE_FLOAT, {.dbl=1.01f}, 0, 10, .flags = FLAGS },
{ "yaw", "View orientation for flat_fixed projection, degrees", OFFSET(fixed_yaw), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -360, 360, .flags = FLAGS },
{ "pitch", "View orientation for flat_fixed projection, degrees", OFFSET(fixed_pitch), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -180, 180, .flags = FLAGS },
{ "roll", "View orientation for flat_fixed projection, degrees", OFFSET(fixed_roll), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -180, 180, .flags = FLAGS },
{ "hfov", "Horizontal field of view for flat_fixed projection, degrees (default 120)", OFFSET(fixed_hfov), AV_OPT_TYPE_FLOAT, {.dbl = 120.0}, -360, 360, .flags = FLAGS },
{ "vfov", "Vertical field of view for flat_fixed projection, degrees (default 110)", OFFSET(fixed_vfov), AV_OPT_TYPE_FLOAT, {.dbl = 110.0}, -180, 180, .flags = FLAGS },
{ "cube_offcenter_x", "Offcenter cube displacement x", OFFSET(fixed_cube_offcenter_x), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1, 1, .flags = FLAGS },
{ "cube_offcenter_y", "Offcenter cube displacement y", OFFSET(fixed_cube_offcenter_y), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1, 1, .flags = FLAGS },
{ "cube_offcenter_z", "Offcenter cube displacement z", OFFSET(fixed_cube_offcenter_z), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1, 1, .flags = FLAGS },
{ "NEAREST", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = NEAREST }, 0, 0, FLAGS, "interpolation_alg" },
{ "LINEAR", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LINEAR }, 0, 0, FLAGS, "interpolation_alg" },
{ "CUBIC", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = CUBIC }, 0, 0, FLAGS, "interpolation_alg" },
{ "LANCZOS4", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LANCZOS4 }, 0, 0, FLAGS, "interpolation_alg" },
{ "nearest", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = NEAREST }, 0, 0, FLAGS, "interpolation_alg" },
{ "linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LINEAR }, 0, 0, FLAGS, "interpolation_alg" },
{ "cubic", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = CUBIC }, 0, 0, FLAGS, "interpolation_alg" },
{ "lanczos4", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = LANCZOS4 }, 0, 0, FLAGS, "interpolation_alg" },
{ "interpolation_alg", "Interpolation algorithm", OFFSET(interpolation_alg), AV_OPT_TYPE_INT, {.i64 = 2}, 0, 4, .flags = FLAGS, "interpolation_alg"},
{ "width_scale_factor", "Scale factor of width for antialiasing", OFFSET(width_scale_factor), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0, 10, .flags = FLAGS, "width_scale_factor" },
{ "height_scale_factor", "Scale factor of height for antialiasing", OFFSET(height_scale_factor), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0, 10, .flags = FLAGS, "height_scale_factor" },
{ "enable_low_pass_filter", "Enable low pass filter-based antialiasing", OFFSET(enable_low_pass_filter), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, .flags = FLAGS, "enable_low_pass_filter" },
{ "enable_multi_threading", "Enable multi-threading to speed up low pass filter-based antialiasing", OFFSET(enable_multi_threading), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, .flags = FLAGS, "enable_multi_threading" },
{ "num_vertical_segments" , "Number of vertical segments per frame plane", OFFSET(num_vertical_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 2, 500, .flags = FLAGS, "num_vertical_segments" },
{ "num_horizontal_segments" , "Number of horizontal segments per frame plane", OFFSET(num_horizontal_segments), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 500, .flags = FLAGS, "num_horizontal_segments" },
{ "kernel_height_scale_factor", "Factor to scale the calculated kernel height for low pass filtering", OFFSET(kernel_height_scale_factor), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.1, 100.0, .flags = FLAGS, "kernel_height_scale_factor" },
{ "min_kernel_half_height", "Half of the mininum kernel height", OFFSET(min_kernel_half_height), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.5, 200, .flags = FLAGS, "min_kernel_half_height" },
{ "max_kernel_half_height", "The maximum value of the kernel height", OFFSET(max_kernel_half_height), AV_OPT_TYPE_FLOAT, {.dbl = 10000.0}, 0.5, 100000, .flags = FLAGS, "max_kernel_half_height" },
{ "adjust_kernel", "Enable adjustment of kernel", OFFSET(adjust_kernel), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, .flags = FLAGS, "adjust_kernel" },
{ "kernel_adjust_factor", "Factor to further adjust the kernel size", OFFSET(kernel_adjust_factor), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.1, 100.0, .flags = FLAGS, "kernel_adjust_factor" },
{ NULL }
};
static const AVClass transform360_class = {
.class_name = "transform360",
.item_name = av_default_item_name,
.option = transform360_options,
.version = LIBAVUTIL_VERSION_INT,
.category = AV_CLASS_CATEGORY_FILTER,
};
static const AVFilterPad avfilter_vf_transform_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = filter_frame,
},
{ NULL }
};
static const AVFilterPad avfilter_vf_transform_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_output,
},
{ NULL }
};
AVFilter ff_vf_transform360 = {
.name = "transform360",
.description = NULL_IF_CONFIG_SMALL("Transforms equirectangular input video to the other format."),
.init_dict = init_dict,
.uninit = uninit,
.priv_size = sizeof(TransformContext),
.priv_class = &transform360_class,
.inputs = avfilter_vf_transform_inputs,
.outputs = avfilter_vf_transform_outputs,
};