| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2017 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 | #include "libavutil/imgutils.h" | ||
| 22 | #include "libavutil/mem.h" | ||
| 23 | #include "libavutil/pixdesc.h" | ||
| 24 | #include "libavutil/opt.h" | ||
| 25 | #include "avfilter.h" | ||
| 26 | #include "filters.h" | ||
| 27 | #include "video.h" | ||
| 28 | #include "framesync.h" | ||
| 29 | |||
| 30 | typedef struct MidEqualizerContext { | ||
| 31 | const AVClass *class; | ||
| 32 | int width[2][4], height[2][4]; | ||
| 33 | int nb_planes; | ||
| 34 | int planes; | ||
| 35 | int histogram_size; | ||
| 36 | float *histogram[2]; | ||
| 37 | unsigned *cchange; | ||
| 38 | FFFrameSync fs; | ||
| 39 | |||
| 40 | void (*midequalizer)(const uint8_t *in0, const uint8_t *in1, | ||
| 41 | uint8_t *dst, | ||
| 42 | ptrdiff_t linesize1, ptrdiff_t linesize2, | ||
| 43 | ptrdiff_t dlinesize, | ||
| 44 | int w0, int h0, | ||
| 45 | int w1, int h1, | ||
| 46 | float *histogram1, float *histogram2, | ||
| 47 | unsigned *cchange, size_t hsize); | ||
| 48 | } MidEqualizerContext; | ||
| 49 | |||
| 50 | #define OFFSET(x) offsetof(MidEqualizerContext, x) | ||
| 51 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | ||
| 52 | |||
| 53 | static const AVOption midequalizer_options[] = { | ||
| 54 | { "planes", "set planes", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, FLAGS }, | ||
| 55 | { NULL } | ||
| 56 | }; | ||
| 57 | |||
| 58 | AVFILTER_DEFINE_CLASS(midequalizer); | ||
| 59 | |||
| 60 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 61 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, | ||
| 62 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, | ||
| 63 | AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, | ||
| 64 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, | ||
| 65 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, | ||
| 66 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, | ||
| 67 | AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, | ||
| 68 | AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, | ||
| 69 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, | ||
| 70 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, | ||
| 71 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, | ||
| 72 | AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, | ||
| 73 | AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, | ||
| 74 | AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, | ||
| 75 | AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12, | ||
| 76 | AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, | ||
| 77 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, | ||
| 78 | AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, | ||
| 79 | AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16, | ||
| 80 | AV_PIX_FMT_GRAY16, | ||
| 81 | AV_PIX_FMT_NONE | ||
| 82 | }; | ||
| 83 | |||
| 84 | ✗ | static int process_frame(FFFrameSync *fs) | |
| 85 | { | ||
| 86 | ✗ | AVFilterContext *ctx = fs->parent; | |
| 87 | ✗ | MidEqualizerContext *s = fs->opaque; | |
| 88 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 89 | AVFrame *out, *in0, *in1; | ||
| 90 | int ret; | ||
| 91 | |||
| 92 | ✗ | if ((ret = ff_framesync_get_frame(&s->fs, 0, &in0, 0)) < 0 || | |
| 93 | ✗ | (ret = ff_framesync_get_frame(&s->fs, 1, &in1, 0)) < 0) | |
| 94 | ✗ | return ret; | |
| 95 | |||
| 96 | ✗ | if (ctx->is_disabled) { | |
| 97 | ✗ | out = av_frame_clone(in0); | |
| 98 | ✗ | if (!out) | |
| 99 | ✗ | return AVERROR(ENOMEM); | |
| 100 | } else { | ||
| 101 | int p; | ||
| 102 | |||
| 103 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 104 | ✗ | if (!out) | |
| 105 | ✗ | return AVERROR(ENOMEM); | |
| 106 | ✗ | av_frame_copy_props(out, in0); | |
| 107 | |||
| 108 | ✗ | for (p = 0; p < s->nb_planes; p++) { | |
| 109 | ✗ | if (!((1 << p) & s->planes)) { | |
| 110 | ✗ | av_image_copy_plane(out->data[p], out->linesize[p], in0->data[p], in0->linesize[p], | |
| 111 | ✗ | s->width[0][p] * (1 + (s->histogram_size > 256)), s->height[0][p]); | |
| 112 | ✗ | continue; | |
| 113 | } | ||
| 114 | |||
| 115 | ✗ | s->midequalizer(in0->data[p], in1->data[p], | |
| 116 | out->data[p], | ||
| 117 | ✗ | in0->linesize[p], in1->linesize[p], | |
| 118 | ✗ | out->linesize[p], | |
| 119 | s->width[0][p], s->height[0][p], | ||
| 120 | s->width[1][p], s->height[1][p], | ||
| 121 | s->histogram[0], s->histogram[1], | ||
| 122 | ✗ | s->cchange, s->histogram_size); | |
| 123 | } | ||
| 124 | } | ||
| 125 | ✗ | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); | |
| 126 | |||
| 127 | ✗ | return ff_filter_frame(outlink, out); | |
| 128 | } | ||
| 129 | |||
| 130 | ✗ | static void compute_histogram8(const uint8_t *src, ptrdiff_t linesize, | |
| 131 | int w, int h, float *histogram, size_t hsize) | ||
| 132 | { | ||
| 133 | int y, x; | ||
| 134 | |||
| 135 | ✗ | memset(histogram, 0, hsize * sizeof(*histogram)); | |
| 136 | |||
| 137 | ✗ | for (y = 0; y < h; y++) { | |
| 138 | ✗ | for (x = 0; x < w; x++) { | |
| 139 | ✗ | histogram[src[x]] += 1; | |
| 140 | } | ||
| 141 | ✗ | src += linesize; | |
| 142 | } | ||
| 143 | |||
| 144 | ✗ | for (x = 0; x < hsize - 1; x++) { | |
| 145 | ✗ | histogram[x + 1] += histogram[x]; | |
| 146 | ✗ | histogram[x] /= hsize; | |
| 147 | } | ||
| 148 | ✗ | histogram[x] /= hsize; | |
| 149 | ✗ | } | |
| 150 | |||
| 151 | ✗ | static void compute_histogram16(const uint16_t *src, ptrdiff_t linesize, | |
| 152 | int w, int h, float *histogram, size_t hsize) | ||
| 153 | { | ||
| 154 | int y, x; | ||
| 155 | |||
| 156 | ✗ | memset(histogram, 0, hsize * sizeof(*histogram)); | |
| 157 | |||
| 158 | ✗ | for (y = 0; y < h; y++) { | |
| 159 | ✗ | for (x = 0; x < w; x++) { | |
| 160 | ✗ | histogram[src[x]] += 1; | |
| 161 | } | ||
| 162 | ✗ | src += linesize; | |
| 163 | } | ||
| 164 | |||
| 165 | ✗ | for (x = 0; x < hsize - 1; x++) { | |
| 166 | ✗ | histogram[x + 1] += histogram[x]; | |
| 167 | ✗ | histogram[x] /= hsize; | |
| 168 | } | ||
| 169 | ✗ | histogram[x] /= hsize; | |
| 170 | ✗ | } | |
| 171 | |||
| 172 | ✗ | static void compute_contrast_change(float *histogram1, float *histogram2, | |
| 173 | unsigned *cchange, size_t hsize) | ||
| 174 | { | ||
| 175 | int i; | ||
| 176 | |||
| 177 | ✗ | for (i = 0; i < hsize; i++) { | |
| 178 | int j; | ||
| 179 | |||
| 180 | ✗ | for (j = 0; j < hsize && histogram2[j] < histogram1[i]; j++); | |
| 181 | |||
| 182 | ✗ | cchange[i] = (i + j) / 2; | |
| 183 | } | ||
| 184 | ✗ | } | |
| 185 | |||
| 186 | ✗ | static void midequalizer8(const uint8_t *in0, const uint8_t *in1, | |
| 187 | uint8_t *dst, | ||
| 188 | ptrdiff_t linesize1, ptrdiff_t linesize2, | ||
| 189 | ptrdiff_t dlinesize, | ||
| 190 | int w0, int h0, | ||
| 191 | int w1, int h1, | ||
| 192 | float *histogram1, float *histogram2, | ||
| 193 | unsigned *cchange, | ||
| 194 | size_t hsize) | ||
| 195 | { | ||
| 196 | int x, y; | ||
| 197 | |||
| 198 | ✗ | compute_histogram8(in0, linesize1, w0, h0, histogram1, hsize); | |
| 199 | ✗ | compute_histogram8(in1, linesize2, w1, h1, histogram2, hsize); | |
| 200 | |||
| 201 | ✗ | compute_contrast_change(histogram1, histogram2, cchange, hsize); | |
| 202 | |||
| 203 | ✗ | for (y = 0; y < h0; y++) { | |
| 204 | ✗ | for (x = 0; x < w0; x++) { | |
| 205 | ✗ | dst[x] = av_clip_uint8(cchange[in0[x]]); | |
| 206 | } | ||
| 207 | ✗ | dst += dlinesize; | |
| 208 | ✗ | in0 += linesize1; | |
| 209 | } | ||
| 210 | ✗ | } | |
| 211 | |||
| 212 | ✗ | static void midequalizer16(const uint8_t *in0, const uint8_t *in1, | |
| 213 | uint8_t *dst, | ||
| 214 | ptrdiff_t linesize1, ptrdiff_t linesize2, | ||
| 215 | ptrdiff_t dlinesize, | ||
| 216 | int w0, int h0, | ||
| 217 | int w1, int h1, | ||
| 218 | float *histogram1, float *histogram2, | ||
| 219 | unsigned *cchange, | ||
| 220 | size_t hsize) | ||
| 221 | { | ||
| 222 | ✗ | const uint16_t *i = (const uint16_t *)in0; | |
| 223 | ✗ | uint16_t *d = (uint16_t *)dst; | |
| 224 | int x, y; | ||
| 225 | |||
| 226 | ✗ | compute_histogram16(i, linesize1 / 2, w0, h0, histogram1, hsize); | |
| 227 | ✗ | compute_histogram16((const uint16_t *)in1, linesize2 / 2, w1, h1, histogram2, hsize); | |
| 228 | |||
| 229 | ✗ | compute_contrast_change(histogram1, histogram2, cchange, hsize); | |
| 230 | |||
| 231 | ✗ | for (y = 0; y < h0; y++) { | |
| 232 | ✗ | for (x = 0; x < w0; x++) { | |
| 233 | ✗ | d[x] = cchange[i[x]]; | |
| 234 | } | ||
| 235 | ✗ | d += dlinesize / 2; | |
| 236 | ✗ | i += linesize1 / 2; | |
| 237 | } | ||
| 238 | ✗ | } | |
| 239 | |||
| 240 | ✗ | static int config_input0(AVFilterLink *inlink) | |
| 241 | { | ||
| 242 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 243 | ✗ | MidEqualizerContext *s = ctx->priv; | |
| 244 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 245 | int vsub, hsub; | ||
| 246 | |||
| 247 | ✗ | s->nb_planes = av_pix_fmt_count_planes(inlink->format); | |
| 248 | |||
| 249 | ✗ | hsub = desc->log2_chroma_w; | |
| 250 | ✗ | vsub = desc->log2_chroma_h; | |
| 251 | |||
| 252 | ✗ | s->height[0][0] = s->height[0][3] = inlink->h; | |
| 253 | ✗ | s->width[0][0] = s->width[0][3] = inlink->w; | |
| 254 | ✗ | s->height[0][1] = s->height[0][2] = AV_CEIL_RSHIFT(inlink->h, vsub); | |
| 255 | ✗ | s->width[0][1] = s->width[0][2] = AV_CEIL_RSHIFT(inlink->w, hsub); | |
| 256 | |||
| 257 | ✗ | s->histogram_size = 1 << desc->comp[0].depth; | |
| 258 | |||
| 259 | ✗ | s->histogram[0] = av_calloc(s->histogram_size, sizeof(float)); | |
| 260 | ✗ | s->histogram[1] = av_calloc(s->histogram_size, sizeof(float)); | |
| 261 | ✗ | s->cchange = av_calloc(s->histogram_size, sizeof(unsigned)); | |
| 262 | ✗ | if (!s->histogram[0] || !s->histogram[1] || !s->cchange) | |
| 263 | ✗ | return AVERROR(ENOMEM); | |
| 264 | |||
| 265 | ✗ | if (s->histogram_size == 256) { | |
| 266 | ✗ | s->midequalizer = midequalizer8; | |
| 267 | } else { | ||
| 268 | ✗ | s->midequalizer = midequalizer16; | |
| 269 | } | ||
| 270 | |||
| 271 | ✗ | return 0; | |
| 272 | } | ||
| 273 | |||
| 274 | ✗ | static int config_input1(AVFilterLink *inlink) | |
| 275 | { | ||
| 276 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 277 | ✗ | MidEqualizerContext *s = ctx->priv; | |
| 278 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 279 | int vsub, hsub; | ||
| 280 | |||
| 281 | ✗ | s->nb_planes = av_pix_fmt_count_planes(inlink->format); | |
| 282 | |||
| 283 | ✗ | hsub = desc->log2_chroma_w; | |
| 284 | ✗ | vsub = desc->log2_chroma_h; | |
| 285 | |||
| 286 | ✗ | s->height[1][0] = s->height[1][3] = inlink->h; | |
| 287 | ✗ | s->width[1][0] = s->width[1][3] = inlink->w; | |
| 288 | ✗ | s->height[1][1] = s->height[1][2] = AV_CEIL_RSHIFT(inlink->h, vsub); | |
| 289 | ✗ | s->width[1][1] = s->width[1][2] = AV_CEIL_RSHIFT(inlink->w, hsub); | |
| 290 | |||
| 291 | ✗ | return 0; | |
| 292 | } | ||
| 293 | |||
| 294 | ✗ | static int config_output(AVFilterLink *outlink) | |
| 295 | { | ||
| 296 | ✗ | AVFilterContext *ctx = outlink->src; | |
| 297 | ✗ | MidEqualizerContext *s = ctx->priv; | |
| 298 | ✗ | AVFilterLink *in0 = ctx->inputs[0]; | |
| 299 | ✗ | AVFilterLink *in1 = ctx->inputs[1]; | |
| 300 | ✗ | FilterLink *il = ff_filter_link(in0); | |
| 301 | ✗ | FilterLink *ol = ff_filter_link(outlink); | |
| 302 | FFFrameSyncIn *in; | ||
| 303 | int ret; | ||
| 304 | |||
| 305 | ✗ | outlink->w = in0->w; | |
| 306 | ✗ | outlink->h = in0->h; | |
| 307 | ✗ | outlink->sample_aspect_ratio = in0->sample_aspect_ratio; | |
| 308 | ✗ | ol->frame_rate = il->frame_rate; | |
| 309 | |||
| 310 | ✗ | if ((ret = ff_framesync_init(&s->fs, ctx, 2)) < 0) | |
| 311 | ✗ | return ret; | |
| 312 | |||
| 313 | ✗ | in = s->fs.in; | |
| 314 | ✗ | in[0].time_base = in0->time_base; | |
| 315 | ✗ | in[1].time_base = in1->time_base; | |
| 316 | ✗ | in[0].sync = 1; | |
| 317 | ✗ | in[0].before = EXT_STOP; | |
| 318 | ✗ | in[0].after = EXT_INFINITY; | |
| 319 | ✗ | in[1].sync = 1; | |
| 320 | ✗ | in[1].before = EXT_STOP; | |
| 321 | ✗ | in[1].after = EXT_INFINITY; | |
| 322 | ✗ | s->fs.opaque = s; | |
| 323 | ✗ | s->fs.on_event = process_frame; | |
| 324 | |||
| 325 | ✗ | ret = ff_framesync_configure(&s->fs); | |
| 326 | ✗ | outlink->time_base = s->fs.time_base; | |
| 327 | |||
| 328 | ✗ | return ret; | |
| 329 | } | ||
| 330 | |||
| 331 | ✗ | static int activate(AVFilterContext *ctx) | |
| 332 | { | ||
| 333 | ✗ | MidEqualizerContext *s = ctx->priv; | |
| 334 | ✗ | return ff_framesync_activate(&s->fs); | |
| 335 | } | ||
| 336 | |||
| 337 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 338 | { | ||
| 339 | ✗ | MidEqualizerContext *s = ctx->priv; | |
| 340 | |||
| 341 | ✗ | ff_framesync_uninit(&s->fs); | |
| 342 | ✗ | av_freep(&s->histogram[0]); | |
| 343 | ✗ | av_freep(&s->histogram[1]); | |
| 344 | ✗ | av_freep(&s->cchange); | |
| 345 | ✗ | } | |
| 346 | |||
| 347 | static const AVFilterPad midequalizer_inputs[] = { | ||
| 348 | { | ||
| 349 | .name = "in0", | ||
| 350 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 351 | .config_props = config_input0, | ||
| 352 | }, | ||
| 353 | { | ||
| 354 | .name = "in1", | ||
| 355 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 356 | .config_props = config_input1, | ||
| 357 | }, | ||
| 358 | }; | ||
| 359 | |||
| 360 | static const AVFilterPad midequalizer_outputs[] = { | ||
| 361 | { | ||
| 362 | .name = "default", | ||
| 363 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 364 | .config_props = config_output, | ||
| 365 | }, | ||
| 366 | }; | ||
| 367 | |||
| 368 | const FFFilter ff_vf_midequalizer = { | ||
| 369 | .p.name = "midequalizer", | ||
| 370 | .p.description = NULL_IF_CONFIG_SMALL("Apply Midway Equalization."), | ||
| 371 | .p.priv_class = &midequalizer_class, | ||
| 372 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, | ||
| 373 | .priv_size = sizeof(MidEqualizerContext), | ||
| 374 | .uninit = uninit, | ||
| 375 | .activate = activate, | ||
| 376 | FILTER_INPUTS(midequalizer_inputs), | ||
| 377 | FILTER_OUTPUTS(midequalizer_outputs), | ||
| 378 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 379 | }; | ||
| 380 |