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/opt.h"
26 #define SCREEN_ROWS 15
27 #define SCREEN_COLUMNS 32
29 #define SET_FLAG(var, val) ( (var) |= ( 1 << (val)) )
30 #define UNSET_FLAG(var, val) ( (var) &= ~( 1 << (val)) )
31 #define CHECK_FLAG(var, val) ( (var) & ( 1 << (val)) )
35 * 1) handle font and color completely
63 CCFONT_UNDERLINED_ITALICS,
66 static const unsigned char pac2_attribs[32][3] = // Color, font, ident
68 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x40 || 0x60
69 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x41 || 0x61
70 { CCCOL_GREEN, CCFONT_REGULAR, 0 }, // 0x42 || 0x62
71 { CCCOL_GREEN, CCFONT_UNDERLINED, 0 }, // 0x43 || 0x63
72 { CCCOL_BLUE, CCFONT_REGULAR, 0 }, // 0x44 || 0x64
73 { CCCOL_BLUE, CCFONT_UNDERLINED, 0 }, // 0x45 || 0x65
74 { CCCOL_CYAN, CCFONT_REGULAR, 0 }, // 0x46 || 0x66
75 { CCCOL_CYAN, CCFONT_UNDERLINED, 0 }, // 0x47 || 0x67
76 { CCCOL_RED, CCFONT_REGULAR, 0 }, // 0x48 || 0x68
77 { CCCOL_RED, CCFONT_UNDERLINED, 0 }, // 0x49 || 0x69
78 { CCCOL_YELLOW, CCFONT_REGULAR, 0 }, // 0x4a || 0x6a
79 { CCCOL_YELLOW, CCFONT_UNDERLINED, 0 }, // 0x4b || 0x6b
80 { CCCOL_MAGENTA, CCFONT_REGULAR, 0 }, // 0x4c || 0x6c
81 { CCCOL_MAGENTA, CCFONT_UNDERLINED, 0 }, // 0x4d || 0x6d
82 { CCCOL_WHITE, CCFONT_ITALICS, 0 }, // 0x4e || 0x6e
83 { CCCOL_WHITE, CCFONT_UNDERLINED_ITALICS, 0 }, // 0x4f || 0x6f
84 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x50 || 0x70
85 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x51 || 0x71
86 { CCCOL_WHITE, CCFONT_REGULAR, 4 }, // 0x52 || 0x72
87 { CCCOL_WHITE, CCFONT_UNDERLINED, 4 }, // 0x53 || 0x73
88 { CCCOL_WHITE, CCFONT_REGULAR, 8 }, // 0x54 || 0x74
89 { CCCOL_WHITE, CCFONT_UNDERLINED, 8 }, // 0x55 || 0x75
90 { CCCOL_WHITE, CCFONT_REGULAR, 12 }, // 0x56 || 0x76
91 { CCCOL_WHITE, CCFONT_UNDERLINED, 12 }, // 0x57 || 0x77
92 { CCCOL_WHITE, CCFONT_REGULAR, 16 }, // 0x58 || 0x78
93 { CCCOL_WHITE, CCFONT_UNDERLINED, 16 }, // 0x59 || 0x79
94 { CCCOL_WHITE, CCFONT_REGULAR, 20 }, // 0x5a || 0x7a
95 { CCCOL_WHITE, CCFONT_UNDERLINED, 20 }, // 0x5b || 0x7b
96 { CCCOL_WHITE, CCFONT_REGULAR, 24 }, // 0x5c || 0x7c
97 { CCCOL_WHITE, CCFONT_UNDERLINED, 24 }, // 0x5d || 0x7d
98 { CCCOL_WHITE, CCFONT_REGULAR, 28 }, // 0x5e || 0x7e
99 { CCCOL_WHITE, CCFONT_UNDERLINED, 28 } // 0x5f || 0x7f
100 /* total 32 entries */
103 /* 0-255 needs 256 spaces */
104 static const uint8_t parity_table[256] = { 0, 1, 1, 0, 1, 0, 0, 1,
105 1, 0, 0, 1, 0, 1, 1, 0,
106 1, 0, 0, 1, 0, 1, 1, 0,
107 0, 1, 1, 0, 1, 0, 0, 1,
108 1, 0, 0, 1, 0, 1, 1, 0,
109 0, 1, 1, 0, 1, 0, 0, 1,
110 0, 1, 1, 0, 1, 0, 0, 1,
111 1, 0, 0, 1, 0, 1, 1, 0,
112 1, 0, 0, 1, 0, 1, 1, 0,
113 0, 1, 1, 0, 1, 0, 0, 1,
114 0, 1, 1, 0, 1, 0, 0, 1,
115 1, 0, 0, 1, 0, 1, 1, 0,
116 0, 1, 1, 0, 1, 0, 0, 1,
117 1, 0, 0, 1, 0, 1, 1, 0,
118 1, 0, 0, 1, 0, 1, 1, 0,
119 0, 1, 1, 0, 1, 0, 0, 1,
120 1, 0, 0, 1, 0, 1, 1, 0,
121 0, 1, 1, 0, 1, 0, 0, 1,
122 0, 1, 1, 0, 1, 0, 0, 1,
123 1, 0, 0, 1, 0, 1, 1, 0,
124 0, 1, 1, 0, 1, 0, 0, 1,
125 1, 0, 0, 1, 0, 1, 1, 0,
126 1, 0, 0, 1, 0, 1, 1, 0,
127 0, 1, 1, 0, 1, 0, 0, 1,
128 0, 1, 1, 0, 1, 0, 0, 1,
129 1, 0, 0, 1, 0, 1, 1, 0,
130 1, 0, 0, 1, 0, 1, 1, 0,
131 0, 1, 1, 0, 1, 0, 0, 1,
132 1, 0, 0, 1, 0, 1, 1, 0,
133 0, 1, 1, 0, 1, 0, 0, 1,
134 0, 1, 1, 0, 1, 0, 0, 1,
135 1, 0, 0, 1, 0, 1, 1, 0 };
138 /* +1 is used to compensate null character of string */
139 uint8_t characters[SCREEN_ROWS][SCREEN_COLUMNS+1];
140 uint8_t colors[SCREEN_ROWS][SCREEN_COLUMNS+1];
141 uint8_t fonts[SCREEN_ROWS][SCREEN_COLUMNS+1];
143 * Bitmask of used rows; if a bit is not set, the
144 * corresponding row is not used.
145 * for setting row 1 use row | (1 << 0)
146 * for setting row 15 use row | (1 << 14)
152 typedef struct CCaptionSubContext {
154 struct Screen screen[2];
157 uint8_t cursor_column;
158 uint8_t cursor_color;
165 /* visible screen time */
169 /* buffer to store pkt data */
174 static av_cold int init_decoder(AVCodecContext *avctx)
177 CCaptionSubContext *ctx = avctx->priv_data;
179 av_bprint_init(&ctx->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
180 /* taking by default roll up to 2 */
181 ctx->mode = CCMODE_ROLLUP_2;
183 ret = ff_ass_subtitle_header_default(avctx);
187 /* allocate pkt buffer */
188 ctx->pktbuf = av_buffer_alloc(128);
190 ret = AVERROR(ENOMEM);
197 static av_cold int close_decoder(AVCodecContext *avctx)
199 CCaptionSubContext *ctx = avctx->priv_data;
200 av_bprint_finalize( &ctx->buffer, NULL);
201 av_buffer_unref(&ctx->pktbuf);
206 * @param ctx closed caption context just to print log
208 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
210 if(col < SCREEN_COLUMNS) {
214 /* We have extra space at end only for null character */
215 else if ( col == SCREEN_COLUMNS && ch == 0) {
220 av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
221 return AVERROR_INVALIDDATA;
226 * This function after validating parity bit, also remove it from data pair.
227 * The first byte doesn't pass parity, we replace it with a solid blank
228 * and process the pair.
229 * If the second byte doesn't pass parity, it returns INVALIDDATA
230 * user can ignore the whole pair and pass the other pair.
232 static int validate_cc_data_pair (uint8_t *cc_data_pair)
234 uint8_t cc_valid = (*cc_data_pair & 4) >>2;
235 uint8_t cc_type = *cc_data_pair & 3;
238 return AVERROR_INVALIDDATA;
240 // if EIA-608 data then verify parity.
241 if (cc_type==0 || cc_type==1) {
242 if (!parity_table[cc_data_pair[2]]) {
243 return AVERROR_INVALIDDATA;
245 if (!parity_table[cc_data_pair[1]]) {
246 cc_data_pair[1]=0x7F;
251 if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
252 && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
253 return AVERROR_PATCHWELCOME;
256 if(cc_type == 3 || cc_type == 2 )
257 return AVERROR_PATCHWELCOME;
259 /* remove parity bit */
260 cc_data_pair[1] &= 0x7F;
261 cc_data_pair[2] &= 0x7F;
268 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
272 // use Inactive screen
273 return ctx->screen + !ctx->active_screen;
275 case CCMODE_ROLLUP_2:
276 case CCMODE_ROLLUP_3:
277 case CCMODE_ROLLUP_4:
280 return ctx->screen + ctx->active_screen;
282 /* It was never an option */
286 static void roll_up(CCaptionSubContext *ctx)
288 struct Screen *screen;
291 if(ctx->mode == CCMODE_TEXT)
294 screen = get_writing_screen(ctx);
296 /* +1 signify cursor_row starts from 0
297 * Can't keep lines less then row cursor pos
299 keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
301 for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
302 UNSET_FLAG(screen->row_used, i);
305 for( i = 0; i < keep_lines && screen->row_used; i++ ) {
306 const int i_row = ctx->cursor_row - keep_lines + i + 1;
308 memcpy( screen->characters[i_row], screen->characters[i_row+1], SCREEN_COLUMNS );
309 memcpy( screen->colors[i_row], screen->colors[i_row+1], SCREEN_COLUMNS);
310 memcpy( screen->fonts[i_row], screen->fonts[i_row+1], SCREEN_COLUMNS);
311 if(CHECK_FLAG(screen->row_used, i_row + 1))
312 SET_FLAG(screen->row_used, i_row);
315 UNSET_FLAG(screen->row_used, ctx->cursor_row);
319 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
323 struct Screen *screen = ctx->screen + ctx->active_screen;
324 ctx->start_time = ctx->startv_time;
326 for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
328 if(CHECK_FLAG(screen->row_used,i)) {
329 char *str = screen->characters[i];
334 av_bprintf(&ctx->buffer, "%s\\N", str);
335 ret = av_bprint_is_complete(&ctx->buffer);
337 ret = AVERROR(ENOMEM);
343 ctx->startv_time = pts;
348 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
352 struct Screen *screen = get_writing_screen(ctx);
353 char *row = screen->characters[ctx->cursor_row];
358 ctx->cursor_color = pac2_attribs[i][0];
359 ctx->cursor_font = pac2_attribs[i][1];
361 SET_FLAG(screen->row_used,ctx->cursor_row);
362 ret = write_char(ctx, row, ctx->cursor_column, ' ');
364 ctx->cursor_column++;
367 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
369 static const int8_t row_map[] = {
370 11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
372 const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
373 struct Screen *screen = get_writing_screen(ctx);
377 if( row_map[index] <= 0 ) {
378 av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
384 ctx->cursor_row = row_map[index] - 1;
385 ctx->cursor_color = pac2_attribs[lo][0];
386 ctx->cursor_font = pac2_attribs[lo][1];
387 ctx->cursor_column = 0;
388 indent = pac2_attribs[lo][2];
389 row = screen->characters[ctx->cursor_row];
390 for(i = 0;i < indent; i++) {
391 ret = write_char(ctx, row, ctx->cursor_column, ' ');
393 ctx->cursor_column++;
399 * @param pts it is required to set end time
401 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
404 struct Screen *screen = ctx->screen + ctx->active_screen;
406 reap_screen(ctx, pts);
407 screen->row_used = 0;
408 ctx->screen_changed = 1;
412 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
415 ret = handle_edm(ctx,pts);
416 ctx->active_screen = !ctx->active_screen;
417 ctx->cursor_column = 0;
421 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
423 struct Screen *screen = get_writing_screen(ctx);
424 char *row = screen->characters[ctx->cursor_row];
425 write_char(ctx, row, ctx->cursor_column, 0);
429 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
431 struct Screen *screen = get_writing_screen(ctx);
432 char *row = screen->characters[ctx->cursor_row];
435 SET_FLAG(screen->row_used,ctx->cursor_row);
437 ret = write_char(ctx, row, ctx->cursor_column, hi);
439 ctx->cursor_column++;
442 ret = write_char(ctx, row, ctx->cursor_column, lo);
444 ctx->cursor_column++;
446 write_char(ctx, row, ctx->cursor_column, 0);
448 /* reset prev command since character can repeat */
449 ctx->prev_cmd[0] = 0;
450 ctx->prev_cmd[1] = 0;
452 av_dlog(ctx, "(%c,%c)\n",hi,lo);
454 av_dlog(ctx, "(%c)\n",hi);
457 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
460 #define COR3(var, with1, with2, with3) ( (var) == (with1) || (var) == (with2) || (var) == (with3) )
461 if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
462 /* ignore redundant command */
463 } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
464 ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
465 handle_pac(ctx, hi, lo);
466 } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
467 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
468 handle_textattr(ctx, hi, lo);
469 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
470 /* resume caption loading */
471 ctx->mode = CCMODE_POPON;
472 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
473 handle_delete_end_of_row(ctx, hi, lo);
474 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
476 ctx->mode = CCMODE_ROLLUP_2;
477 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
479 ctx->mode = CCMODE_ROLLUP_3;
480 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
482 ctx->mode = CCMODE_ROLLUP_4;
483 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
484 /* resume direct captioning */
485 ctx->mode = CCMODE_PAINTON;
486 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
487 /* resume text display */
488 ctx->mode = CCMODE_TEXT;
489 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
490 /* erase display memory */
491 ret = handle_edm(ctx, pts);
492 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
493 /* carriage return */
494 av_dlog(ctx, "carriage return\n");
495 reap_screen(ctx, pts);
497 ctx->screen_changed = 1;
498 ctx->cursor_column = 0;
499 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
501 av_dlog(ctx, "handle_eoc\n");
502 ret = handle_eoc(ctx, pts);
503 } else if (hi>=0x20) {
504 /* Standard characters (always in pairs) */
505 handle_char(ctx, hi, lo, pts);
507 /* Ignoring all other non data code */
508 av_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
511 /* set prev command */
512 ctx->prev_cmd[0] = hi;
513 ctx->prev_cmd[1] = lo;
520 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
522 CCaptionSubContext *ctx = avctx->priv_data;
523 AVSubtitle *sub = data;
524 uint8_t *bptr = NULL;
525 int len = avpkt->size;
529 if ( ctx->pktbuf->size < len) {
530 ret = av_buffer_realloc(&ctx->pktbuf, len);
532 av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
533 len = ctx->pktbuf->size;
537 memcpy(ctx->pktbuf->data, avpkt->data, len);
538 bptr = ctx->pktbuf->data;
541 for (i = 0; i < len; i += 3) {
542 uint8_t cc_type = *(bptr + i) & 3;
543 if (validate_cc_data_pair( bptr + i) )
545 /* ignoring data field 1 */
549 process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
550 if(ctx->screen_changed && *ctx->buffer.str)
552 int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
553 int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
554 av_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
555 ret = ff_ass_add_rect(sub, ctx->buffer.str, start_time, end_time - start_time , 0);
558 sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
559 ctx->screen_changed = 0;
560 av_bprint_clear(&ctx->buffer);
564 *got_sub = sub->num_rects > 0;
568 static const AVOption options[] = {
572 static const AVClass ccaption_dec_class = {
573 .class_name = "Closed caption Decoder",
574 .item_name = av_default_item_name,
576 .version = LIBAVUTIL_VERSION_INT,
579 AVCodec ff_ccaption_decoder = {
581 .long_name = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
582 .type = AVMEDIA_TYPE_SUBTITLE,
583 .id = AV_CODEC_ID_EIA_608,
584 .priv_data_size = sizeof(CCaptionSubContext),
585 .init = init_decoder,
586 .close = close_decoder,
588 .priv_class = &ccaption_dec_class,