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"
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
66 CCFONT_UNDERLINED_ITALICS,
69 static const unsigned char pac2_attribs[][3] = // Color, font, ident
71 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x40 || 0x60
72 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x41 || 0x61
73 { CCCOL_GREEN, CCFONT_REGULAR, 0 }, // 0x42 || 0x62
74 { CCCOL_GREEN, CCFONT_UNDERLINED, 0 }, // 0x43 || 0x63
75 { CCCOL_BLUE, CCFONT_REGULAR, 0 }, // 0x44 || 0x64
76 { CCCOL_BLUE, CCFONT_UNDERLINED, 0 }, // 0x45 || 0x65
77 { CCCOL_CYAN, CCFONT_REGULAR, 0 }, // 0x46 || 0x66
78 { CCCOL_CYAN, CCFONT_UNDERLINED, 0 }, // 0x47 || 0x67
79 { CCCOL_RED, CCFONT_REGULAR, 0 }, // 0x48 || 0x68
80 { CCCOL_RED, CCFONT_UNDERLINED, 0 }, // 0x49 || 0x69
81 { CCCOL_YELLOW, CCFONT_REGULAR, 0 }, // 0x4a || 0x6a
82 { CCCOL_YELLOW, CCFONT_UNDERLINED, 0 }, // 0x4b || 0x6b
83 { CCCOL_MAGENTA, CCFONT_REGULAR, 0 }, // 0x4c || 0x6c
84 { CCCOL_MAGENTA, CCFONT_UNDERLINED, 0 }, // 0x4d || 0x6d
85 { CCCOL_WHITE, CCFONT_ITALICS, 0 }, // 0x4e || 0x6e
86 { CCCOL_WHITE, CCFONT_UNDERLINED_ITALICS, 0 }, // 0x4f || 0x6f
87 { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x50 || 0x70
88 { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x51 || 0x71
89 { CCCOL_WHITE, CCFONT_REGULAR, 4 }, // 0x52 || 0x72
90 { CCCOL_WHITE, CCFONT_UNDERLINED, 4 }, // 0x53 || 0x73
91 { CCCOL_WHITE, CCFONT_REGULAR, 8 }, // 0x54 || 0x74
92 { CCCOL_WHITE, CCFONT_UNDERLINED, 8 }, // 0x55 || 0x75
93 { CCCOL_WHITE, CCFONT_REGULAR, 12 }, // 0x56 || 0x76
94 { CCCOL_WHITE, CCFONT_UNDERLINED, 12 }, // 0x57 || 0x77
95 { CCCOL_WHITE, CCFONT_REGULAR, 16 }, // 0x58 || 0x78
96 { CCCOL_WHITE, CCFONT_UNDERLINED, 16 }, // 0x59 || 0x79
97 { CCCOL_WHITE, CCFONT_REGULAR, 20 }, // 0x5a || 0x7a
98 { CCCOL_WHITE, CCFONT_UNDERLINED, 20 }, // 0x5b || 0x7b
99 { CCCOL_WHITE, CCFONT_REGULAR, 24 }, // 0x5c || 0x7c
100 { CCCOL_WHITE, CCFONT_UNDERLINED, 24 }, // 0x5d || 0x7d
101 { CCCOL_WHITE, CCFONT_REGULAR, 28 }, // 0x5e || 0x7e
102 { CCCOL_WHITE, CCFONT_UNDERLINED, 28 } // 0x5f || 0x7f
103 /* total 32 entries */
105 /* 0-255 needs 256 spaces */
106 static const uint8_t parity_table[256] = { 0, 1, 1, 0, 1, 0, 0, 1,
107 1, 0, 0, 1, 0, 1, 1, 0,
108 1, 0, 0, 1, 0, 1, 1, 0,
109 0, 1, 1, 0, 1, 0, 0, 1,
110 1, 0, 0, 1, 0, 1, 1, 0,
111 0, 1, 1, 0, 1, 0, 0, 1,
112 0, 1, 1, 0, 1, 0, 0, 1,
113 1, 0, 0, 1, 0, 1, 1, 0,
114 1, 0, 0, 1, 0, 1, 1, 0,
115 0, 1, 1, 0, 1, 0, 0, 1,
116 0, 1, 1, 0, 1, 0, 0, 1,
117 1, 0, 0, 1, 0, 1, 1, 0,
118 0, 1, 1, 0, 1, 0, 0, 1,
119 1, 0, 0, 1, 0, 1, 1, 0,
120 1, 0, 0, 1, 0, 1, 1, 0,
121 0, 1, 1, 0, 1, 0, 0, 1,
122 1, 0, 0, 1, 0, 1, 1, 0,
123 0, 1, 1, 0, 1, 0, 0, 1,
124 0, 1, 1, 0, 1, 0, 0, 1,
125 1, 0, 0, 1, 0, 1, 1, 0,
126 0, 1, 1, 0, 1, 0, 0, 1,
127 1, 0, 0, 1, 0, 1, 1, 0,
128 1, 0, 0, 1, 0, 1, 1, 0,
129 0, 1, 1, 0, 1, 0, 0, 1,
130 0, 1, 1, 0, 1, 0, 0, 1,
131 1, 0, 0, 1, 0, 1, 1, 0,
132 1, 0, 0, 1, 0, 1, 1, 0,
133 0, 1, 1, 0, 1, 0, 0, 1,
134 1, 0, 0, 1, 0, 1, 1, 0,
135 0, 1, 1, 0, 1, 0, 0, 1,
136 0, 1, 1, 0, 1, 0, 0, 1,
137 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];
142 * Bitmask of used rows; if a bit is not set, the
143 * corresponding row is not used.
144 * for setting row 1 use row | (1 << 0)
145 * for setting row 15 use row | (1 << 14)
151 typedef struct CCaptionSubContext {
154 struct Screen screen[2];
157 uint8_t cursor_column;
158 uint8_t cursor_color;
161 int erase_display_memory;
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 */
182 ret = ff_ass_subtitle_header_default(avctx);
183 /* allocate pkt buffer */
184 ctx->pktbuf = av_buffer_alloc(128);
186 ret = AVERROR(ENOMEM);
193 static av_cold int close_decoder(AVCodecContext *avctx)
195 CCaptionSubContext *ctx = avctx->priv_data;
196 av_bprint_finalize( &ctx->buffer, NULL);
197 av_buffer_unref(&ctx->pktbuf);
201 * @param ctx closed caption context just to print log
203 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
205 if(col < SCREEN_COLUMNS) {
209 /* We have extra space at end only for null character */
210 else if ( col == SCREEN_COLUMNS && ch == 0) {
215 av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
216 return AVERROR_INVALIDDATA;
220 * This function after validating parity bit, also remove it from data pair.
221 * The first byte doesn't pass parity, we replace it with a solid blank
222 * and process the pair.
223 * If the second byte doesn't pass parity, it returns INVALIDDATA
224 * user can ignore the whole pair and pass the other pair.
226 static int validate_cc_data_pair (uint8_t *cc_data_pair)
228 uint8_t cc_valid = (*cc_data_pair & 4) >>2;
229 uint8_t cc_type = *cc_data_pair & 3;
232 return AVERROR_INVALIDDATA;
234 // if EIA-608 data then verify parity.
235 if (cc_type==0 || cc_type==1) {
236 if (!parity_table[cc_data_pair[2]]) {
237 return AVERROR_INVALIDDATA;
239 if (!parity_table[cc_data_pair[1]]) {
240 cc_data_pair[1]=0x7F;
245 if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
246 && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
247 return AVERROR_PATCHWELCOME;
250 if(cc_type == 3 || cc_type == 2 )
251 return AVERROR_PATCHWELCOME;
253 /* remove parity bit */
254 cc_data_pair[1] &= 0x7F;
255 cc_data_pair[2] &= 0x7F;
262 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
266 // use Inactive screen
267 return ctx->screen + !ctx->active_screen;
269 case CCMODE_ROLLUP_2:
270 case CCMODE_ROLLUP_3:
271 case CCMODE_ROLLUP_4:
274 return ctx->screen + ctx->active_screen;
276 /* It was never an option */
280 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
284 struct Screen *screen = get_writing_screen(ctx);
285 char *row = screen->characters[ctx->cursor_row];
290 ctx->cursor_color = pac2_attribs[i][0];
291 ctx->cursor_font = pac2_attribs[i][1];
293 SET_FLAG(screen->row_used,ctx->cursor_row);
294 ret = write_char(ctx, row, ctx->cursor_column, ' ');
296 ctx->cursor_column++;
298 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
300 static const int8_t row_map[] = {
301 11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
303 const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
304 struct Screen *screen = get_writing_screen(ctx);
308 if( row_map[index] <= 0 )
313 ctx->cursor_row = row_map[index] - 1;
314 ctx->cursor_color = pac2_attribs[lo][0];
315 ctx->cursor_font = pac2_attribs[lo][1];
316 ctx->cursor_column = 0;
317 indent = pac2_attribs[lo][2];
318 row = screen->characters[ctx->cursor_row];
319 for(i = 0;i < indent; i++) {
320 ret = write_char(ctx, row, ctx->cursor_column, ' ');
322 ctx->cursor_column++;
328 * @param pts it is required to set end time
330 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
334 struct Screen *screen = ctx->screen + ctx->active_screen;
336 ctx->start_time = ctx->startv_time;
337 for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
339 if(CHECK_FLAG(screen->row_used,i)) {
340 char *str = screen->characters[i];
344 av_bprint_append_data(&ctx->buffer, str, strlen(str));
345 av_bprint_append_data(&ctx->buffer, "\\N",2);
346 UNSET_FLAG(screen->row_used, i);
347 ret = av_bprint_is_complete(&ctx->buffer);
349 ret = AVERROR(ENOMEM);
355 ctx->startv_time = pts;
356 ctx->erase_display_memory = 1;
360 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
363 ret = handle_edm(ctx,pts);
364 ctx->active_screen = !ctx->active_screen;
365 ctx->cursor_column = 0;
368 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
370 struct Screen *screen = get_writing_screen(ctx);
371 char *row = screen->characters[ctx->cursor_row];
372 write_char(ctx, row, ctx->cursor_column, 0);
375 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
377 struct Screen *screen = get_writing_screen(ctx);
378 char *row = screen->characters[ctx->cursor_row];
381 SET_FLAG(screen->row_used,ctx->cursor_row);
383 ret = write_char(ctx, row, ctx->cursor_column, hi);
385 ctx->cursor_column++;
388 ret = write_char(ctx, row, ctx->cursor_column, lo);
390 ctx->cursor_column++;
392 write_char(ctx, row, ctx->cursor_column, 0);
394 /* reset prev command since character can repeat */
395 ctx->prev_cmd[0] = 0;
396 ctx->prev_cmd[1] = 0;
398 av_log(ctx, AV_LOG_DEBUG,"(%c,%c)\n",hi,lo);
401 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
404 #define COR3(var, with1, with2, with3) ( (var) == (with1) || (var) == (with2) || (var) == (with3) )
405 if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
406 /* ignore redundant command */
407 } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
408 ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
409 handle_pac(ctx, hi, lo);
410 } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
411 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
412 handle_textattr(ctx, hi, lo);
413 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
414 /* resume caption loading */
415 ctx->mode = CCMODE_POPON;
416 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
417 handle_delete_end_of_row(ctx, hi, lo);
418 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
420 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
422 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
424 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
425 /* resume direct captioning */
426 ctx->mode = CCMODE_PAINTON;
427 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
428 /* resume text display */
429 ctx->mode = CCMODE_TEXT;
430 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
431 /* erase display memory */
432 ret = handle_edm(ctx, pts);
433 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
434 /* carriage return */
436 if(ctx->row_cnt >= ctx->rollup) {
438 ret = handle_edm(ctx, pts);
439 ctx->active_screen = !ctx->active_screen;
441 ctx->cursor_column = 0;
442 } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
444 ret = handle_eoc(ctx, pts);
445 } else if (hi>=0x20) {
446 /* Standard characters (always in pairs) */
447 handle_char(ctx, hi, lo, pts);
449 /* Ignoring all other non data code */
452 /* set prev command */
453 ctx->prev_cmd[0] = hi;
454 ctx->prev_cmd[1] = lo;
460 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
462 CCaptionSubContext *ctx = avctx->priv_data;
463 AVSubtitle *sub = data;
464 uint8_t *bptr = NULL;
465 int len = avpkt->size;
469 if ( ctx->pktbuf->size < len) {
470 ret = av_buffer_realloc(&ctx->pktbuf, len);
472 av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
473 len = ctx->pktbuf->size;
477 memcpy(ctx->pktbuf->data, avpkt->data, len);
478 bptr = ctx->pktbuf->data;
481 for (i = 0; i < len; i += 3) {
482 uint8_t cc_type = *(bptr + i) & 3;
483 if (validate_cc_data_pair( bptr + i) )
485 /* ignoring data field 1 */
489 process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
490 if(ctx->erase_display_memory && *ctx->buffer.str)
492 int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
493 int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
495 av_log(ctx, AV_LOG_DEBUG,"cdp writing data (%s)\n",ctx->buffer.str);
497 ret = ff_ass_add_rect(sub, ctx->buffer.str, start_time, end_time - start_time , 0);
500 sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
501 ctx->erase_display_memory = 0;
502 av_bprint_clear(&ctx->buffer);
506 *got_sub = sub->num_rects > 0;
509 static const AVOption options[] = {
512 static const AVClass ccaption_dec_class = {
513 .class_name = "Closed caption Decoder",
514 .item_name = av_default_item_name,
516 .version = LIBAVUTIL_VERSION_INT,
519 AVCodec ff_ccaption_decoder = {
521 .long_name = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
522 .type = AVMEDIA_TYPE_SUBTITLE,
523 .id = AV_CODEC_ID_EIA_608,
524 .priv_data_size = sizeof(CCaptionSubContext),
525 .init = init_decoder,
526 .close = close_decoder,
528 .priv_class = &ccaption_dec_class,