• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2020 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 
29 typedef struct TMidEqualizerContext {
30     const AVClass *class;
31 
32     int planes;
33     int radius;
34     float sigma;
35 
36     int plane_width[4], plane_height[4];
37     int nb_frames;
38     int depth;
39     int f_frames;
40     int l_frames;
41     int del_frame;
42     int cur_frame;
43     int nb_planes;
44     int histogram_size;
45     float  kernel[127];
46     float *histogram[4][256];
47     float *change[4];
48 
49     AVFrame **frames;
50 
51     void (*compute_histogram)(const uint8_t *ssrc, ptrdiff_t linesize,
52                               int w, int h, float *histogram, size_t hsize);
53     void (*apply_contrast_change)(const uint8_t *src, ptrdiff_t src_linesize,
54                                   uint8_t *dst, ptrdiff_t dst_linesize,
55                                   int w, int h, float *change, float *orig);
56 } TMidEqualizerContext;
57 
58 #define OFFSET(x) offsetof(TMidEqualizerContext, x)
59 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
60 
61 static const AVOption tmidequalizer_options[] = {
62     { "radius", "set radius", OFFSET(radius), AV_OPT_TYPE_INT,   {.i64=5},   1, 127, FLAGS },
63     { "sigma",  "set sigma",  OFFSET(sigma),  AV_OPT_TYPE_FLOAT, {.dbl=0.5}, 0,   1, FLAGS },
64     { "planes", "set planes", OFFSET(planes), AV_OPT_TYPE_INT,   {.i64=0xF}, 0, 0xF, FLAGS },
65     { NULL }
66 };
67 
68 AVFILTER_DEFINE_CLASS(tmidequalizer);
69 
70 static const enum AVPixelFormat pix_fmts[] = {
71     AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
72     AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
73     AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
74     AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
75     AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
76     AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP,
77     AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14,
78     AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
79     AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
80     AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12,
81     AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
82     AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14,
83     AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
84     AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
85     AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
86     AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12,
87     AV_PIX_FMT_YUV420P16,  AV_PIX_FMT_YUV422P16,  AV_PIX_FMT_YUV444P16,
88     AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
89     AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16,
90     AV_PIX_FMT_GRAY16,
91     AV_PIX_FMT_NONE
92 };
93 
compute_contrast_function(const float * const histograms[256],const float * const kernel,int nb_frames,int radius,int hsize,float * f,int idx)94 static void compute_contrast_function(const float *const histograms[256],
95                                       const float *const kernel,
96                                       int nb_frames, int radius, int hsize,
97                                       float *f, int idx)
98 {
99     const float *const h1 = histograms[idx];
100     int p2[256] = { 0 };
101 
102     for (int p1 = 0; p1 < hsize; p1++) {
103         float weight = 1.f;
104         float sum = p1 * weight;
105 
106         for (int j = 0; j < radius; j++) {
107             const int nidx = ((idx - radius + j) % nb_frames);
108             const float *const h2 = histograms[nidx < 0 ? nidx + nb_frames: nidx];
109             int k = j;
110 
111             for (; p2[k] < hsize && h2[p2[k]] < h1[p1]; p2[k]++);
112             if (p2[k] == hsize)
113                 p2[k]--;
114 
115             weight += kernel[j];
116             sum += kernel[j] * p2[k];
117         }
118 
119         for (int j = radius + 1; j < nb_frames; j++) {
120             const int nidx = (idx - radius + j) % nb_frames;
121             const float *const h2 = histograms[nidx < 0 ? nidx + nb_frames: nidx];
122             int k = j;
123 
124             for (; p2[k] < hsize && h2[p2[k]] < h1[p1]; p2[k]++);
125             if (p2[k] == hsize)
126                 p2[k]--;
127 
128             weight += kernel[j - radius - 1];
129             sum += kernel[j - radius - 1] * p2[k];
130         }
131 
132         f[p1] = sum / weight;
133     }
134 }
135 
apply_contrast_change8(const uint8_t * src,ptrdiff_t src_linesize,uint8_t * dst,ptrdiff_t dst_linesize,int w,int h,float * change,float * orig)136 static void apply_contrast_change8(const uint8_t *src, ptrdiff_t src_linesize,
137                                    uint8_t *dst, ptrdiff_t dst_linesize,
138                                    int w, int h, float *change, float *orig)
139 {
140     for (int y = 0; y < h; y++) {
141         for (int x = 0; x < w; x++)
142             dst[x] = lrintf(change[src[x]]);
143 
144         dst += dst_linesize;
145         src += src_linesize;
146     }
147 }
148 
apply_contrast_change16(const uint8_t * ssrc,ptrdiff_t src_linesize,uint8_t * ddst,ptrdiff_t dst_linesize,int w,int h,float * change,float * orig)149 static void apply_contrast_change16(const uint8_t *ssrc, ptrdiff_t src_linesize,
150                                     uint8_t *ddst, ptrdiff_t dst_linesize,
151                                     int w, int h, float *change, float *orig)
152 {
153     const uint16_t *src = (const uint16_t *)ssrc;
154     uint16_t *dst = (uint16_t *)ddst;
155 
156     for (int y = 0; y < h; y++) {
157         for (int x = 0; x < w; x++)
158             dst[x] = lrintf(change[src[x]]);
159 
160         dst += dst_linesize / 2;
161         src += src_linesize / 2;
162     }
163 }
164 
filter_frame(AVFilterLink * inlink,AVFrame * in)165 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
166 {
167     AVFilterContext *ctx = inlink->dst;
168     TMidEqualizerContext *s = ctx->priv;
169     AVFilterLink *outlink = ctx->outputs[0];
170     AVFrame *out;
171     int eof = 0;
172 
173     if (!in) {
174         int idx = s->f_frames < s->nb_frames ? s->radius : s->del_frame ? s->del_frame - 1 : s->nb_frames - 1;
175 
176         if (s->f_frames < s->nb_frames) {
177             s->l_frames = s->nb_frames - s->f_frames;
178         } else {
179             s->l_frames++;
180         }
181         if (!s->frames[idx])
182             return AVERROR_EOF;
183         in = av_frame_clone(s->frames[idx]);
184         if (!in)
185             return AVERROR(ENOMEM);
186         eof = 1;
187     }
188 
189     if (s->f_frames < s->nb_frames) {
190         s->frames[s->f_frames] = in;
191 
192         for (int p = 0; p < s->nb_planes; p++) {
193             s->compute_histogram(in->data[p], in->linesize[p],
194                                  s->plane_width[p], s->plane_height[p],
195                                  s->histogram[p][s->f_frames],
196                                  s->histogram_size);
197         }
198 
199         s->f_frames++;
200 
201         while (s->f_frames <= s->radius) {
202             s->frames[s->f_frames] = av_frame_clone(in);
203             if (!s->frames[s->f_frames])
204                 return AVERROR(ENOMEM);
205             for (int p = 0; p < s->nb_planes; p++) {
206                 memcpy(s->histogram[p][s->f_frames],
207                        s->histogram[p][s->f_frames - 1],
208                        s->histogram_size * sizeof(float));
209             }
210             s->f_frames++;
211         }
212 
213         if (!eof && s->f_frames < s->nb_frames) {
214             return 0;
215         } else {
216             while (s->f_frames < s->nb_frames) {
217                 s->frames[s->f_frames] = av_frame_clone(in);
218                 if (!s->frames[s->f_frames])
219                     return AVERROR(ENOMEM);
220                 for (int p = 0; p < s->nb_planes; p++) {
221                     memcpy(s->histogram[p][s->f_frames],
222                            s->histogram[p][s->f_frames - 1],
223                            s->histogram_size * sizeof(float));
224                 }
225                 s->f_frames++;
226             }
227         }
228         s->cur_frame = s->radius;
229         s->del_frame = 0;
230     } else {
231         av_frame_free(&s->frames[s->del_frame]);
232         s->frames[s->del_frame] = in;
233 
234         for (int p = 0; p < s->nb_planes; p++) {
235             s->compute_histogram(in->data[p], in->linesize[p],
236                                  s->plane_width[p], s->plane_height[p],
237                                  s->histogram[p][s->del_frame],
238                                  s->histogram_size);
239         }
240 
241         s->del_frame++;
242         if (s->del_frame >= s->nb_frames)
243             s->del_frame = 0;
244     }
245 
246     if (ctx->is_disabled) {
247         const int idx = s->cur_frame;
248 
249         out = av_frame_clone(s->frames[idx]);
250         if (!out)
251             return AVERROR(ENOMEM);
252     } else {
253         const int idx = s->cur_frame;
254 
255         in = s->frames[idx];
256         out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
257         if (!out)
258             return AVERROR(ENOMEM);
259         av_frame_copy_props(out, in);
260 
261         for (int p = 0; p < s->nb_planes; p++) {
262             if (!((1 << p) & s->planes)) {
263                 av_image_copy_plane(out->data[p], out->linesize[p], in->data[p], in->linesize[p],
264                                     s->plane_width[p] * (1 + (s->depth > 8)), s->plane_height[p]);
265                 continue;
266             }
267 
268             compute_contrast_function((const float *const *)s->histogram[p], s->kernel,
269                                       s->nb_frames, s->radius, s->histogram_size, s->change[p], idx);
270 
271             s->apply_contrast_change(in->data[p], in->linesize[p],
272                                      out->data[p], out->linesize[p],
273                                      s->plane_width[p], s->plane_height[p],
274                                      s->change[p], s->histogram[p][idx]);
275         }
276     }
277 
278     s->cur_frame++;
279     if (s->cur_frame >= s->nb_frames)
280         s->cur_frame = 0;
281 
282     return ff_filter_frame(outlink, out);
283 }
284 
compute_histogram8(const uint8_t * src,ptrdiff_t linesize,int w,int h,float * histogram,size_t hsize)285 static void compute_histogram8(const uint8_t *src, ptrdiff_t linesize,
286                                int w, int h, float *histogram, size_t hsize)
287 {
288     memset(histogram, 0, hsize * sizeof(*histogram));
289 
290     for (int y = 0; y < h; y++) {
291         for (int x = 0; x < w; x++)
292             histogram[src[x]] += 1;
293         src += linesize;
294     }
295 
296     for (int x = 0; x < hsize; x++)
297         histogram[x] /= hsize;
298 
299     for (int x = 1; x < hsize; x++)
300         histogram[x] += histogram[x-1];
301 }
302 
compute_histogram16(const uint8_t * ssrc,ptrdiff_t linesize,int w,int h,float * histogram,size_t hsize)303 static void compute_histogram16(const uint8_t *ssrc, ptrdiff_t linesize,
304                                 int w, int h, float *histogram, size_t hsize)
305 {
306     const uint16_t *src = (const uint16_t *)ssrc;
307 
308     memset(histogram, 0, hsize * sizeof(*histogram));
309 
310     for (int y = 0; y < h; y++) {
311         for (int x = 0; x < w; x++)
312             histogram[src[x]] += 1;
313         src += linesize / 2;
314     }
315 
316     for (int x = 0; x < hsize; x++)
317         histogram[x] /= hsize;
318 
319     for (int x = 1; x < hsize; x++)
320         histogram[x] += histogram[x-1];
321 }
322 
config_input(AVFilterLink * inlink)323 static int config_input(AVFilterLink *inlink)
324 {
325     AVFilterContext *ctx = inlink->dst;
326     TMidEqualizerContext *s = ctx->priv;
327     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
328     float sigma = s->radius * s->sigma;
329     int vsub, hsub;
330 
331     s->depth = desc->comp[0].depth;
332     s->nb_frames = s->radius * 2 + 1;
333     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
334 
335     hsub = desc->log2_chroma_w;
336     vsub = desc->log2_chroma_h;
337 
338     s->plane_height[0] = s->plane_height[3] = inlink->h;
339     s->plane_width[0]  = s->plane_width[3]  = inlink->w;
340     s->plane_height[1] = s->plane_height[2] = AV_CEIL_RSHIFT(inlink->h, vsub);
341     s->plane_width[1]  = s->plane_width[2]  = AV_CEIL_RSHIFT(inlink->w, hsub);
342 
343     s->histogram_size = 1 << s->depth;
344 
345     for (int n = 0; n < s->radius; n++)
346         s->kernel[n] = expf(-0.5 * (n + 1) * (n + 1) / (sigma * sigma));
347 
348     for (int p = 0; p < s->nb_planes; p++) {
349         for (int n = 0; n < s->nb_frames; n++) {
350             s->histogram[p][n] = av_calloc(s->histogram_size, sizeof(float));
351             if (!s->histogram[p][n])
352                 return AVERROR(ENOMEM);
353         }
354 
355         s->change[p] = av_calloc(s->histogram_size, sizeof(float));
356         if (!s->change[p])
357             return AVERROR(ENOMEM);
358     }
359 
360     if (!s->frames)
361         s->frames = av_calloc(s->nb_frames, sizeof(*s->frames));
362     if (!s->frames)
363         return AVERROR(ENOMEM);
364 
365     s->compute_histogram = s->depth <= 8 ? compute_histogram8 : compute_histogram16;
366     s->apply_contrast_change = s->depth <= 8 ? apply_contrast_change8 : apply_contrast_change16;
367 
368     return 0;
369 }
370 
request_frame(AVFilterLink * outlink)371 static int request_frame(AVFilterLink *outlink)
372 {
373     AVFilterContext *ctx = outlink->src;
374     TMidEqualizerContext *s = ctx->priv;
375     int ret;
376 
377     ret = ff_request_frame(ctx->inputs[0]);
378     if (ret == AVERROR_EOF && s->l_frames < s->radius) {
379         ret = filter_frame(ctx->inputs[0], NULL);
380     }
381 
382     return ret;
383 }
384 
free_histograms(AVFilterContext * ctx,int x,int nb_frames)385 static void free_histograms(AVFilterContext *ctx, int x, int nb_frames)
386 {
387     TMidEqualizerContext *s = ctx->priv;
388 
389     for (int n = 0; n < nb_frames; n++)
390         av_freep(&s->histogram[x][n]);
391     av_freep(&s->change[x]);
392 }
393 
uninit(AVFilterContext * ctx)394 static av_cold void uninit(AVFilterContext *ctx)
395 {
396     TMidEqualizerContext *s = ctx->priv;
397 
398     free_histograms(ctx, 0, s->nb_frames);
399     free_histograms(ctx, 1, s->nb_frames);
400     free_histograms(ctx, 2, s->nb_frames);
401     free_histograms(ctx, 3, s->nb_frames);
402 
403     for (int i = 0; i < s->nb_frames && s->frames; i++)
404         av_frame_free(&s->frames[i]);
405     av_freep(&s->frames);
406 }
407 
408 static const AVFilterPad tmidequalizer_inputs[] = {
409     {
410         .name         = "default",
411         .type         = AVMEDIA_TYPE_VIDEO,
412         .config_props = config_input,
413         .filter_frame = filter_frame,
414     },
415 };
416 
417 static const AVFilterPad tmidequalizer_outputs[] = {
418     {
419         .name          = "default",
420         .type          = AVMEDIA_TYPE_VIDEO,
421         .request_frame = request_frame,
422     },
423 };
424 
425 const AVFilter ff_vf_tmidequalizer = {
426     .name          = "tmidequalizer",
427     .description   = NULL_IF_CONFIG_SMALL("Apply Temporal Midway Equalization."),
428     .priv_size     = sizeof(TMidEqualizerContext),
429     .uninit        = uninit,
430     FILTER_INPUTS(tmidequalizer_inputs),
431     FILTER_OUTPUTS(tmidequalizer_outputs),
432     FILTER_PIXFMTS_ARRAY(pix_fmts),
433     .priv_class    = &tmidequalizer_class,
434     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
435 };
436