2 * Closed Caption Decoding
3 * Copyright (c) 2015 Anshul Maheshwari
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "libavutil/internal.h"
25 #include "libavutil/opt.h"
27 #define SCREEN_ROWS 15
28 #define SCREEN_COLUMNS 32
30 #define SET_FLAG(var, val) ( (var) |= ( 1 << (val)) )
31 #define UNSET_FLAG(var, val) ( (var) &= ~( 1 << (val)) )
32 #define CHECK_FLAG(var, val) ( (var) & ( 1 << (val)) )
36 * 1) handle font and color completely
64 CCFONT_UNDERLINED_ITALICS,
67 static const unsigned char pac2_attribs[32][3] = // Color, font, ident
69 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x40 || 0x60
70 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x41 || 0x61
71 { CCCOL_GREEN, CCFONT_REGULAR, 0 }, // 0x42 || 0x62
72 { CCCOL_GREEN, CCFONT_UNDERLINED, 0 }, // 0x43 || 0x63
73 { CCCOL_BLUE, CCFONT_REGULAR, 0 }, // 0x44 || 0x64
74 { CCCOL_BLUE, CCFONT_UNDERLINED, 0 }, // 0x45 || 0x65
75 { CCCOL_CYAN, CCFONT_REGULAR, 0 }, // 0x46 || 0x66
76 { CCCOL_CYAN, CCFONT_UNDERLINED, 0 }, // 0x47 || 0x67
77 { CCCOL_RED, CCFONT_REGULAR, 0 }, // 0x48 || 0x68
78 { CCCOL_RED, CCFONT_UNDERLINED, 0 }, // 0x49 || 0x69
79 { CCCOL_YELLOW, CCFONT_REGULAR, 0 }, // 0x4a || 0x6a
80 { CCCOL_YELLOW, CCFONT_UNDERLINED, 0 }, // 0x4b || 0x6b
81 { CCCOL_MAGENTA, CCFONT_REGULAR, 0 }, // 0x4c || 0x6c
82 { CCCOL_MAGENTA, CCFONT_UNDERLINED, 0 }, // 0x4d || 0x6d
83 { CCCOL_WHITE, CCFONT_ITALICS, 0 }, // 0x4e || 0x6e
84 { CCCOL_WHITE, CCFONT_UNDERLINED_ITALICS, 0 }, // 0x4f || 0x6f
85 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x50 || 0x70
86 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x51 || 0x71
87 { CCCOL_WHITE, CCFONT_REGULAR, 4 }, // 0x52 || 0x72
88 { CCCOL_WHITE, CCFONT_UNDERLINED, 4 }, // 0x53 || 0x73
89 { CCCOL_WHITE, CCFONT_REGULAR, 8 }, // 0x54 || 0x74
90 { CCCOL_WHITE, CCFONT_UNDERLINED, 8 }, // 0x55 || 0x75
91 { CCCOL_WHITE, CCFONT_REGULAR, 12 }, // 0x56 || 0x76
92 { CCCOL_WHITE, CCFONT_UNDERLINED, 12 }, // 0x57 || 0x77
93 { CCCOL_WHITE, CCFONT_REGULAR, 16 }, // 0x58 || 0x78
94 { CCCOL_WHITE, CCFONT_UNDERLINED, 16 }, // 0x59 || 0x79
95 { CCCOL_WHITE, CCFONT_REGULAR, 20 }, // 0x5a || 0x7a
96 { CCCOL_WHITE, CCFONT_UNDERLINED, 20 }, // 0x5b || 0x7b
97 { CCCOL_WHITE, CCFONT_REGULAR, 24 }, // 0x5c || 0x7c
98 { CCCOL_WHITE, CCFONT_UNDERLINED, 24 }, // 0x5d || 0x7d
99 { CCCOL_WHITE, CCFONT_REGULAR, 28 }, // 0x5e || 0x7e
100 { CCCOL_WHITE, CCFONT_UNDERLINED, 28 } // 0x5f || 0x7f
101 /* total 32 entries */
104 /* 0-255 needs 256 spaces */
105 static const uint8_t parity_table[256] = { 0, 1, 1, 0, 1, 0, 0, 1,
106 1, 0, 0, 1, 0, 1, 1, 0,
107 1, 0, 0, 1, 0, 1, 1, 0,
108 0, 1, 1, 0, 1, 0, 0, 1,
109 1, 0, 0, 1, 0, 1, 1, 0,
110 0, 1, 1, 0, 1, 0, 0, 1,
111 0, 1, 1, 0, 1, 0, 0, 1,
112 1, 0, 0, 1, 0, 1, 1, 0,
113 1, 0, 0, 1, 0, 1, 1, 0,
114 0, 1, 1, 0, 1, 0, 0, 1,
115 0, 1, 1, 0, 1, 0, 0, 1,
116 1, 0, 0, 1, 0, 1, 1, 0,
117 0, 1, 1, 0, 1, 0, 0, 1,
118 1, 0, 0, 1, 0, 1, 1, 0,
119 1, 0, 0, 1, 0, 1, 1, 0,
120 0, 1, 1, 0, 1, 0, 0, 1,
121 1, 0, 0, 1, 0, 1, 1, 0,
122 0, 1, 1, 0, 1, 0, 0, 1,
123 0, 1, 1, 0, 1, 0, 0, 1,
124 1, 0, 0, 1, 0, 1, 1, 0,
125 0, 1, 1, 0, 1, 0, 0, 1,
126 1, 0, 0, 1, 0, 1, 1, 0,
127 1, 0, 0, 1, 0, 1, 1, 0,
128 0, 1, 1, 0, 1, 0, 0, 1,
129 0, 1, 1, 0, 1, 0, 0, 1,
130 1, 0, 0, 1, 0, 1, 1, 0,
131 1, 0, 0, 1, 0, 1, 1, 0,
132 0, 1, 1, 0, 1, 0, 0, 1,
133 1, 0, 0, 1, 0, 1, 1, 0,
134 0, 1, 1, 0, 1, 0, 0, 1,
135 0, 1, 1, 0, 1, 0, 0, 1,
136 1, 0, 0, 1, 0, 1, 1, 0 };
139 /* +1 is used to compensate null character of string */
140 uint8_t characters[SCREEN_ROWS][SCREEN_COLUMNS+1];
141 uint8_t colors[SCREEN_ROWS][SCREEN_COLUMNS+1];
142 uint8_t fonts[SCREEN_ROWS][SCREEN_COLUMNS+1];
144 * Bitmask of used rows; if a bit is not set, the
145 * corresponding row is not used.
146 * for setting row 1 use row | (1 << 0)
147 * for setting row 15 use row | (1 << 14)
153 typedef struct CCaptionSubContext {
155 struct Screen screen[2];
158 uint8_t cursor_column;
159 uint8_t cursor_color;
166 /* visible screen time */
170 /* buffer to store pkt data */
175 static av_cold int init_decoder(AVCodecContext *avctx)
178 CCaptionSubContext *ctx = avctx->priv_data;
180 av_bprint_init(&ctx->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
181 /* taking by default roll up to 2 */
182 ctx->mode = CCMODE_ROLLUP_2;
184 ret = ff_ass_subtitle_header_default(avctx);
188 /* allocate pkt buffer */
189 ctx->pktbuf = av_buffer_alloc(128);
191 ret = AVERROR(ENOMEM);
196 static av_cold int close_decoder(AVCodecContext *avctx)
198 CCaptionSubContext *ctx = avctx->priv_data;
199 av_bprint_finalize( &ctx->buffer, NULL);
200 av_buffer_unref(&ctx->pktbuf);
205 * @param ctx closed caption context just to print log
207 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
209 if(col < SCREEN_COLUMNS) {
213 /* We have extra space at end only for null character */
214 else if ( col == SCREEN_COLUMNS && ch == 0) {
219 av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
220 return AVERROR_INVALIDDATA;
225 * This function after validating parity bit, also remove it from data pair.
226 * The first byte doesn't pass parity, we replace it with a solid blank
227 * and process the pair.
228 * If the second byte doesn't pass parity, it returns INVALIDDATA
229 * user can ignore the whole pair and pass the other pair.
231 static int validate_cc_data_pair (uint8_t *cc_data_pair)
233 uint8_t cc_valid = (*cc_data_pair & 4) >>2;
234 uint8_t cc_type = *cc_data_pair & 3;
237 return AVERROR_INVALIDDATA;
239 // if EIA-608 data then verify parity.
240 if (cc_type==0 || cc_type==1) {
241 if (!parity_table[cc_data_pair[2]]) {
242 return AVERROR_INVALIDDATA;
244 if (!parity_table[cc_data_pair[1]]) {
245 cc_data_pair[1]=0x7F;
250 if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
251 && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
252 return AVERROR_PATCHWELCOME;
255 if(cc_type == 3 || cc_type == 2 )
256 return AVERROR_PATCHWELCOME;
258 /* remove parity bit */
259 cc_data_pair[1] &= 0x7F;
260 cc_data_pair[2] &= 0x7F;
267 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
271 // use Inactive screen
272 return ctx->screen + !ctx->active_screen;
274 case CCMODE_ROLLUP_2:
275 case CCMODE_ROLLUP_3:
276 case CCMODE_ROLLUP_4:
279 return ctx->screen + ctx->active_screen;
281 /* It was never an option */
285 static void roll_up(CCaptionSubContext *ctx)
287 struct Screen *screen;
290 if(ctx->mode == CCMODE_TEXT)
293 screen = get_writing_screen(ctx);
295 /* +1 signify cursor_row starts from 0
296 * Can't keep lines less then row cursor pos
298 keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
300 for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
301 UNSET_FLAG(screen->row_used, i);
304 for( i = 0; i < keep_lines && screen->row_used; i++ ) {
305 const int i_row = ctx->cursor_row - keep_lines + i + 1;
307 memcpy( screen->characters[i_row], screen->characters[i_row+1], SCREEN_COLUMNS );
308 memcpy( screen->colors[i_row], screen->colors[i_row+1], SCREEN_COLUMNS);
309 memcpy( screen->fonts[i_row], screen->fonts[i_row+1], SCREEN_COLUMNS);
310 if(CHECK_FLAG(screen->row_used, i_row + 1))
311 SET_FLAG(screen->row_used, i_row);
314 UNSET_FLAG(screen->row_used, ctx->cursor_row);
318 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
322 struct Screen *screen = ctx->screen + ctx->active_screen;
323 ctx->start_time = ctx->startv_time;
325 for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
327 if(CHECK_FLAG(screen->row_used,i)) {
328 char *str = screen->characters[i];
333 av_bprintf(&ctx->buffer, "%s\\N", str);
334 ret = av_bprint_is_complete(&ctx->buffer);
336 ret = AVERROR(ENOMEM);
342 if(screen->row_used && ctx->buffer.len >= 2 ) {
343 ctx->buffer.len -= 2;
344 ctx->buffer.str[ctx->buffer.len] = 0;
346 ctx->startv_time = pts;
351 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
355 struct Screen *screen = get_writing_screen(ctx);
356 char *row = screen->characters[ctx->cursor_row];
361 ctx->cursor_color = pac2_attribs[i][0];
362 ctx->cursor_font = pac2_attribs[i][1];
364 SET_FLAG(screen->row_used,ctx->cursor_row);
365 ret = write_char(ctx, row, ctx->cursor_column, ' ');
367 ctx->cursor_column++;
370 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
372 static const int8_t row_map[] = {
373 11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
375 const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
376 struct Screen *screen = get_writing_screen(ctx);
380 if( row_map[index] <= 0 ) {
381 av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
387 ctx->cursor_row = row_map[index] - 1;
388 ctx->cursor_color = pac2_attribs[lo][0];
389 ctx->cursor_font = pac2_attribs[lo][1];
390 ctx->cursor_column = 0;
391 indent = pac2_attribs[lo][2];
392 row = screen->characters[ctx->cursor_row];
393 for(i = 0;i < indent; i++) {
394 ret = write_char(ctx, row, ctx->cursor_column, ' ');
396 ctx->cursor_column++;
402 * @param pts it is required to set end time
404 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
407 struct Screen *screen = ctx->screen + ctx->active_screen;
409 reap_screen(ctx, pts);
410 screen->row_used = 0;
411 ctx->screen_changed = 1;
415 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
418 ret = handle_edm(ctx,pts);
419 ctx->active_screen = !ctx->active_screen;
420 ctx->cursor_column = 0;
424 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
426 struct Screen *screen = get_writing_screen(ctx);
427 char *row = screen->characters[ctx->cursor_row];
428 write_char(ctx, row, ctx->cursor_column, 0);
432 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
434 struct Screen *screen = get_writing_screen(ctx);
435 char *row = screen->characters[ctx->cursor_row];
438 SET_FLAG(screen->row_used,ctx->cursor_row);
440 ret = write_char(ctx, row, ctx->cursor_column, hi);
442 ctx->cursor_column++;
445 ret = write_char(ctx, row, ctx->cursor_column, lo);
447 ctx->cursor_column++;
449 write_char(ctx, row, ctx->cursor_column, 0);
451 /* reset prev command since character can repeat */
452 ctx->prev_cmd[0] = 0;
453 ctx->prev_cmd[1] = 0;
455 ff_dlog(ctx, "(%c,%c)\n",hi,lo);
457 ff_dlog(ctx, "(%c)\n",hi);
460 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
463 #define COR3(var, with1, with2, with3) ( (var) == (with1) || (var) == (with2) || (var) == (with3) )
464 if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
465 /* ignore redundant command */
466 } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
467 ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
468 handle_pac(ctx, hi, lo);
469 } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
470 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
471 handle_textattr(ctx, hi, lo);
472 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
473 /* resume caption loading */
474 ctx->mode = CCMODE_POPON;
475 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
476 handle_delete_end_of_row(ctx, hi, lo);
477 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
479 ctx->mode = CCMODE_ROLLUP_2;
480 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
482 ctx->mode = CCMODE_ROLLUP_3;
483 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
485 ctx->mode = CCMODE_ROLLUP_4;
486 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
487 /* resume direct captioning */
488 ctx->mode = CCMODE_PAINTON;
489 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
490 /* resume text display */
491 ctx->mode = CCMODE_TEXT;
492 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
493 /* erase display memory */
494 ret = handle_edm(ctx, pts);
495 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
496 /* carriage return */
497 ff_dlog(ctx, "carriage return\n");
498 reap_screen(ctx, pts);
500 ctx->screen_changed = 1;
501 ctx->cursor_column = 0;
502 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
504 ff_dlog(ctx, "handle_eoc\n");
505 ret = handle_eoc(ctx, pts);
506 } else if (hi>=0x20) {
507 /* Standard characters (always in pairs) */
508 handle_char(ctx, hi, lo, pts);
510 /* Ignoring all other non data code */
511 ff_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
514 /* set prev command */
515 ctx->prev_cmd[0] = hi;
516 ctx->prev_cmd[1] = lo;
523 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
525 CCaptionSubContext *ctx = avctx->priv_data;
526 AVSubtitle *sub = data;
527 uint8_t *bptr = NULL;
528 int len = avpkt->size;
532 if ( ctx->pktbuf->size < len) {
533 ret = av_buffer_realloc(&ctx->pktbuf, len);
535 av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
536 len = ctx->pktbuf->size;
540 memcpy(ctx->pktbuf->data, avpkt->data, len);
541 bptr = ctx->pktbuf->data;
544 for (i = 0; i < len; i += 3) {
545 uint8_t cc_type = *(bptr + i) & 3;
546 if (validate_cc_data_pair( bptr + i) )
548 /* ignoring data field 1 */
552 process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
553 if(ctx->screen_changed && *ctx->buffer.str)
555 int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
556 int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
557 ff_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
558 ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, end_time - start_time);
561 sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
562 ctx->screen_changed = 0;
563 av_bprint_clear(&ctx->buffer);
567 *got_sub = sub->num_rects > 0;
571 static const AVOption options[] = {
575 static const AVClass ccaption_dec_class = {
576 .class_name = "Closed caption Decoder",
577 .item_name = av_default_item_name,
579 .version = LIBAVUTIL_VERSION_INT,
582 AVCodec ff_ccaption_decoder = {
584 .long_name = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
585 .type = AVMEDIA_TYPE_SUBTITLE,
586 .id = AV_CODEC_ID_EIA_608,
587 .priv_data_size = sizeof(CCaptionSubContext),
588 .init = init_decoder,
589 .close = close_decoder,
591 .priv_class = &ccaption_dec_class,