]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_readeia608.c
avfilter/vf_readeia608: implement lowpass operation prior to processing lines
[ffmpeg] / libavfilter / vf_readeia608.c
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 /**
22  * @file
23  * Filter for reading closed captioning data (EIA-608).
24  * See also https://en.wikipedia.org/wiki/EIA-608
25  */
26
27 #include <string.h>
28
29 #include "libavutil/internal.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/pixdesc.h"
32 #include "libavutil/timestamp.h"
33
34 #include "avfilter.h"
35 #include "formats.h"
36 #include "internal.h"
37 #include "video.h"
38
39 #define FALL 0
40 #define RISE 1
41
42 typedef struct ReadEIA608Context {
43     const AVClass *class;
44     int start, end;
45     int min_range;
46     int max_peak_diff;
47     int max_period_diff;
48     int max_start_diff;
49     int nb_found;
50     int white;
51     int black;
52     float mpd, mhd, msd, mac, spw, bhd, wth, bth;
53     int chp;
54     int lp;
55     uint8_t *temp;
56 } ReadEIA608Context;
57
58 #define OFFSET(x) offsetof(ReadEIA608Context, x)
59 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
60
61 static const AVOption readeia608_options[] = {
62     { "scan_min", "set from which line to scan for codes",                            OFFSET(start), AV_OPT_TYPE_INT,   {.i64=0},       0, INT_MAX, FLAGS },
63     { "scan_max", "set to which line to scan for codes",                              OFFSET(end),   AV_OPT_TYPE_INT,   {.i64=29},      0, INT_MAX, FLAGS },
64     { "mac",      "set minimal acceptable amplitude change for sync codes detection", OFFSET(mac),   AV_OPT_TYPE_FLOAT, {.dbl=.2},  0.001,       1, FLAGS },
65     { "spw",      "set ratio of width reserved for sync code detection",              OFFSET(spw),   AV_OPT_TYPE_FLOAT, {.dbl=.27},   0.1,     0.7, FLAGS },
66     { "mhd",      "set max peaks height difference for sync code detection",          OFFSET(mhd),   AV_OPT_TYPE_FLOAT, {.dbl=.1},      0,     0.5, FLAGS },
67     { "mpd",      "set max peaks period difference for sync code detection",          OFFSET(mpd),   AV_OPT_TYPE_FLOAT, {.dbl=.1},      0,     0.5, FLAGS },
68     { "msd",      "set first two max start code bits differences",                    OFFSET(msd),   AV_OPT_TYPE_FLOAT, {.dbl=.02},     0,     0.5, FLAGS },
69     { "bhd",      "set min ratio of bits height compared to 3rd start code bit",      OFFSET(bhd),   AV_OPT_TYPE_FLOAT, {.dbl=.75},  0.01,       1, FLAGS },
70     { "th_w",     "set white color threshold",                                        OFFSET(wth),   AV_OPT_TYPE_FLOAT, {.dbl=.35},   0.1,       1, FLAGS },
71     { "th_b",     "set black color threshold",                                        OFFSET(bth),   AV_OPT_TYPE_FLOAT, {.dbl=.15},     0,     0.5, FLAGS },
72     { "chp",      "check and apply parity bit",                                       OFFSET(chp),   AV_OPT_TYPE_BOOL,  {.i64= 0},      0,       1, FLAGS },
73     { "lp",       "lowpass line prior to processing",                                 OFFSET(lp),    AV_OPT_TYPE_BOOL,  {.i64= 0},      0,       1, FLAGS },
74     { NULL }
75 };
76
77 AVFILTER_DEFINE_CLASS(readeia608);
78
79 static int query_formats(AVFilterContext *ctx)
80 {
81     static const enum AVPixelFormat pixel_fmts[] = {
82         AV_PIX_FMT_GRAY8,
83         AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
84         AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
85         AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
86         AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P,
87         AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P,
88         AV_PIX_FMT_YUVJ411P,
89         AV_PIX_FMT_NONE
90     };
91     AVFilterFormats *formats = ff_make_format_list(pixel_fmts);
92     if (!formats)
93         return AVERROR(ENOMEM);
94     return ff_set_common_formats(ctx, formats);
95 }
96
97 static int config_input(AVFilterLink *inlink)
98 {
99     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
100     AVFilterContext *ctx = inlink->dst;
101     ReadEIA608Context *s = ctx->priv;
102     int depth = desc->comp[0].depth;
103
104     if (s->end >= inlink->h) {
105         av_log(ctx, AV_LOG_WARNING, "Last line to scan too large, clipping.\n");
106         s->end = inlink->h - 1;
107     }
108
109     if (s->start > s->end) {
110         av_log(ctx, AV_LOG_ERROR, "Invalid range.\n");
111         return AVERROR(EINVAL);
112     }
113
114     s->min_range = s->mac * ((1 << depth) - 1);
115     s->max_peak_diff = s->mhd * ((1 << depth) - 1);
116     s->max_period_diff = s->mpd * ((1 << depth) - 1);
117     s->max_start_diff = s->msd * ((1 << depth) - 1);
118     s->white = s->wth * ((1 << depth) - 1);
119     s->black = s->bth * ((1 << depth) - 1);
120     s->temp = av_calloc(inlink->w, sizeof(*s->temp));
121     if (!s->temp)
122         return AVERROR(ENOMEM);
123
124     return 0;
125 }
126
127 static void extract_line(AVFilterContext *ctx, AVFilterLink *inlink, AVFrame *in, int line)
128 {
129     ReadEIA608Context *s = ctx->priv;
130     int max = 0, min = INT_MAX;
131     int i, ch, range = 0;
132     const uint8_t *src;
133     uint16_t clock[8][2] = { { 0 } };
134     const int sync_width = s->spw * in->width;
135     int last = 0, peaks = 0, max_peak_diff = 0, dir = RISE;
136     const int width_per_bit = (in->width - sync_width) / 19;
137     uint8_t byte[2] = { 0 };
138     int s1, s2, s3, parity;
139
140     src = &in->data[0][line * in->linesize[0]];
141
142     if (s->lp) {
143         uint8_t *dst = s->temp;
144         int w = inlink->w - 1;
145
146         for (i = 0; i < inlink->w; i++) {
147             int a = FFMAX(i - 3, 0);
148             int b = FFMAX(i - 2, 0);
149             int c = FFMAX(i - 1, 0);
150             int d = FFMIN(i + 3, w);
151             int e = FFMIN(i + 2, w);
152             int f = FFMIN(i + 1, w);
153
154             dst[i] = (src[a] + src[b] + src[c] + src[i] + src[d] + src[e] + src[f] + 6) / 7;
155         }
156
157         src = s->temp;
158     }
159
160     for (i = 0; i < sync_width; i++) {
161         max = FFMAX(max, src[i]);
162         min = FFMIN(min, src[i]);
163     }
164
165     range = max - min;
166     if (range < s->min_range)
167         return;
168
169     for (i = 0; i < sync_width; i++) {
170         int Y = src[i];
171
172         if (dir == RISE) {
173             if (Y < last) {
174                 dir = FALL;
175                 if (last >= s->white) {
176                     clock[peaks][0] = last;
177                     clock[peaks][1] = i;
178                     peaks++;
179                     if (peaks > 7)
180                         break;
181                 }
182             }
183         } else if (dir == FALL) {
184             if (Y > last && last <= s->black) {
185                 dir = RISE;
186             }
187         }
188         last = Y;
189     }
190
191     if (peaks != 7) {
192         av_log(ctx, AV_LOG_DEBUG, "peaks: %d != 7\n", peaks);
193         return;
194     }
195
196     for (i = 1; i < 7; i++)
197         max_peak_diff = FFMAX(max_peak_diff, FFABS(clock[i][0] - clock[i-1][0]));
198
199     if (max_peak_diff > s->max_peak_diff) {
200         av_log(ctx, AV_LOG_DEBUG, "mhd: %d > %d\n", max_peak_diff, s->max_peak_diff);
201         return;
202     }
203
204     max = 0; min = INT_MAX;
205     for (i = 1; i < 7; i++) {
206         max = FFMAX(max, FFABS(clock[i][1] - clock[i-1][1]));
207         min = FFMIN(min, FFABS(clock[i][1] - clock[i-1][1]));
208     }
209
210     range = max - min;
211     if (range > s->max_period_diff) {
212         av_log(ctx, AV_LOG_DEBUG, "mpd: %d > %d\n", range, s->max_period_diff);
213         return;
214     }
215
216     s1 = src[sync_width + width_per_bit * 0 + width_per_bit / 2];
217     s2 = src[sync_width + width_per_bit * 1 + width_per_bit / 2];
218     s3 = src[sync_width + width_per_bit * 2 + width_per_bit / 2];
219
220     if (FFABS(s1 - s2) > s->max_start_diff || s1 > s->black || s2 > s->black || s3 < s->white) {
221         av_log(ctx, AV_LOG_DEBUG, "msd: %d > %d\n", FFABS(s1 - s2), s->max_start_diff);
222         return;
223     }
224
225     for (ch = 0; ch < 2; ch++) {
226         for (parity = 0, i = 0; i < 8; i++) {
227             int b = src[sync_width + width_per_bit * (i + 3 + 8 * ch) + width_per_bit / 2];
228
229             if (b - s1 > (s3 - s1) * s->bhd) {
230                 b = 1;
231                 parity++;
232             } else {
233                 b = 0;
234             }
235             byte[ch] |= b << i;
236         }
237
238         if (s->chp) {
239             if (!(parity & 1)) {
240                 byte[ch] = 0;
241             }
242         }
243     }
244
245     {
246         uint8_t key[128], value[128];
247
248         snprintf(key, sizeof(key), "lavfi.readeia608.%d.cc", s->nb_found);
249         snprintf(value, sizeof(value), "0x%02X%02X", byte[0], byte[1]);
250         av_dict_set(&in->metadata, key, value, 0);
251
252         snprintf(key, sizeof(key), "lavfi.readeia608.%d.line", s->nb_found);
253         snprintf(value, sizeof(value), "%d", line);
254         av_dict_set(&in->metadata, key, value, 0);
255     }
256
257     s->nb_found++;
258 }
259
260 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
261 {
262     AVFilterContext *ctx  = inlink->dst;
263     AVFilterLink *outlink = ctx->outputs[0];
264     ReadEIA608Context *s = ctx->priv;
265     int i;
266
267     s->nb_found = 0;
268     for (i = s->start; i <= s->end; i++)
269         extract_line(ctx, inlink, in, i);
270
271     return ff_filter_frame(outlink, in);
272 }
273
274 static av_cold void uninit(AVFilterContext *ctx)
275 {
276     ReadEIA608Context *s = ctx->priv;
277
278     av_freep(&s->temp);
279 }
280
281 static const AVFilterPad readeia608_inputs[] = {
282     {
283         .name         = "default",
284         .type         = AVMEDIA_TYPE_VIDEO,
285         .filter_frame = filter_frame,
286         .config_props = config_input,
287     },
288     { NULL }
289 };
290
291 static const AVFilterPad readeia608_outputs[] = {
292     {
293         .name = "default",
294         .type = AVMEDIA_TYPE_VIDEO,
295     },
296     { NULL }
297 };
298
299 AVFilter ff_vf_readeia608 = {
300     .name          = "readeia608",
301     .description   = NULL_IF_CONFIG_SMALL("Read EIA-608 Closed Caption codes from input video and write them to frame metadata."),
302     .priv_size     = sizeof(ReadEIA608Context),
303     .priv_class    = &readeia608_class,
304     .query_formats = query_formats,
305     .inputs        = readeia608_inputs,
306     .outputs       = readeia608_outputs,
307     .uninit        = uninit,
308     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
309 };