Browse code

avfilter: add EIA-608 line extractor

Signed-off-by: Dave Rice <dave@dericed.com>
Signed-off-by: Paul B Mahol <onemda@gmail.com>

Paul B Mahol authored on 2017/01/15 03:04:54
Showing 6 changed files
... ...
@@ -15,6 +15,7 @@ version <next>:
15 15
 - QDMC audio decoder
16 16
 - NewTek SpeedHQ decoder
17 17
 - MIDI Sample Dump Standard demuxer
18
+- readeia608 filter
18 19
 
19 20
 version 3.2:
20 21
 - libopenmpt demuxer
... ...
@@ -11253,6 +11253,76 @@ less than @code{0}, the filter will try to use a good random seed on a
11253 11253
 best effort basis.
11254 11254
 @end table
11255 11255
 
11256
+@section readeia608
11257
+
11258
+Read closed captioning (EIA-608) information from the top lines of a video frame.
11259
+
11260
+This filter adds frame metadata for @code{lavfi.readeia608.X.cc} and
11261
+@code{lavfi.readeia608.X.line}, where @code{X} is the number of the identified line
11262
+with EIA-608 data (starting from 0). A description of each metadata value follows:
11263
+
11264
+@table @option
11265
+@item lavfi.readeia608.X.cc
11266
+The two bytes stored as EIA-608 data (printed in hexadecimal).
11267
+
11268
+@item lavfi.readeia608.X.line
11269
+The number of the line on which the EIA-608 data was identified and read.
11270
+@end table
11271
+
11272
+This filter accepts the following options:
11273
+
11274
+@table @option
11275
+@item scan_min
11276
+Set the line to start scanning for EIA-608 data. Default is @code{0}.
11277
+
11278
+@item scan_max
11279
+Set the line to end scanning for EIA-608 data. Default is @code{29}.
11280
+
11281
+@item mac
11282
+Set minimal acceptable amplitude change for sync codes detection.
11283
+Default is @code{0.2}. Allowed range is @code{[0.001 - 1]}.
11284
+
11285
+@item spw
11286
+Set the ratio of width reserved for sync code detection.
11287
+Default is @code{0.27}. Allowed range is @code{[0.01 - 0.7]}.
11288
+
11289
+@item mhd
11290
+Set the max peaks height difference for sync code detection.
11291
+Default is @code{0.1}. Allowed range is @code{[0.0 - 0.5]}.
11292
+
11293
+@item mpd
11294
+Set max peaks period difference for sync code detection.
11295
+Default is @code{0.1}. Allowed range is @code{[0.0 - 0.5]}.
11296
+
11297
+@item msd
11298
+Set the first two max start code bits differences.
11299
+Default is @code{0.02}. Allowed range is @code{[0.0 - 0.5]}.
11300
+
11301
+@item bhd
11302
+Set the minimum ratio of bits height compared to 3rd start code bit.
11303
+Default is @code{0.75}. Allowed range is @code{[0.01 - 1]}.
11304
+
11305
+@item th_w
11306
+Set the white color threshold. Default is @code{0.35}. Allowed range is @code{[0.1 - 1]}.
11307
+
11308
+@item th_b
11309
+Set the black color threshold. Default is @code{0.15}. Allowed range is @code{[0.0 - 0.5]}.
11310
+
11311
+@item chp
11312
+Enable checking the parity bit. In the event of a parity error, the filter will output
11313
+@code{0x00} for that character. Default is false.
11314
+@end table
11315
+
11316
+@subsection Examples
11317
+
11318
+@itemize
11319
+@item
11320
+Output a csv with presentation time and the first two lines of identified EIA-608 captioning data.
11321
+@example
11322
+ffprobe -f lavfi -i movie=captioned_video.mov,readeia608 -show_entries frame=pkt_pts_time:frame_tags=lavfi.readeia608.0.cc,lavfi.readeia608.1.cc -of csv
11323
+@end example
11324
+@end itemize
11325
+
11256 11326
 @section readvitc
11257 11327
 
11258 11328
 Read vertical interval timecode (VITC) information from the top lines of a
