FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ccaption_dec.c
Go to the documentation of this file.
1 /*
2  * Closed Caption Decoding
3  * Copyright (c) 2015 Anshul Maheshwari
4  *
5  * This file is part of FFmpeg.
6  *
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.
11  *
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.
16  *
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
20  */
21 
22 #include "avcodec.h"
23 #include "ass.h"
24 #include "libavutil/opt.h"
25 
26 #define SCREEN_ROWS 15
27 #define SCREEN_COLUMNS 32
28 
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)) )
32 
33 /*
34  * TODO list
35  * 1) handle font and color completely
36  */
37 enum cc_mode {
44 };
45 
57 };
58 
59 enum cc_font {
64 };
65 
66 static const unsigned char pac2_attribs[32][3] = // Color, font, ident
67 {
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 */
101 };
102 
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 };
136 
137 struct Screen {
138  /* +1 is used to compensate null character of string */
142  /*
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)
147  */
148  int16_t row_used;
149 };
150 
151 
152 typedef struct CCaptionSubContext {
153  AVClass *class;
154  struct Screen screen[2];
160  AVBPrint buffer;
162  int rollup;
163  enum cc_mode mode;
164  int64_t start_time;
165  /* visible screen time */
166  int64_t startv_time;
167  int64_t end_time;
168  char prev_cmd[2];
169  /* buffer to store pkt data */
172 
173 
175 {
176  int ret;
177  CCaptionSubContext *ctx = avctx->priv_data;
178 
180  /* taking by default roll up to 2 */
181  ctx->mode = CCMODE_ROLLUP_2;
182  ctx->rollup = 2;
183  ret = ff_ass_subtitle_header_default(avctx);
184  if(ret < 0) {
185  goto fail;
186  }
187  /* allocate pkt buffer */
188  ctx->pktbuf = av_buffer_alloc(128);
189  if( !ctx->pktbuf) {
190  ret = AVERROR(ENOMEM);
191  }
192 
193 fail:
194  return ret;
195 }
196 
198 {
199  CCaptionSubContext *ctx = avctx->priv_data;
200  av_bprint_finalize( &ctx->buffer, NULL);
201  av_buffer_unref(&ctx->pktbuf);
202  return 0;
203 }
204 
205 /**
206  * @param ctx closed caption context just to print log
207  */
208 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
209 {
210  if(col < SCREEN_COLUMNS) {
211  row[col] = ch;
212  return 0;
213  }
214  /* We have extra space at end only for null character */
215  else if ( col == SCREEN_COLUMNS && ch == 0) {
216  row[col] = ch;
217  return 0;
218  }
219  else {
220  av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
221  return AVERROR_INVALIDDATA;
222  }
223 }
224 
225 /**
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.
231  */
232 static int validate_cc_data_pair (uint8_t *cc_data_pair)
233 {
234  uint8_t cc_valid = (*cc_data_pair & 4) >>2;
235  uint8_t cc_type = *cc_data_pair & 3;
236 
237  if (!cc_valid)
238  return AVERROR_INVALIDDATA;
239 
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;
244  }
245  if (!parity_table[cc_data_pair[1]]) {
246  cc_data_pair[1]=0x7F;
247  }
248  }
249 
250  //Skip non-data
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;
254 
255  //skip 708 data
256  if(cc_type == 3 || cc_type == 2 )
257  return AVERROR_PATCHWELCOME;
258 
259  /* remove parity bit */
260  cc_data_pair[1] &= 0x7F;
261  cc_data_pair[2] &= 0x7F;
262 
263 
264  return 0;
265 
266 }
267 
269 {
270  switch (ctx->mode) {
271  case CCMODE_POPON:
272  // use Inactive screen
273  return ctx->screen + !ctx->active_screen;
274  case CCMODE_PAINTON:
275  case CCMODE_ROLLUP_2:
276  case CCMODE_ROLLUP_3:
277  case CCMODE_ROLLUP_4:
278  case CCMODE_TEXT:
279  // use active screen
280  return ctx->screen + ctx->active_screen;
281  }
282  /* It was never an option */
283  return NULL;
284 }
285 
286 static void roll_up(CCaptionSubContext *ctx)
287 {
288  struct Screen *screen;
289  int i, keep_lines;
290 
291  if(ctx->mode == CCMODE_TEXT)
292  return;
293 
294  screen = get_writing_screen(ctx);
295 
296  /* +1 signify cursor_row starts from 0
297  * Can't keep lines less then row cursor pos
298  */
299  keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
300 
301  for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
302  UNSET_FLAG(screen->row_used, i);
303 
304 
305  for( i = 0; i < keep_lines && screen->row_used; i++ ) {
306  const int i_row = ctx->cursor_row - keep_lines + i + 1;
307 
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);
313 
314  }
315  UNSET_FLAG(screen->row_used, ctx->cursor_row);
316 
317 }
318 
319 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
320 {
321  int i;
322  int ret = 0;
323  struct Screen *screen = ctx->screen + ctx->active_screen;
324  ctx->start_time = ctx->startv_time;
325 
326  for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
327  {
328  if(CHECK_FLAG(screen->row_used,i)) {
329  char *str = screen->characters[i];
330  /* skip space */
331  while (*str == ' ')
332  str++;
333 
334  av_bprintf(&ctx->buffer, "%s\\N", str);
335  ret = av_bprint_is_complete(&ctx->buffer);
336  if( ret == 0) {
337  ret = AVERROR(ENOMEM);
338  break;
339  }
340  }
341 
342  }
343  ctx->startv_time = pts;
344  ctx->end_time = pts;
345  return ret;
346 }
347 
349 {
350  int i = lo - 0x20;
351  int ret;
352  struct Screen *screen = get_writing_screen(ctx);
353  char *row = screen->characters[ctx->cursor_row];
354 
355  if( i >= 32)
356  return;
357 
358  ctx->cursor_color = pac2_attribs[i][0];
359  ctx->cursor_font = pac2_attribs[i][1];
360 
361  SET_FLAG(screen->row_used,ctx->cursor_row);
362  ret = write_char(ctx, row, ctx->cursor_column, ' ');
363  if(ret == 0)
364  ctx->cursor_column++;
365 }
366 
367 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
368 {
369  static const int8_t row_map[] = {
370  11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
371  };
372  const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
373  struct Screen *screen = get_writing_screen(ctx);
374  char *row;
375  int indent,i,ret;
376 
377  if( row_map[index] <= 0 ) {
378  av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
379  return;
380  }
381 
382  lo &= 0x1f;
383 
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, ' ');
392  if( ret == 0 )
393  ctx->cursor_column++;
394  }
395 
396 }
397 
398 /**
399  * @param pts it is required to set end time
400  */
401 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
402 {
403  int ret = 0;
404  struct Screen *screen = ctx->screen + ctx->active_screen;
405 
406  reap_screen(ctx, pts);
407  screen->row_used = 0;
408  ctx->screen_changed = 1;
409  return ret;
410 }
411 
412 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
413 {
414  int ret;
415  ret = handle_edm(ctx,pts);
416  ctx->active_screen = !ctx->active_screen;
417  ctx->cursor_column = 0;
418  return ret;
419 }
420 
421 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
422 {
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);
426 
427 }
428 
429 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
430 {
431  struct Screen *screen = get_writing_screen(ctx);
432  char *row = screen->characters[ctx->cursor_row];
433  int ret;
434 
435  SET_FLAG(screen->row_used,ctx->cursor_row);
436 
437  ret = write_char(ctx, row, ctx->cursor_column, hi);
438  if( ret == 0 )
439  ctx->cursor_column++;
440 
441  if(lo) {
442  ret = write_char(ctx, row, ctx->cursor_column, lo);
443  if ( ret == 0 )
444  ctx->cursor_column++;
445  }
446  write_char(ctx, row, ctx->cursor_column, 0);
447 
448  /* reset prev command since character can repeat */
449  ctx->prev_cmd[0] = 0;
450  ctx->prev_cmd[1] = 0;
451  if (lo)
452  av_dlog(ctx, "(%c,%c)\n",hi,lo);
453  else
454  av_dlog(ctx, "(%c)\n",hi);
455 }
456 
457 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
458 {
459  int ret = 0;
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 ) {
475  ctx->rollup = 2;
476  ctx->mode = CCMODE_ROLLUP_2;
477  } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
478  ctx->rollup = 3;
479  ctx->mode = CCMODE_ROLLUP_3;
480  } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
481  ctx->rollup = 4;
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);
496  roll_up(ctx);
497  ctx->screen_changed = 1;
498  ctx->cursor_column = 0;
499  } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
500  /* end of caption */
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);
506  } else {
507  /* Ignoring all other non data code */
508  av_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
509  }
510 
511  /* set prev command */
512  ctx->prev_cmd[0] = hi;
513  ctx->prev_cmd[1] = lo;
514 
515 #undef COR3
516  return ret;
517 
518 }
519 
520 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
521 {
522  CCaptionSubContext *ctx = avctx->priv_data;
523  AVSubtitle *sub = data;
524  uint8_t *bptr = NULL;
525  int len = avpkt->size;
526  int ret = 0;
527  int i;
528 
529  if ( ctx->pktbuf->size < len) {
530  ret = av_buffer_realloc(&ctx->pktbuf, len);
531  if(ret < 0) {
532  av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
533  len = ctx->pktbuf->size;
534  ret = 0;
535  }
536  }
537  memcpy(ctx->pktbuf->data, avpkt->data, len);
538  bptr = ctx->pktbuf->data;
539 
540 
541  for (i = 0; i < len; i += 3) {
542  uint8_t cc_type = *(bptr + i) & 3;
543  if (validate_cc_data_pair( bptr + i) )
544  continue;
545  /* ignoring data field 1 */
546  if(cc_type == 1)
547  continue;
548  else
549  process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
550  if(ctx->screen_changed && *ctx->buffer.str)
551  {
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);
556  if (ret < 0)
557  return ret;
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);
561  }
562  }
563 
564  *got_sub = sub->num_rects > 0;
565  return ret;
566 }
567 
568 static const AVOption options[] = {
569  {NULL}
570 };
571 
572 static const AVClass ccaption_dec_class = {
573  .class_name = "Closed caption Decoder",
574  .item_name = av_default_item_name,
575  .option = options,
576  .version = LIBAVUTIL_VERSION_INT,
577 };
578 
580  .name = "cc_dec",
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,
587  .decode = decode,
588  .priv_class = &ccaption_dec_class,
589 };