| @@ -0,0 +1,349 @@ |
| | 1 | /* |
| | 2 | * Copyright (c) 2016 Paul B Mahol |
| | 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 | /** |
| | 22 | * @file |
| | 23 | * threshold video filter |
| | 24 | */ |
| | 25 | |
| | 26 | #include "libavutil/imgutils.h" |
| | 27 | #include "libavutil/internal.h" |
| | 28 | #include "libavutil/opt.h" |
| | 29 | #include "libavutil/pixdesc.h" |
| | 30 | #include "avfilter.h" |
| | 31 | #include "framesync.h" |
| | 32 | #include "internal.h" |
| | 33 | #include "video.h" |
| | 34 | |
| | 35 | typedef struct ThresholdContext { |
| | 36 | const AVClass *class; |
| | 37 | |
| | 38 | int planes; |
| | 39 | int bpc; |
| | 40 | |
| | 41 | int nb_planes; |
| | 42 | int width[4], height[4]; |
| | 43 | |
| | 44 | void (*threshold)(const uint8_t *in, const uint8_t *threshold, |
| | 45 | const uint8_t *min, const uint8_t *max, |
| | 46 | uint8_t *out, |
| | 47 | ptrdiff_t ilinesize, ptrdiff_t tlinesize, |
| | 48 | ptrdiff_t flinesize, ptrdiff_t slinesize, |
| | 49 | ptrdiff_t olinesize, |
| | 50 | int w, int h); |
| | 51 | |
| | 52 | AVFrame *frames[4]; |
| | 53 | FFFrameSync fs; |
| | 54 | } ThresholdContext; |
| | 55 | |
| | 56 | #define OFFSET(x) offsetof(ThresholdContext, x) |
| | 57 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
| | 58 | |
| | 59 | static const AVOption threshold_options[] = { |
| | 60 | { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=15}, 0, 15, FLAGS}, |
| | 61 | { NULL } |
| | 62 | }; |
| | 63 | |
| | 64 | AVFILTER_DEFINE_CLASS(threshold); |
| | 65 | |
| | 66 | static int query_formats(AVFilterContext *ctx) |
| | 67 | { |
| | 68 | static const enum AVPixelFormat pix_fmts[] = { |
| | 69 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, |
| | 70 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, |
| | 71 | AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, |
| | 72 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, |
| | 73 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, |
| | 74 | AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, |
| | 75 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, |
| | 76 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, |
| | 77 | AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, |
| | 78 | AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, |
| | 79 | AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, |
| | 80 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, |
| | 81 | AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, |
| | 82 | AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP16, |
| | 83 | AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY10, |
| | 84 | AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY16, |
| | 85 | AV_PIX_FMT_NONE |
| | 86 | }; |
| | 87 | |
| | 88 | return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
| | 89 | } |
| | 90 | |
| | 91 | static int process_frame(FFFrameSync *fs) |
| | 92 | { |
| | 93 | AVFilterContext *ctx = fs->parent; |
| | 94 | ThresholdContext *s = fs->opaque; |
| | 95 | AVFilterLink *outlink = ctx->outputs[0]; |
| | 96 | AVFrame *out, *in, *threshold, *min, *max; |
| | 97 | int ret; |
| | 98 | |
| | 99 | if ((ret = ff_framesync_get_frame(&s->fs, 0, &in, 0)) < 0 || |
| | 100 | (ret = ff_framesync_get_frame(&s->fs, 1, &threshold, 0)) < 0 || |
| | 101 | (ret = ff_framesync_get_frame(&s->fs, 2, &min, 0)) < 0 || |
| | 102 | (ret = ff_framesync_get_frame(&s->fs, 3, &max, 0)) < 0) |
| | 103 | return ret; |
| | 104 | |
| | 105 | if (ctx->is_disabled) { |
| | 106 | out = av_frame_clone(in); |
| | 107 | if (!out) |
| | 108 | return AVERROR(ENOMEM); |
| | 109 | } else { |
| | 110 | int p; |
| | 111 | |
| | 112 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| | 113 | if (!out) |
| | 114 | return AVERROR(ENOMEM); |
| | 115 | av_frame_copy_props(out, in); |
| | 116 | |
| | 117 | for (p = 0; p < s->nb_planes; p++) { |
| | 118 | if (!(s->planes & (1 << p))) { |
| | 119 | av_image_copy_plane(out->data[p], out->linesize[p], |
| | 120 | in->data[p], in->linesize[p], |
| | 121 | s->width[p] * s->bpc, |
| | 122 | s->height[p]); |
| | 123 | continue; |
| | 124 | } |
| | 125 | s->threshold(in->data[p], threshold->data[p], |
| | 126 | min->data[p], max->data[p], |
| | 127 | out->data[p], |
| | 128 | in->linesize[p], threshold->linesize[p], |
| | 129 | min->linesize[p], max->linesize[p], |
| | 130 | out->linesize[p], |
| | 131 | s->width[p], s->height[p]); |
| | 132 | } |
| | 133 | } |
| | 134 | |
| | 135 | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); |
| | 136 | |
| | 137 | return ff_filter_frame(outlink, out); |
| | 138 | } |
| | 139 | |
| | 140 | static void threshold8(const uint8_t *in, const uint8_t *threshold, |
| | 141 | const uint8_t *min, const uint8_t *max, |
| | 142 | uint8_t *out, |
| | 143 | ptrdiff_t ilinesize, ptrdiff_t tlinesize, |
| | 144 | ptrdiff_t flinesize, ptrdiff_t slinesize, |
| | 145 | ptrdiff_t olinesize, |
| | 146 | int w, int h) |
| | 147 | { |
| | 148 | int x, y; |
| | 149 | |
| | 150 | for (y = 0; y < h; y++) { |
| | 151 | for (x = 0; x < w; x++) { |
| | 152 | out[x] = in[x] < threshold[x] ? min[x] : max[x]; |
| | 153 | } |
| | 154 | |
| | 155 | in += ilinesize; |
| | 156 | threshold += tlinesize; |
| | 157 | min += flinesize; |
| | 158 | max += flinesize; |
| | 159 | out += olinesize; |
| | 160 | } |
| | 161 | } |
| | 162 | |
| | 163 | static void threshold16(const uint8_t *iin, const uint8_t *tthreshold, |
| | 164 | const uint8_t *ffirst, const uint8_t *ssecond, |
| | 165 | uint8_t *oout, |
| | 166 | ptrdiff_t ilinesize, ptrdiff_t tlinesize, |
| | 167 | ptrdiff_t flinesize, ptrdiff_t slinesize, |
| | 168 | ptrdiff_t olinesize, |
| | 169 | int w, int h) |
| | 170 | { |
| | 171 | const uint16_t *in = (const uint16_t *)iin; |
| | 172 | const uint16_t *threshold = (const uint16_t *)tthreshold; |
| | 173 | const uint16_t *min = (const uint16_t *)ffirst; |
| | 174 | const uint16_t *max = (const uint16_t *)ssecond; |
| | 175 | uint16_t *out = (uint16_t *)oout; |
| | 176 | int x, y; |
| | 177 | |
| | 178 | for (y = 0; y < h; y++) { |
| | 179 | for (x = 0; x < w; x++) { |
| | 180 | out[x] = in[x] < threshold[x] ? min[x] : max[x]; |
| | 181 | } |
| | 182 | |
| | 183 | in += ilinesize / 2; |
| | 184 | threshold += tlinesize / 2; |
| | 185 | min += flinesize / 2; |
| | 186 | max += flinesize / 2; |
| | 187 | out += olinesize / 2; |
| | 188 | } |
| | 189 | } |
| | 190 | |
| | 191 | static int config_input(AVFilterLink *inlink) |
| | 192 | { |
| | 193 | AVFilterContext *ctx = inlink->dst; |
| | 194 | ThresholdContext *s = ctx->priv; |
| | 195 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| | 196 | int vsub, hsub; |
| | 197 | |
| | 198 | s->nb_planes = av_pix_fmt_count_planes(inlink->format); |
| | 199 | |
| | 200 | hsub = desc->log2_chroma_w; |
| | 201 | vsub = desc->log2_chroma_h; |
| | 202 | s->height[1] = s->height[2] = AV_CEIL_RSHIFT(inlink->h, vsub); |
| | 203 | s->height[0] = s->height[3] = inlink->h; |
| | 204 | s->width[1] = s->width[2] = AV_CEIL_RSHIFT(inlink->w, hsub); |
| | 205 | s->width[0] = s->width[3] = inlink->w; |
| | 206 | |
| | 207 | if (desc->comp[0].depth == 8) { |
| | 208 | s->threshold = threshold8; |
| | 209 | s->bpc = 1; |
| | 210 | } else { |
| | 211 | s->threshold = threshold16; |
| | 212 | s->bpc = 2; |
| | 213 | } |
| | 214 | |
| | 215 | return 0; |
| | 216 | } |
| | 217 | |
| | 218 | static int config_output(AVFilterLink *outlink) |
| | 219 | { |
| | 220 | AVFilterContext *ctx = outlink->src; |
| | 221 | ThresholdContext *s = ctx->priv; |
| | 222 | AVFilterLink *base = ctx->inputs[0]; |
| | 223 | AVFilterLink *threshold = ctx->inputs[1]; |
| | 224 | AVFilterLink *min = ctx->inputs[2]; |
| | 225 | AVFilterLink *max = ctx->inputs[3]; |
| | 226 | FFFrameSyncIn *in; |
| | 227 | int ret; |
| | 228 | |
| | 229 | if (base->format != threshold->format || |
| | 230 | base->format != min->format || |
| | 231 | base->format != max->format) { |
| | 232 | av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n"); |
| | 233 | return AVERROR(EINVAL); |
| | 234 | } |
| | 235 | if (base->w != threshold->w || |
| | 236 | base->h != threshold->h || |
| | 237 | base->w != min->w || |
| | 238 | base->h != min->h || |
| | 239 | base->w != max->w || |
| | 240 | base->h != max->h) { |
| | 241 | av_log(ctx, AV_LOG_ERROR, "First input link %s parameters " |
| | 242 | "(size %dx%d) do not match the corresponding " |
| | 243 | "second input link %s parameters (%dx%d) " |
| | 244 | "and/or third input link %s parameters (%dx%d) " |
| | 245 | "and/or fourth input link %s parameters (%dx%d)\n", |
| | 246 | ctx->input_pads[0].name, base->w, base->h, |
| | 247 | ctx->input_pads[1].name, threshold->w, threshold->h, |
| | 248 | ctx->input_pads[2].name, min->w, min->h, |
| | 249 | ctx->input_pads[3].name, max->w, max->h); |
| | 250 | return AVERROR(EINVAL); |
| | 251 | } |
| | 252 | |
| | 253 | outlink->w = base->w; |
| | 254 | outlink->h = base->h; |
| | 255 | outlink->time_base = base->time_base; |
| | 256 | outlink->sample_aspect_ratio = base->sample_aspect_ratio; |
| | 257 | outlink->frame_rate = base->frame_rate; |
| | 258 | |
| | 259 | if ((ret = ff_framesync_init(&s->fs, ctx, 4)) < 0) |
| | 260 | return ret; |
| | 261 | |
| | 262 | in = s->fs.in; |
| | 263 | in[0].time_base = base->time_base; |
| | 264 | in[1].time_base = threshold->time_base; |
| | 265 | in[2].time_base = min->time_base; |
| | 266 | in[3].time_base = max->time_base; |
| | 267 | in[0].sync = 1; |
| | 268 | in[0].before = EXT_STOP; |
| | 269 | in[0].after = EXT_STOP; |
| | 270 | in[1].sync = 1; |
| | 271 | in[1].before = EXT_STOP; |
| | 272 | in[1].after = EXT_STOP; |
| | 273 | in[2].sync = 1; |
| | 274 | in[2].before = EXT_STOP; |
| | 275 | in[2].after = EXT_STOP; |
| | 276 | in[3].sync = 1; |
| | 277 | in[3].before = EXT_STOP; |
| | 278 | in[3].after = EXT_STOP; |
| | 279 | s->fs.opaque = s; |
| | 280 | s->fs.on_event = process_frame; |
| | 281 | |
| | 282 | return ff_framesync_configure(&s->fs); |
| | 283 | } |
| | 284 | |
| | 285 | static int filter_frame(AVFilterLink *inlink, AVFrame *buf) |
| | 286 | { |
| | 287 | ThresholdContext *s = inlink->dst->priv; |
| | 288 | return ff_framesync_filter_frame(&s->fs, inlink, buf); |
| | 289 | } |
| | 290 | |
| | 291 | static int request_frame(AVFilterLink *outlink) |
| | 292 | { |
| | 293 | ThresholdContext *s = outlink->src->priv; |
| | 294 | return ff_framesync_request_frame(&s->fs, outlink); |
| | 295 | } |
| | 296 | |
| | 297 | static av_cold void uninit(AVFilterContext *ctx) |
| | 298 | { |
| | 299 | ThresholdContext *s = ctx->priv; |
| | 300 | |
| | 301 | ff_framesync_uninit(&s->fs); |
| | 302 | } |
| | 303 | |
| | 304 | static const AVFilterPad inputs[] = { |
| | 305 | { |
| | 306 | .name = "default", |
| | 307 | .type = AVMEDIA_TYPE_VIDEO, |
| | 308 | .filter_frame = filter_frame, |
| | 309 | .config_props = config_input, |
| | 310 | }, |
| | 311 | { |
| | 312 | .name = "threshold", |
| | 313 | .type = AVMEDIA_TYPE_VIDEO, |
| | 314 | .filter_frame = filter_frame, |
| | 315 | }, |
| | 316 | { |
| | 317 | .name = "min", |
| | 318 | .type = AVMEDIA_TYPE_VIDEO, |
| | 319 | .filter_frame = filter_frame, |
| | 320 | }, |
| | 321 | { |
| | 322 | .name = "max", |
| | 323 | .type = AVMEDIA_TYPE_VIDEO, |
| | 324 | .filter_frame = filter_frame, |
| | 325 | }, |
| | 326 | { NULL } |
| | 327 | }; |
| | 328 | |
| | 329 | static const AVFilterPad outputs[] = { |
| | 330 | { |
| | 331 | .name = "default", |
| | 332 | .type = AVMEDIA_TYPE_VIDEO, |
| | 333 | .config_props = config_output, |
| | 334 | .request_frame = request_frame, |
| | 335 | }, |
| | 336 | { NULL } |
| | 337 | }; |
| | 338 | |
| | 339 | AVFilter ff_vf_threshold = { |
| | 340 | .name = "threshold", |
| | 341 | .description = NULL_IF_CONFIG_SMALL("Threshold first video stream using other video streams."), |
| | 342 | .priv_size = sizeof(ThresholdContext), |
| | 343 | .priv_class = &threshold_class, |
| | 344 | .uninit = uninit, |
| | 345 | .query_formats = query_formats, |
| | 346 | .inputs = inputs, |
| | 347 | .outputs = outputs, |
| | 348 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, |
| | 349 | }; |