blob: b95a86dd706722813bf420b1782d33062a8eabee
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/pixdesc.h" |
23 | #include "libavutil/opt.h" |
24 | #include "avfilter.h" |
25 | #include "formats.h" |
26 | #include "internal.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 int query_formats(AVFilterContext *ctx) |
61 | { |
62 | static const enum AVPixelFormat pix_fmts[] = { |
63 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, |
64 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, |
65 | AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, |
66 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, |
67 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, |
68 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, |
69 | AV_PIX_FMT_GRAY8, |
70 | AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, |
71 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, |
72 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, |
73 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, |
74 | AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, |
75 | AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, |
76 | AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, |
77 | AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, |
78 | AV_PIX_FMT_NONE |
79 | }; |
80 | |
81 | return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
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(in0->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 | FFFrameSyncIn *in; |
301 | int ret; |
302 | |
303 | if (in0->format != in1->format) { |
304 | av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n"); |
305 | return AVERROR(EINVAL); |
306 | } |
307 | |
308 | outlink->w = in0->w; |
309 | outlink->h = in0->h; |
310 | outlink->time_base = in0->time_base; |
311 | outlink->sample_aspect_ratio = in0->sample_aspect_ratio; |
312 | outlink->frame_rate = in0->frame_rate; |
313 | |
314 | if ((ret = ff_framesync_init(&s->fs, ctx, 2)) < 0) |
315 | return ret; |
316 | |
317 | in = s->fs.in; |
318 | in[0].time_base = in0->time_base; |
319 | in[1].time_base = in1->time_base; |
320 | in[0].sync = 1; |
321 | in[0].before = EXT_STOP; |
322 | in[0].after = EXT_INFINITY; |
323 | in[1].sync = 1; |
324 | in[1].before = EXT_STOP; |
325 | in[1].after = EXT_INFINITY; |
326 | s->fs.opaque = s; |
327 | s->fs.on_event = process_frame; |
328 | |
329 | return ff_framesync_configure(&s->fs); |
330 | } |
331 | |
332 | static int filter_frame(AVFilterLink *inlink, AVFrame *buf) |
333 | { |
334 | MidEqualizerContext *s = inlink->dst->priv; |
335 | return ff_framesync_filter_frame(&s->fs, inlink, buf); |
336 | } |
337 | |
338 | static int request_frame(AVFilterLink *outlink) |
339 | { |
340 | MidEqualizerContext *s = outlink->src->priv; |
341 | return ff_framesync_request_frame(&s->fs, outlink); |
342 | } |
343 | |
344 | static av_cold void uninit(AVFilterContext *ctx) |
345 | { |
346 | MidEqualizerContext *s = ctx->priv; |
347 | |
348 | ff_framesync_uninit(&s->fs); |
349 | av_freep(&s->histogram[0]); |
350 | av_freep(&s->histogram[1]); |
351 | av_freep(&s->cchange); |
352 | } |
353 | |
354 | static const AVFilterPad midequalizer_inputs[] = { |
355 | { |
356 | .name = "in0", |
357 | .type = AVMEDIA_TYPE_VIDEO, |
358 | .filter_frame = filter_frame, |
359 | .config_props = config_input0, |
360 | }, |
361 | { |
362 | .name = "in1", |
363 | .type = AVMEDIA_TYPE_VIDEO, |
364 | .filter_frame = filter_frame, |
365 | .config_props = config_input1, |
366 | }, |
367 | { NULL } |
368 | }; |
369 | |
370 | static const AVFilterPad midequalizer_outputs[] = { |
371 | { |
372 | .name = "default", |
373 | .type = AVMEDIA_TYPE_VIDEO, |
374 | .config_props = config_output, |
375 | .request_frame = request_frame, |
376 | }, |
377 | { NULL } |
378 | }; |
379 | |
380 | AVFilter ff_vf_midequalizer = { |
381 | .name = "midequalizer", |
382 | .description = NULL_IF_CONFIG_SMALL("Apply Midway Equalization."), |
383 | .priv_size = sizeof(MidEqualizerContext), |
384 | .uninit = uninit, |
385 | .query_formats = query_formats, |
386 | .inputs = midequalizer_inputs, |
387 | .outputs = midequalizer_outputs, |
388 | .priv_class = &midequalizer_class, |
389 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, |
390 | }; |
391 |