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);
195 static av_cold int close_decoder(AVCodecContext *avctx)
197 CCaptionSubContext *ctx = avctx->priv_data;
198 av_bprint_finalize( &ctx->buffer, NULL);
199 av_buffer_unref(&ctx->pktbuf);
204 * @param ctx closed caption context just to print log
206 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
208 if(col < SCREEN_COLUMNS) {
212 /* We have extra space at end only for null character */
213 else if ( col == SCREEN_COLUMNS && ch == 0) {
218 av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
219 return AVERROR_INVALIDDATA;
224 * This function after validating parity bit, also remove it from data pair.
225 * The first byte doesn't pass parity, we replace it with a solid blank
226 * and process the pair.
227 * If the second byte doesn't pass parity, it returns INVALIDDATA
228 * user can ignore the whole pair and pass the other pair.
230 static int validate_cc_data_pair (uint8_t *cc_data_pair)
232 uint8_t cc_valid = (*cc_data_pair & 4) >>2;
233 uint8_t cc_type = *cc_data_pair & 3;
236 return AVERROR_INVALIDDATA;
238 // if EIA-608 data then verify parity.
239 if (cc_type==0 || cc_type==1) {
240 if (!parity_table[cc_data_pair[2]]) {
241 return AVERROR_INVALIDDATA;
243 if (!parity_table[cc_data_pair[1]]) {
244 cc_data_pair[1]=0x7F;
249 if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
250 && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
251 return AVERROR_PATCHWELCOME;
254 if(cc_type == 3 || cc_type == 2 )
255 return AVERROR_PATCHWELCOME;
257 /* remove parity bit */
258 cc_data_pair[1] &= 0x7F;
259 cc_data_pair[2] &= 0x7F;
266 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
270 // use Inactive screen
271 return ctx->screen + !ctx->active_screen;
273 case CCMODE_ROLLUP_2:
274 case CCMODE_ROLLUP_3:
275 case CCMODE_ROLLUP_4:
278 return ctx->screen + ctx->active_screen;
280 /* It was never an option */
284 static void roll_up(CCaptionSubContext *ctx)
286 struct Screen *screen;
289 if(ctx->mode == CCMODE_TEXT)
292 screen = get_writing_screen(ctx);
294 /* +1 signify cursor_row starts from 0
295 * Can't keep lines less then row cursor pos
297 keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
299 for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
300 UNSET_FLAG(screen->row_used, i);
303 for( i = 0; i < keep_lines && screen->row_used; i++ ) {
304 const int i_row = ctx->cursor_row - keep_lines + i + 1;
306 memcpy( screen->characters[i_row], screen->characters[i_row+1], SCREEN_COLUMNS );
307 memcpy( screen->colors[i_row], screen->colors[i_row+1], SCREEN_COLUMNS);
308 memcpy( screen->fonts[i_row], screen->fonts[i_row+1], SCREEN_COLUMNS);
309 if(CHECK_FLAG(screen->row_used, i_row + 1))
310 SET_FLAG(screen->row_used, i_row);
313 UNSET_FLAG(screen->row_used, ctx->cursor_row);
317 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
321 struct Screen *screen = ctx->screen + ctx->active_screen;
322 ctx->start_time = ctx->startv_time;
324 for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
326 if(CHECK_FLAG(screen->row_used,i)) {
327 char *str = screen->characters[i];
332 av_bprintf(&ctx->buffer, "%s\\N", str);
333 ret = av_bprint_is_complete(&ctx->buffer);
335 ret = AVERROR(ENOMEM);
341 if(screen->row_used && ctx->buffer.len >= 2 ) {
342 ctx->buffer.len -= 2;
343 ctx->buffer.str[ctx->buffer.len] = 0;
345 ctx->startv_time = pts;
350 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
354 struct Screen *screen = get_writing_screen(ctx);
355 char *row = screen->characters[ctx->cursor_row];
360 ctx->cursor_color = pac2_attribs[i][0];
361 ctx->cursor_font = pac2_attribs[i][1];
363 SET_FLAG(screen->row_used,ctx->cursor_row);
364 ret = write_char(ctx, row, ctx->cursor_column, ' ');
366 ctx->cursor_column++;
369 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
371 static const int8_t row_map[] = {
372 11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
374 const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
375 struct Screen *screen = get_writing_screen(ctx);
379 if( row_map[index] <= 0 ) {
380 av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
386 ctx->cursor_row = row_map[index] - 1;
387 ctx->cursor_color = pac2_attribs[lo][0];
388 ctx->cursor_font = pac2_attribs[lo][1];
389 ctx->cursor_column = 0;
390 indent = pac2_attribs[lo][2];
391 row = screen->characters[ctx->cursor_row];
392 for(i = 0;i < indent; i++) {
393 ret = write_char(ctx, row, ctx->cursor_column, ' ');
395 ctx->cursor_column++;
401 * @param pts it is required to set end time
403 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
406 struct Screen *screen = ctx->screen + ctx->active_screen;
408 reap_screen(ctx, pts);
409 screen->row_used = 0;
410 ctx->screen_changed = 1;
414 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
417 ret = handle_edm(ctx,pts);
418 ctx->active_screen = !ctx->active_screen;
419 ctx->cursor_column = 0;
423 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
425 struct Screen *screen = get_writing_screen(ctx);
426 char *row = screen->characters[ctx->cursor_row];
427 write_char(ctx, row, ctx->cursor_column, 0);
431 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
433 struct Screen *screen = get_writing_screen(ctx);
434 char *row = screen->characters[ctx->cursor_row];
437 SET_FLAG(screen->row_used,ctx->cursor_row);
439 ret = write_char(ctx, row, ctx->cursor_column, hi);
441 ctx->cursor_column++;
444 ret = write_char(ctx, row, ctx->cursor_column, lo);
446 ctx->cursor_column++;
448 write_char(ctx, row, ctx->cursor_column, 0);
450 /* reset prev command since character can repeat */
451 ctx->prev_cmd[0] = 0;
452 ctx->prev_cmd[1] = 0;
454 av_dlog(ctx, "(%c,%c)\n",hi,lo);
456 av_dlog(ctx, "(%c)\n",hi);
459 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
462 #define COR3(var, with1, with2, with3) ( (var) == (with1) || (var) == (with2) || (var) == (with3) )
463 if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
464 /* ignore redundant command */
465 } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
466 ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
467 handle_pac(ctx, hi, lo);
468 } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
469 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
470 handle_textattr(ctx, hi, lo);
471 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
472 /* resume caption loading */
473 ctx->mode = CCMODE_POPON;
474 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
475 handle_delete_end_of_row(ctx, hi, lo);
476 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
478 ctx->mode = CCMODE_ROLLUP_2;
479 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
481 ctx->mode = CCMODE_ROLLUP_3;
482 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
484 ctx->mode = CCMODE_ROLLUP_4;
485 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
486 /* resume direct captioning */
487 ctx->mode = CCMODE_PAINTON;
488 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
489 /* resume text display */
490 ctx->mode = CCMODE_TEXT;
491 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
492 /* erase display memory */
493 ret = handle_edm(ctx, pts);
494 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
495 /* carriage return */
496 av_dlog(ctx, "carriage return\n");
497 reap_screen(ctx, pts);
499 ctx->screen_changed = 1;
500 ctx->cursor_column = 0;
501 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
503 av_dlog(ctx, "handle_eoc\n");
504 ret = handle_eoc(ctx, pts);
505 } else if (hi>=0x20) {
506 /* Standard characters (always in pairs) */
507 handle_char(ctx, hi, lo, pts);
509 /* Ignoring all other non data code */
510 av_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
513 /* set prev command */
514 ctx->prev_cmd[0] = hi;
515 ctx->prev_cmd[1] = lo;
522 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
524 CCaptionSubContext *ctx = avctx->priv_data;
525 AVSubtitle *sub = data;
526 uint8_t *bptr = NULL;
527 int len = avpkt->size;
531 if ( ctx->pktbuf->size < len) {
532 ret = av_buffer_realloc(&ctx->pktbuf, len);
534 av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
535 len = ctx->pktbuf->size;
539 memcpy(ctx->pktbuf->data, avpkt->data, len);
540 bptr = ctx->pktbuf->data;
543 for (i = 0; i < len; i += 3) {
544 uint8_t cc_type = *(bptr + i) & 3;
545 if (validate_cc_data_pair( bptr + i) )
547 /* ignoring data field 1 */
551 process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
552 if(ctx->screen_changed && *ctx->buffer.str)
554 int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
555 int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
556 av_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
557 ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, end_time - start_time);
560 sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
561 ctx->screen_changed = 0;
562 av_bprint_clear(&ctx->buffer);
566 *got_sub = sub->num_rects > 0;
570 static const AVOption options[] = {
574 static const AVClass ccaption_dec_class = {
575 .class_name = "Closed caption Decoder",
576 .item_name = av_default_item_name,
578 .version = LIBAVUTIL_VERSION_INT,
581 AVCodec ff_ccaption_decoder = {
583 .long_name = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
584 .type = AVMEDIA_TYPE_SUBTITLE,
585 .id = AV_CODEC_ID_EIA_608,
586 .priv_data_size = sizeof(CCaptionSubContext),
587 .init = init_decoder,
588 .close = close_decoder,
590 .priv_class = &ccaption_dec_class,