... ...
@@ -247,6 +247,7 @@ OBJS-$(CONFIG_PSNR_FILTER)                   += vf_psnr.o dualinput.o framesync.
247 247
 OBJS-$(CONFIG_PULLUP_FILTER)                 += vf_pullup.o
248 248
 OBJS-$(CONFIG_QP_FILTER)                     += vf_qp.o
249 249
 OBJS-$(CONFIG_RANDOM_FILTER)                 += vf_random.o
250
+OBJS-$(CONFIG_READEIA608_FILTER)             += vf_readeia608.o
250 251
 OBJS-$(CONFIG_READVITC_FILTER)               += vf_readvitc.o
251 252
 OBJS-$(CONFIG_REALTIME_FILTER)               += f_realtime.o
252 253
 OBJS-$(CONFIG_REMAP_FILTER)                  += vf_remap.o framesync.o
... ...
@@ -262,6 +262,7 @@ void avfilter_register_all(void)
262 262
     REGISTER_FILTER(PULLUP,         pullup,         vf);
263 263
     REGISTER_FILTER(QP,             qp,             vf);
264 264
     REGISTER_FILTER(RANDOM,         random,         vf);
265
+    REGISTER_FILTER(READEIA608,     readeia608,     vf);
265 266
     REGISTER_FILTER(READVITC,       readvitc,       vf);
266 267
     REGISTER_FILTER(REALTIME,       realtime,       vf);
