/* * 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 */ #include "libavutil/avassert.h" #include "libavutil/eval.h" #include "libavutil/opt.h" #include "avfilter.h" #include "internal.h" enum { X, Y, W, H, NB_PARAMS, }; static const char *addroi_param_names[] = { "x", "y", "w", "h", }; enum { VAR_IW, VAR_IH, NB_VARS, }; static const char *const addroi_var_names[] = { "iw", "ih", }; typedef struct AddROIContext { const AVClass *class; char *region_str[NB_PARAMS]; AVExpr *region_expr[NB_PARAMS]; int region[NB_PARAMS]; AVRational qoffset; int clear; } AddROIContext; static int addroi_config_input(AVFilterLink *inlink) { AVFilterContext *avctx = inlink->dst; AddROIContext *ctx = avctx->priv; int i; double vars[NB_VARS]; double val; vars[VAR_IW] = inlink->w; vars[VAR_IH] = inlink->h; for (i = 0; i < NB_PARAMS; i++) { int max_value; switch (i) { case X: max_value = inlink->w; break; case Y: max_value = inlink->h; break; case W: max_value = inlink->w - ctx->region[X]; break; case H: max_value = inlink->h - ctx->region[Y]; break; } val = av_expr_eval(ctx->region_expr[i], vars, NULL); if (val < 0.0) { av_log(avctx, AV_LOG_WARNING, "Calculated value %g for %s is " "less than zero - using zero instead.\n", val, addroi_param_names[i]); val = 0.0; } else if (val > max_value) { av_log(avctx, AV_LOG_WARNING, "Calculated value %g for %s is " "greater than maximum allowed value %d - " "using %d instead.\n", val, addroi_param_names[i], max_value, max_value); val = max_value; } ctx->region[i] = val; } return 0; } static int addroi_filter_frame(AVFilterLink *inlink, AVFrame *frame) { AVFilterContext *avctx = inlink->dst; AVFilterLink *outlink = avctx->outputs[0]; AddROIContext *ctx = avctx->priv; AVRegionOfInterest *roi; AVFrameSideData *sd; int err; if (ctx->clear) { av_frame_remove_side_data(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST); sd = NULL; } else { sd = av_frame_get_side_data(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST); } if (sd) { const AVRegionOfInterest *old_roi; uint32_t old_roi_size; AVBufferRef *roi_ref; int nb_roi, i; old_roi = (const AVRegionOfInterest*)sd->data; old_roi_size = old_roi->self_size; av_assert0(old_roi_size && sd->size % old_roi_size == 0); nb_roi = sd->size / old_roi_size + 1; roi_ref = av_buffer_alloc(sizeof(*roi) * nb_roi); if (!roi_ref) { err = AVERROR(ENOMEM); goto fail; } roi = (AVRegionOfInterest*)roi_ref->data; for (i = 0; i < nb_roi - 1; i++) { old_roi = (const AVRegionOfInterest*) (sd->data + old_roi_size * i); roi[i] = (AVRegionOfInterest) { .self_size = sizeof(*roi), .top = old_roi->top, .bottom = old_roi->bottom, .left = old_roi->left, .right = old_roi->right, .qoffset = old_roi->qoffset, }; } roi[nb_roi - 1] = (AVRegionOfInterest) { .self_size = sizeof(*roi), .top = ctx->region[Y], .bottom = ctx->region[Y] + ctx->region[H], .left = ctx->region[X], .right = ctx->region[X] + ctx->region[W], .qoffset = ctx->qoffset, }; av_frame_remove_side_data(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST); sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST, roi_ref); if (!sd) { av_buffer_unref(&roi_ref); err = AVERROR(ENOMEM); goto fail; } } else { sd = av_frame_new_side_data(frame, AV_FRAME_DATA_REGIONS_OF_INTEREST, sizeof(AVRegionOfInterest)); if (!sd) { err = AVERROR(ENOMEM); goto fail; } roi = (AVRegionOfInterest*)sd->data; *roi = (AVRegionOfInterest) { .self_size = sizeof(*roi), .top = ctx->region[Y], .bottom = ctx->region[Y] + ctx->region[H], .left = ctx->region[X], .right = ctx->region[X] + ctx->region[W], .qoffset = ctx->qoffset, }; } return ff_filter_frame(outlink, frame); fail: av_frame_free(&frame); return err; } static av_cold int addroi_init(AVFilterContext *avctx) { AddROIContext *ctx = avctx->priv; int i, err; for (i = 0; i < NB_PARAMS; i++) { err = av_expr_parse(&ctx->region_expr[i], ctx->region_str[i], addroi_var_names, NULL, NULL, NULL, NULL, 0, avctx); if (err < 0) { av_log(ctx, AV_LOG_ERROR, "Error parsing %s expression '%s'.\n", addroi_param_names[i], ctx->region_str[i]); return err; } } return 0; } static av_cold void addroi_uninit(AVFilterContext *avctx) { AddROIContext *ctx = avctx->priv; int i; for (i = 0; i < NB_PARAMS; i++) { av_expr_free(ctx->region_expr[i]); ctx->region_expr[i] = NULL; } } #define OFFSET(x) offsetof(AddROIContext, x) #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM static const AVOption addroi_options[] = { { "x", "Region distance from left edge of frame.", OFFSET(region_str[X]), AV_OPT_TYPE_STRING, { .str = "0" }, .flags = FLAGS }, { "y", "Region distance from top edge of frame.", OFFSET(region_str[Y]), AV_OPT_TYPE_STRING, { .str = "0" }, .flags = FLAGS }, { "w", "Region width.", OFFSET(region_str[W]), AV_OPT_TYPE_STRING, { .str = "0" }, .flags = FLAGS }, { "h", "Region height.", OFFSET(region_str[H]), AV_OPT_TYPE_STRING, { .str = "0" }, .flags = FLAGS }, { "qoffset", "Quantisation offset to apply in the region.", OFFSET(qoffset), AV_OPT_TYPE_RATIONAL, { .dbl = -0.1 }, -1, +1, FLAGS }, { "clear", "Remove any existing regions of interest before adding the new one.", OFFSET(clear), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, { NULL } }; AVFILTER_DEFINE_CLASS(addroi); static const AVFilterPad addroi_inputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, .config_props = addroi_config_input, .filter_frame = addroi_filter_frame, }, { NULL } }; static const AVFilterPad addroi_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, }, { NULL } }; AVFilter ff_vf_addroi = { .name = "addroi", .description = NULL_IF_CONFIG_SMALL("Add region of interest to frame."), .init = addroi_init, .uninit = addroi_uninit, .priv_size = sizeof(AddROIContext), .priv_class = &addroi_class, .inputs = addroi_inputs, .outputs = addroi_outputs, };