2 * Copyright (c) 2017 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
23 * Filter for reading closed captioning data (EIA-608).
24 * See also https://en.wikipedia.org/wiki/EIA-608
29 #include "libavutil/internal.h"
30 #include "libavutil/mem.h"
31 #include "libavutil/opt.h"
32 #include "libavutil/pixdesc.h"
39 #define CLOCK_BITSIZE_MIN 0.2f
40 #define CLOCK_BITSIZE_MAX 1.5f
41 #define SYNC_BITSIZE_MIN 12.f
42 #define SYNC_BITSIZE_MAX 15.f
44 typedef struct LineItem
{
54 typedef struct CodeItem
{
59 typedef struct ScanItem
{
71 typedef struct ReadEIA608Context
{
84 void (*read_line
[2])(AVFrame
*in
, int nb_line
,
85 LineItem
*line
, int lp
, int w
);
88 #define OFFSET(x) offsetof(ReadEIA608Context, x)
89 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
91 static const AVOption readeia608_options
[] = {
92 { "scan_min", "set from which line to scan for codes", OFFSET(start
), AV_OPT_TYPE_INT
, {.i64
=0}, 0, INT_MAX
, FLAGS
},
93 { "scan_max", "set to which line to scan for codes", OFFSET(end
), AV_OPT_TYPE_INT
, {.i64
=29}, 0, INT_MAX
, FLAGS
},
94 { "spw", "set ratio of width reserved for sync code detection", OFFSET(spw
), AV_OPT_TYPE_FLOAT
, {.dbl
=.27}, 0.1, 0.7, FLAGS
},
95 { "chp", "check and apply parity bit", OFFSET(chp
), AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, FLAGS
},
96 { "lp", "lowpass line prior to processing", OFFSET(lp
), AV_OPT_TYPE_BOOL
, {.i64
= 1}, 0, 1, FLAGS
},
100 AVFILTER_DEFINE_CLASS(readeia608
);
102 static const enum AVPixelFormat pixel_fmts
[] = {
103 AV_PIX_FMT_GRAY8
, AV_PIX_FMT_GRAY9
,
104 AV_PIX_FMT_GRAY10
, AV_PIX_FMT_GRAY12
, AV_PIX_FMT_GRAY14
,
106 AV_PIX_FMT_YUV410P
, AV_PIX_FMT_YUV411P
,
107 AV_PIX_FMT_YUV420P
, AV_PIX_FMT_YUV422P
,
108 AV_PIX_FMT_YUV440P
, AV_PIX_FMT_YUV444P
,
109 AV_PIX_FMT_YUVJ420P
, AV_PIX_FMT_YUVJ422P
,
110 AV_PIX_FMT_YUVJ440P
, AV_PIX_FMT_YUVJ444P
,
112 AV_PIX_FMT_YUV420P9
, AV_PIX_FMT_YUV422P9
, AV_PIX_FMT_YUV444P9
,
113 AV_PIX_FMT_YUV420P10
, AV_PIX_FMT_YUV422P10
, AV_PIX_FMT_YUV444P10
,
114 AV_PIX_FMT_YUV440P10
,
115 AV_PIX_FMT_YUV444P12
, AV_PIX_FMT_YUV422P12
, AV_PIX_FMT_YUV420P12
,
116 AV_PIX_FMT_YUV440P12
,
117 AV_PIX_FMT_YUV444P14
, AV_PIX_FMT_YUV422P14
, AV_PIX_FMT_YUV420P14
,
118 AV_PIX_FMT_YUV420P16
, AV_PIX_FMT_YUV422P16
, AV_PIX_FMT_YUV444P16
,
119 AV_PIX_FMT_YUVA420P
, AV_PIX_FMT_YUVA422P
, AV_PIX_FMT_YUVA444P
,
120 AV_PIX_FMT_YUVA444P9
, AV_PIX_FMT_YUVA444P10
, AV_PIX_FMT_YUVA444P12
, AV_PIX_FMT_YUVA444P16
,
121 AV_PIX_FMT_YUVA422P9
, AV_PIX_FMT_YUVA422P10
, AV_PIX_FMT_YUVA422P12
, AV_PIX_FMT_YUVA422P16
,
122 AV_PIX_FMT_YUVA420P9
, AV_PIX_FMT_YUVA420P10
, AV_PIX_FMT_YUVA420P16
,
126 static int config_filter(AVFilterContext
*ctx
, int start
, int end
)
128 ReadEIA608Context
*s
= ctx
->priv
;
129 AVFilterLink
*inlink
= ctx
->inputs
[0];
130 int size
= inlink
->w
+ LAG
;
132 if (end
>= inlink
->h
) {
133 av_log(ctx
, AV_LOG_WARNING
, "Last line to scan too large, clipping.\n");
138 av_log(ctx
, AV_LOG_ERROR
, "Invalid range.\n");
139 return AVERROR(EINVAL
);
142 if (s
->nb_allocated
< end
- start
+ 1) {
143 const int diff
= end
- start
+ 1 - s
->nb_allocated
;
145 s
->scan
= av_realloc_f(s
->scan
, end
- start
+ 1, sizeof(*s
->scan
));
147 return AVERROR(ENOMEM
);
148 memset(&s
->scan
[s
->nb_allocated
], 0, diff
* sizeof(*s
->scan
));
149 s
->nb_allocated
= end
- start
+ 1;
152 for (int i
= 0; i
< s
->nb_allocated
; i
++) {
153 ScanItem
*scan
= &s
->scan
[i
];
155 if (!scan
->histogram
)
156 scan
->histogram
= av_calloc(s
->max
+ 1, sizeof(*scan
->histogram
));
158 scan
->line
= av_calloc(size
, sizeof(*scan
->line
));
160 scan
->code
= av_calloc(size
, sizeof(*scan
->code
));
161 if (!scan
->line
|| !scan
->code
|| !scan
->histogram
)
162 return AVERROR(ENOMEM
);
171 static void build_histogram(ReadEIA608Context
*s
, ScanItem
*scan
, const LineItem
*line
, int len
)
173 memset(scan
->histogram
, 0, (s
->max
+ 1) * sizeof(*scan
->histogram
));
175 for (int i
= LAG
; i
< len
+ LAG
; i
++)
176 scan
->histogram
[line
[i
].input
]++;
179 static void find_black_and_white(ReadEIA608Context
*s
, ScanItem
*scan
)
181 const int max
= s
->max
;
182 int start
= 0, end
= 0, middle
;
183 int black
= 0, white
= 0;
186 for (int i
= 0; i
<= max
; i
++) {
187 if (scan
->histogram
[i
]) {
193 for (int i
= max
; i
>= 0; i
--) {
194 if (scan
->histogram
[i
]) {
200 middle
= start
+ (end
- start
) / 2;
203 for (int i
= start
; i
<= middle
; i
++) {
204 if (scan
->histogram
[i
] > cnt
) {
205 cnt
= scan
->histogram
[i
];
211 for (int i
= end
; i
>= middle
; i
--) {
212 if (scan
->histogram
[i
] > cnt
) {
213 cnt
= scan
->histogram
[i
];
222 static float meanf(const LineItem
*line
, int len
)
224 float sum
= 0.0, mean
= 0.0;
226 for (int i
= 0; i
< len
; i
++)
227 sum
+= line
[i
].filtered
;
234 static float stddevf(const LineItem
*line
, int len
)
236 float m
= meanf(line
, len
);
237 float standard_deviation
= 0.f
;
239 for (int i
= 0; i
< len
; i
++)
240 standard_deviation
+= (line
[i
].filtered
- m
) * (line
[i
].filtered
- m
);
242 return sqrtf(standard_deviation
/ (len
- 1));
245 static void thresholding(ReadEIA608Context
*s
, ScanItem
*scan
, LineItem
*line
,
246 int lag
, float threshold
, float influence
, int len
)
248 for (int i
= lag
; i
< len
+ lag
; i
++) {
249 line
[i
].unfiltered
= line
[i
].input
/ 255.f
;
250 line
[i
].filtered
= line
[i
].unfiltered
;
253 for (int i
= 0; i
< lag
; i
++) {
254 line
[i
].unfiltered
= meanf(line
, len
* s
->spw
);
255 line
[i
].filtered
= line
[i
].unfiltered
;
258 line
[lag
- 1].average
= meanf(line
, lag
);
259 line
[lag
- 1].deviation
= stddevf(line
, lag
);
261 for (int i
= lag
; i
< len
+ lag
; i
++) {
262 if (fabsf(line
[i
].unfiltered
- line
[i
-1].average
) > threshold
* line
[i
-1].deviation
) {
263 if (line
[i
].unfiltered
> line
[i
-1].average
) {
264 line
[i
].output
= 255;
269 line
[i
].filtered
= influence
* line
[i
].unfiltered
+ (1.f
- influence
) * line
[i
-1].filtered
;
271 int distance_from_black
, distance_from_white
;
273 distance_from_black
= FFABS(line
[i
].input
- scan
->black
);
274 distance_from_white
= FFABS(line
[i
].input
- scan
->white
);
276 line
[i
].output
= distance_from_black
<= distance_from_white
? 0 : 255;
279 line
[i
].average
= meanf(line
+ i
- lag
, lag
);
280 line
[i
].deviation
= stddevf(line
+ i
- lag
, lag
);
284 static int periods(const LineItem
*line
, CodeItem
*code
, int len
)
286 int hold
= line
[LAG
].output
, cnt
= 0;
289 memset(code
, 0, len
* sizeof(*code
));
291 for (int i
= LAG
+ 1; i
< len
+ LAG
; i
++) {
292 if (line
[i
].output
!= hold
) {
293 code
[cnt
].size
= i
- last
;
294 code
[cnt
].bit
= hold
;
295 hold
= line
[i
].output
;
301 code
[cnt
].size
= LAG
+ len
- last
;
302 code
[cnt
].bit
= hold
;
307 static void dump_code(AVFilterContext
*ctx
, ScanItem
*scan
, int len
, int item
)
309 av_log(ctx
, AV_LOG_DEBUG
, "%d:", item
);
310 for (int i
= 0; i
< len
; i
++) {
311 av_log(ctx
, AV_LOG_DEBUG
, " %03d", scan
->code
[i
].size
);
313 av_log(ctx
, AV_LOG_DEBUG
, "\n");
316 #define READ_LINE(type, name) \
317 static void read_##name(AVFrame *in, int nb_line, LineItem *line, int lp, int w) \
319 const type *src = (const type *)(&in->data[0][nb_line * in->linesize[0]]);\
322 for (int i = 0; i < w; i++) { \
323 int a = FFMAX(i - 3, 0); \
324 int b = FFMAX(i - 2, 0); \
325 int c = FFMAX(i - 1, 0); \
326 int d = FFMIN(i + 3, w-1); \
327 int e = FFMIN(i + 2, w-1); \
328 int f = FFMIN(i + 1, w-1); \
330 line[LAG + i].input = (src[a] + src[b] + src[c] + src[i] + \
331 src[d] + src[e] + src[f] + 6) / 7; \
334 for (int i = 0; i < w; i++) { \
335 line[LAG + i].input = src[i]; \
340 READ_LINE(uint8_t, byte
)
341 READ_LINE(uint16_t, word
)
343 static int config_input(AVFilterLink
*inlink
)
345 AVFilterContext
*ctx
= inlink
->dst
;
346 ReadEIA608Context
*s
= ctx
->priv
;
347 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
351 s
->depth
= desc
->comp
[0].depth
;
352 s
->max
= (1 << desc
->comp
[0].depth
) - 1;
353 s
->read_line
[0] = read_byte
;
354 s
->read_line
[1] = read_word
;
356 return config_filter(ctx
, s
->start
, s
->end
);
359 static void extract_line(AVFilterContext
*ctx
, AVFrame
*in
, ScanItem
*scan
, int w
, int nb_line
)
361 ReadEIA608Context
*s
= ctx
->priv
;
362 LineItem
*line
= scan
->line
;
364 uint8_t codes
[19] = { 0 };
365 float bit_size
= 0.f
;
368 memset(line
, 0, (w
+ LAG
) * sizeof(*line
));
369 scan
->byte
[0] = scan
->byte
[1] = 0;
372 s
->read_line
[s
->depth
> 8](in
, nb_line
, line
, s
->lp
, w
);
374 build_histogram(s
, scan
, line
, w
);
375 find_black_and_white(s
, scan
);
376 if (scan
->white
- scan
->black
< 5)
379 thresholding(s
, scan
, line
, LAG
, 1, 0, w
);
380 len
= periods(line
, scan
->code
, w
);
381 dump_code(ctx
, scan
, len
, nb_line
);
383 scan
->code
[14].bit
!= 0 ||
384 w
/ (float)scan
->code
[14].size
< SYNC_BITSIZE_MIN
||
385 w
/ (float)scan
->code
[14].size
> SYNC_BITSIZE_MAX
) {
389 for (i
= 14; i
< len
; i
++) {
390 bit_size
+= scan
->code
[i
].size
;
394 for (i
= 1; i
< 14; i
++) {
395 if (scan
->code
[i
].size
/ bit_size
> CLOCK_BITSIZE_MAX
||
396 scan
->code
[i
].size
/ bit_size
< CLOCK_BITSIZE_MIN
) {
401 if (scan
->code
[15].size
/ bit_size
< 0.45f
) {
405 for (j
= 0, i
= 14; i
< len
; i
++) {
408 run
= lrintf(scan
->code
[i
].size
/ bit_size
);
409 bit
= scan
->code
[i
].bit
;
411 for (int k
= 0; j
< 19 && k
< run
; k
++) {
419 for (ch
= 0; ch
< 2; ch
++) {
420 for (parity
= 0, i
= 0; i
< 8; i
++) {
421 int b
= codes
[3 + ch
* 8 + i
];
429 scan
->byte
[ch
] |= b
<< i
;
434 scan
->byte
[ch
] = 0x7F;
439 scan
->nb_line
= nb_line
;
443 static int extract_lines(AVFilterContext
*ctx
, void *arg
,
444 int job
, int nb_jobs
)
446 ReadEIA608Context
*s
= ctx
->priv
;
447 AVFilterLink
*inlink
= ctx
->inputs
[0];
448 const int h
= s
->end
- s
->start
+ 1;
449 const int start
= (h
* job
) / nb_jobs
;
450 const int end
= (h
* (job
+1)) / nb_jobs
;
453 for (int i
= start
; i
< end
; i
++) {
454 ScanItem
*scan
= &s
->scan
[i
];
456 extract_line(ctx
, in
, scan
, inlink
->w
, s
->start
+ i
);
462 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
464 AVFilterContext
*ctx
= inlink
->dst
;
465 AVFilterLink
*outlink
= ctx
->outputs
[0];
466 ReadEIA608Context
*s
= ctx
->priv
;
469 ff_filter_execute(ctx
, extract_lines
, in
, NULL
,
470 FFMIN(FFMAX(s
->end
- s
->start
+ 1, 1), ff_filter_get_nb_threads(ctx
)));
473 for (int i
= 0; i
< s
->end
- s
->start
+ 1; i
++) {
474 ScanItem
*scan
= &s
->scan
[i
];
475 uint8_t key
[128], value
[128];
480 //snprintf(key, sizeof(key), "lavfi.readeia608.%d.bits", nb_found);
481 //snprintf(value, sizeof(value), "0b%d%d%d%d%d%d%d%d 0b%d%d%d%d%d%d%d%d", codes[3]==255,codes[4]==255,codes[5]==255,codes[6]==255,codes[7]==255,codes[8]==255,codes[9]==255,codes[10]==255,codes[11]==255,codes[12]==255,codes[13]==255,codes[14]==255,codes[15]==255,codes[16]==255,codes[17]==255,codes[18]==255);
482 //av_dict_set(&in->metadata, key, value, 0);
484 snprintf(key
, sizeof(key
), "lavfi.readeia608.%d.cc", nb_found
);
485 snprintf(value
, sizeof(value
), "0x%02X%02X", scan
->byte
[0], scan
->byte
[1]);
486 av_dict_set(&in
->metadata
, key
, value
, 0);
488 snprintf(key
, sizeof(key
), "lavfi.readeia608.%d.line", nb_found
);
489 av_dict_set_int(&in
->metadata
, key
, scan
->nb_line
, 0);
494 return ff_filter_frame(outlink
, in
);
497 static av_cold
void uninit(AVFilterContext
*ctx
)
499 ReadEIA608Context
*s
= ctx
->priv
;
501 for (int i
= 0; i
< s
->nb_allocated
; i
++) {
502 ScanItem
*scan
= &s
->scan
[i
];
504 av_freep(&scan
->histogram
);
505 av_freep(&scan
->code
);
506 av_freep(&scan
->line
);
513 static int process_command(AVFilterContext
*ctx
, const char *cmd
, const char *args
,
514 char *res
, int res_len
, int flags
)
516 ReadEIA608Context
*s
= ctx
->priv
;
517 int ret
, start
= s
->start
, end
= s
->end
;
519 ret
= ff_filter_process_command(ctx
, cmd
, args
, res
, res_len
, flags
);
523 ret
= config_filter(ctx
, s
->start
, s
->end
);
532 static const AVFilterPad readeia608_inputs
[] = {
535 .type
= AVMEDIA_TYPE_VIDEO
,
536 .filter_frame
= filter_frame
,
537 .config_props
= config_input
,
541 const FFFilter ff_vf_readeia608
= {
542 .p
.name
= "readeia608",
543 .p
.description
= NULL_IF_CONFIG_SMALL("Read EIA-608 Closed Caption codes from input video and write them to frame metadata."),
544 .p
.priv_class
= &readeia608_class
,
545 .p
.flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
|
546 AVFILTER_FLAG_SLICE_THREADS
|
547 AVFILTER_FLAG_METADATA_ONLY
,
548 .priv_size
= sizeof(ReadEIA608Context
),
549 FILTER_INPUTS(readeia608_inputs
),
550 FILTER_OUTPUTS(ff_video_default_filterpad
),
551 FILTER_PIXFMTS_ARRAY(pixel_fmts
),
553 .process_command
= process_command
,