267 268
     REGISTER_FILTER(REMAP,          remap,          vf);
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR   6
33
-#define LIBAVFILTER_VERSION_MINOR  69
33
+#define LIBAVFILTER_VERSION_MINOR  70
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
new file mode 100644
... ...
@@ -0,0 +1,268 @@
0
+/*
1
+ * Copyright (c) 2017 Paul B Mahol
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * Filter for reading closed captioning data (EIA-608).
23
+ * See also https://en.wikipedia.org/wiki/EIA-608
24
+ */
25
+
26
+#include <string.h>
27
+
28
+#include "libavutil/internal.h"
29
+#include "libavutil/opt.h"
30
+#include "libavutil/pixdesc.h"
31
+#include "libavutil/timestamp.h"
32
+
33
+#include "avfilter.h"
34
+#include "formats.h"
35
+#include "internal.h"
36
+#include "video.h"
37
+
38
+#define FALL 0
39
+#define RISE 1
40
+
41
+typedef struct ReadEIA608Context {
42
+    const AVClass *class;
43
+    int start, end;
44
+    int min_range;
45
+    int max_peak_diff;
46
+    int max_period_diff;
47
+    int max_start_diff;
48
+    int nb_found;
49
+    int white;
50
+    int black;
51
+    float mpd, mhd, msd, mac, spw, bhd, wth, bth;
52
+    int chp;
53
+} ReadEIA608Context;
54
+
55
+#define OFFSET(x) offsetof(ReadEIA608Context, x)
56
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
57
+
58
+static const AVOption readeia608_options[] = {
59
+    { "scan_min", "set from which line to scan for codes",                            OFFSET(start), AV_OPT_TYPE_INT,   {.i64=0},       0, INT_MAX, FLAGS },
60
+    { "scan_max", "set to which line to scan for codes",                              OFFSET(end),   AV_OPT_TYPE_INT,   {.i64=29},      0, INT_MAX, FLAGS },
61
+    { "mac",      "set minimal acceptable amplitude change for sync codes detection", OFFSET(mac),   AV_OPT_TYPE_FLOAT, {.dbl=.2},  0.001,       1, FLAGS },
62
+    { "spw",      "set ratio of width reserved for sync code detection",              OFFSET(spw),   AV_OPT_TYPE_FLOAT, {.dbl=.27},   0.1,     0.7, FLAGS },
63
+    { "mhd",      "set max peaks height difference for sync code detection",          OFFSET(mhd),   AV_OPT_TYPE_FLOAT, {.dbl=.1},      0,     0.5, FLAGS },
64
+    { "mpd",      "set max peaks period difference for sync code detection",          OFFSET(mpd),   AV_OPT_TYPE_FLOAT, {.dbl=.1},      0,     0.5, FLAGS },
65
+    { "msd",      "set first two max start code bits differences",                    OFFSET(msd),   AV_OPT_TYPE_FLOAT, {.dbl=.02},     0,     0.5, FLAGS },
66
+    { "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 },
67
+    { "th_w",     "set white color threshold",                                        OFFSET(wth),   AV_OPT_TYPE_FLOAT, {.dbl=.35},   0.1,       1, FLAGS },
68
+    { "th_b",     "set black color threshold",                                        OFFSET(bth),   AV_OPT_TYPE_FLOAT, {.dbl=.15},     0,     0.5, FLAGS },
69
+    { "chp",      "check and apply parity bit",                                       OFFSET(chp),   AV_OPT_TYPE_BOOL,  {.i64= 0},      0,       1, FLAGS },
70
+    { NULL }
71
+};
72
+
73
+AVFILTER_DEFINE_CLASS(readeia608);
74
+
75
+static int query_formats(AVFilterContext *ctx)
76
+{
77
+    static const enum AVPixelFormat pixel_fmts[] = {
78
+        AV_PIX_FMT_GRAY8,
79
+        AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
80
+        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
81
+        AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
82
+        AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P,
83
+        AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P,
84
+        AV_PIX_FMT_YUVJ411P,
85
+        AV_PIX_FMT_NONE
86
+    };
87
+    AVFilterFormats *formats = ff_make_format_list(pixel_fmts);
88
+    if (!formats)
89
+        return AVERROR(ENOMEM);
90
+    return ff_set_common_formats(ctx, formats);
91
+}
92
+
93
+static int config_input(AVFilterLink *inlink)
94
+{
95
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
96
+    AVFilterContext *ctx = inlink->dst;
97
+    ReadEIA608Context *s = ctx->priv;
98
+    int depth = desc->comp[0].depth;
99
+
100
+    if (s->end >= inlink->h) {
101
+        av_log(ctx, AV_LOG_WARNING, "Last line to scan too large, clipping.\n");
102
+        s->end = inlink->h - 1;
103
+    }
104
+
105
+    if (s->start > s->end) {
106
+        av_log(ctx, AV_LOG_ERROR, "Invalid range.\n");
107
+        return AVERROR(EINVAL);
108
+    }
109
+
110
+    s->min_range = s->mac * ((1 << depth) - 1);
111
+    s->max_peak_diff = s->mhd * ((1 << depth) - 1);
112
+    s->max_period_diff = s->mpd * ((1 << depth) - 1);
113
+    s->max_start_diff = s->msd * ((1 << depth) - 1);
114
+    s->white = s->wth * ((1 << depth) - 1);
115
+    s->black = s->bth * ((1 << depth) - 1);
116
+
117
+    return 0;
118
+}
119
+
120
+static void extract_line(AVFilterContext *ctx, AVFilterLink *inlink, AVFrame *in, int line)
121
+{
122
+    ReadEIA608Context *s = ctx->priv;
123
+    int max = 0, min = INT_MAX;
124
+    int i, ch, range = 0;
125
+    const uint8_t *src;
126
+    uint16_t clock[8][2] = { { 0 } };
127
+    const int sync_width = s->spw * in->width;
128
+    int last = 0, peaks = 0, max_peak_diff = 0, dir = RISE;
129
+    const int width_per_bit = (in->width - sync_width) / 19;
130
+    uint8_t byte[2] = { 0 };
131
+    int s1, s2, s3, parity;
132
+
133
+    src = &in->data[0][line * in->linesize[0]];
134
+    for (i = 0; i < sync_width; i++) {
135
+        max = FFMAX(max, src[i]);
136
+        min = FFMIN(min, src[i]);
137
+    }
138
+
139
+    range = max - min;
140
+    if (range < s->min_range)
141
+        return;
142
+
143
+    for (i = 0; i < sync_width; i++) {
144
+        int Y = src[i];
145
+
146
+        if (dir == RISE) {
147
+            if (Y < last) {
148
+                dir = FALL;
149
+                if (last >= s->white) {
150
+                    clock[peaks][0] = last;
151
+                    clock[peaks][1] = i;
152
+                    peaks++;
153
+                    if (peaks > 7)
154
+                        break;
155
+                }
156
+            }
157
+        } else if (dir == FALL) {
158
+            if (Y > last && last <= s->black) {
159
+                dir = RISE;
160
+            }
161
+        }
162
+        last = Y;
163
+    }
164
+
165
+    if (peaks != 7)
166
+        return;
167
+
168
+    for (i = 1; i < 7; i++)
169
+        max_peak_diff = FFMAX(max_peak_diff, FFABS(clock[i][0] - clock[i-1][0]));
170
+
171
+    if (max_peak_diff > s->max_peak_diff)
172
+        return;
173
+
174
+    max = 0; min = INT_MAX;
175
+    for (i = 1; i < 7; i++) {
176
+        max = FFMAX(max, FFABS(clock[i][1] - clock[i-1][1]));
177
+        min = FFMIN(min, FFABS(clock[i][1] - clock[i-1][1]));
178
+    }
179
+
180
+    range = max - min;
181
+    if (range > s->max_period_diff)
182
+        return;
183
+
184
+    s1 = src[sync_width + width_per_bit * 0 + width_per_bit / 2];
185
+    s2 = src[sync_width + width_per_bit * 1 + width_per_bit / 2];
186
+    s3 = src[sync_width + width_per_bit * 2 + width_per_bit / 2];
187
+
188
+    if (FFABS(s1 - s2) > s->max_start_diff || s1 > s->black || s2 > s->black || s3 < s->white)
189
+        return;
190
+
191
+    for (ch = 0; ch < 2; ch++) {
192
+        for (parity = 0, i = 0; i < 8; i++) {
193
+            int b = src[sync_width + width_per_bit * (i + 3 + 8 * ch) + width_per_bit / 2];
194
+
195
+            if (b - s1 > (s3 - s1) * s->bhd) {
196
+                b = 1;
197
+                parity++;
198
+            } else {
199
+                b = 0;
200
+            }
201
+            byte[ch] |= b << i;
202
+        }
203
+
204
+        if (s->chp) {
205
+            if (!(parity & 1)) {
206
+                byte[ch] = 0;
207
+            }
208
+        }
209
+    }
210
+
211
+    {
212
+        uint8_t key[128], value[128];
213
+
214
+        snprintf(key, sizeof(key), "lavfi.readeia608.%d.cc", s->nb_found);
215
+        snprintf(value, sizeof(value), "0x%02X%02X", byte[0], byte[1]);
216
+        av_dict_set(avpriv_frame_get_metadatap(in), key, value, 0);
217
+
218
+        snprintf(key, sizeof(key), "lavfi.readeia608.%d.line", s->nb_found);
219
+        snprintf(value, sizeof(value), "%d", line);
220
+        av_dict_set(avpriv_frame_get_metadatap(in), key, value, 0);
221
+    }
222
+
223
+    s->nb_found++;
224
+}
225
+
226
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
227
+{
228
+    AVFilterContext *ctx  = inlink->dst;
229
+    AVFilterLink *outlink = ctx->outputs[0];
230
+    ReadEIA608Context *s = ctx->priv;
231
+    int i;
232
+
233
+    s->nb_found = 0;
234
+    for (i = s->start; i <= s->end; i++)
235
+        extract_line(ctx, inlink, in, i);
236
+
237
+    return ff_filter_frame(outlink, in);
238
+}
239
+
240
+static const AVFilterPad readeia608_inputs[] = {
241
+    {
242
+        .name         = "default",
243
+        .type         = AVMEDIA_TYPE_VIDEO,
244
+        .filter_frame = filter_frame,
245
+        .config_props = config_input,
246
+    },
247
+    { NULL }
248
+};
249
+
250
+static const AVFilterPad readeia608_outputs[] = {
251
+    {
252
+        .name = "default",
253
+        .type = AVMEDIA_TYPE_VIDEO,
254
+    },
255
+    { NULL }
256
+};
257
+
258
+AVFilter ff_vf_readeia608 = {
259
+    .name          = "readeia608",
260
+    .description   = NULL_IF_CONFIG_SMALL("Read EIA-608 Closed Caption codes from input video and write them to frame metadata."),
261
+    .priv_size     = sizeof(ReadEIA608Context),
262
+    .priv_class    = &readeia608_class,
263
+    .query_formats = query_formats,
264
+    .inputs        = readeia608_inputs,
265
+    .outputs       = readeia608_outputs,
266
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
267
+};