]> git.sesse.net Git - ffmpeg/blob - libavcodec/ccaption_dec.c
avcodec/ass: fix doxygen typo
[ffmpeg] / libavcodec / ccaption_dec.c
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/internal.h"
25 #include "libavutil/opt.h"
26
27 #define SCREEN_ROWS 15
28 #define SCREEN_COLUMNS 32
29
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)) )
33
34 /*
35  * TODO list
36  * 1) handle font and color completely
37  */
38 enum cc_mode {
39     CCMODE_POPON,
40     CCMODE_PAINTON,
41     CCMODE_ROLLUP_2,
42     CCMODE_ROLLUP_3,
43     CCMODE_ROLLUP_4,
44     CCMODE_TEXT,
45 };
46
47 enum cc_color_code {
48     CCCOL_WHITE,
49     CCCOL_GREEN,
50     CCCOL_BLUE,
51     CCCOL_CYAN,
52     CCCOL_RED,
53     CCCOL_YELLOW,
54     CCCOL_MAGENTA,
55     CCCOL_USERDEFINED,
56     CCCOL_BLACK,
57     CCCOL_TRANSPARENT,
58 };
59
60 enum cc_font {
61     CCFONT_REGULAR,
62     CCFONT_ITALICS,
63     CCFONT_UNDERLINED,
64     CCFONT_UNDERLINED_ITALICS,
65 };
66
67 static const unsigned char pac2_attribs[32][3] = // Color, font, ident
68 {
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 */
102 };
103
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 };
137
138 struct Screen {
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];
143     /*
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)
148      */
149     int16_t  row_used;
150 };
151
152
153 typedef struct CCaptionSubContext {
154     AVClass *class;
155     struct Screen screen[2];
156     int active_screen;
157     uint8_t cursor_row;
158     uint8_t cursor_column;
159     uint8_t cursor_color;
160     uint8_t cursor_font;
161     AVBPrint buffer;
162     int screen_changed;
163     int rollup;
164     enum  cc_mode mode;
165     int64_t start_time;
166     /* visible screen time */
167     int64_t startv_time;
168     int64_t end_time;
169     char prev_cmd[2];
170     /* buffer to store pkt data */
171     AVBufferRef *pktbuf;
172 }CCaptionSubContext;
173
174
175 static av_cold int init_decoder(AVCodecContext *avctx)
176 {
177     int ret;
178     CCaptionSubContext *ctx = avctx->priv_data;
179
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;
183     ctx->rollup = 2;
184     ret = ff_ass_subtitle_header_default(avctx);
185     if(ret < 0) {
186         return ret;
187     }
188     /* allocate pkt buffer */
189     ctx->pktbuf = av_buffer_alloc(128);
190     if( !ctx->pktbuf) {
191         ret = AVERROR(ENOMEM);
192     }
193     return ret;
194 }
195
196 static av_cold int close_decoder(AVCodecContext *avctx)
197 {
198     CCaptionSubContext *ctx = avctx->priv_data;
199     av_bprint_finalize( &ctx->buffer, NULL);
200     av_buffer_unref(&ctx->pktbuf);
201     return 0;
202 }
203
204 /**
205  * @param ctx closed caption context just to print log
206  */
207 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
208 {
209     if(col < SCREEN_COLUMNS) {
210         row[col] = ch;
211         return 0;
212     }
213     /* We have extra space at end only for null character */
214     else if ( col == SCREEN_COLUMNS && ch == 0) {
215         row[col] = ch;
216         return 0;
217     }
218     else {
219         av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
220         return AVERROR_INVALIDDATA;
221     }
222 }
223
224 /**
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.
230  */
231 static int validate_cc_data_pair (uint8_t *cc_data_pair)
232 {
233     uint8_t cc_valid = (*cc_data_pair & 4) >>2;
234     uint8_t cc_type = *cc_data_pair & 3;
235
236     if (!cc_valid)
237         return AVERROR_INVALIDDATA;
238
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;
243         }
244         if (!parity_table[cc_data_pair[1]]) {
245             cc_data_pair[1]=0x7F;
246         }
247     }
248
249     //Skip non-data
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;
253
254     //skip 708 data
255     if(cc_type == 3 || cc_type == 2 )
256         return AVERROR_PATCHWELCOME;
257
258     /* remove parity bit */
259     cc_data_pair[1] &= 0x7F;
260     cc_data_pair[2] &= 0x7F;
261
262
263     return 0;
264
265 }
266
267 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
268 {
269     switch (ctx->mode) {
270     case CCMODE_POPON:
271         // use Inactive screen
272         return ctx->screen + !ctx->active_screen;
273     case CCMODE_PAINTON:
274     case CCMODE_ROLLUP_2:
275     case CCMODE_ROLLUP_3:
276     case CCMODE_ROLLUP_4:
277     case CCMODE_TEXT:
278         // use active screen
279         return ctx->screen + ctx->active_screen;
280     }
281     /* It was never an option */
282     return NULL;
283 }
284
285 static void roll_up(CCaptionSubContext *ctx)
286 {
287     struct Screen *screen;
288     int i, keep_lines;
289
290     if(ctx->mode == CCMODE_TEXT)
291         return;
292
293     screen = get_writing_screen(ctx);
294
295     /* +1 signify cursor_row starts from 0
296      * Can't keep lines less then row cursor pos
297      */
298     keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
299
300     for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
301         UNSET_FLAG(screen->row_used, i);
302
303
304     for( i = 0; i < keep_lines && screen->row_used; i++ ) {
305         const int i_row = ctx->cursor_row - keep_lines + i + 1;
306
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);
312
313     }
314     UNSET_FLAG(screen->row_used, ctx->cursor_row);
315
316 }
317
318 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
319 {
320     int i;
321     int ret = 0;
322     struct Screen *screen = ctx->screen + ctx->active_screen;
323     ctx->start_time = ctx->startv_time;
324
325     for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
326     {
327         if(CHECK_FLAG(screen->row_used,i)) {
328             char *str = screen->characters[i];
329             /* skip space */
330             while (*str == ' ')
331                 str++;
332
333             av_bprintf(&ctx->buffer, "%s\\N", str);
334             ret = av_bprint_is_complete(&ctx->buffer);
335             if( ret == 0) {
336                 ret = AVERROR(ENOMEM);
337                 break;
338             }
339         }
340
341     }
342     if(screen->row_used && ctx->buffer.len >= 2 ) {
343         ctx->buffer.len -= 2;
344         ctx->buffer.str[ctx->buffer.len] = 0;
345     }
346     ctx->startv_time = pts;
347     ctx->end_time = pts;
348     return ret;
349 }
350
351 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
352 {
353     int i = lo - 0x20;
354     int ret;
355     struct Screen *screen = get_writing_screen(ctx);
356     char *row = screen->characters[ctx->cursor_row];
357
358     if( i >= 32)
359         return;
360
361     ctx->cursor_color =  pac2_attribs[i][0];
362     ctx->cursor_font = pac2_attribs[i][1];
363
364     SET_FLAG(screen->row_used,ctx->cursor_row);
365     ret = write_char(ctx, row, ctx->cursor_column, ' ');
366     if(ret == 0)
367         ctx->cursor_column++;
368 }
369
370 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
371 {
372     static const int8_t row_map[] = {
373         11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
374     };
375     const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
376     struct Screen *screen = get_writing_screen(ctx);
377     char *row;
378     int indent,i,ret;
379
380     if( row_map[index] <= 0 ) {
381         av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
382         return;
383     }
384
385     lo &= 0x1f;
386
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, ' ');
395         if(  ret == 0 )
396             ctx->cursor_column++;
397     }
398
399 }
400
401 /**
402  * @param pts it is required to set end time
403  */
404 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
405 {
406     int ret = 0;
407     struct Screen *screen = ctx->screen + ctx->active_screen;
408
409     reap_screen(ctx, pts);
410     screen->row_used = 0;
411     ctx->screen_changed = 1;
412     return ret;
413 }
414
415 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
416 {
417     int ret;
418     ret = handle_edm(ctx,pts);
419     ctx->active_screen = !ctx->active_screen;
420     ctx->cursor_column = 0;
421     return ret;
422 }
423
424 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
425 {
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);
429
430 }
431
432 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
433 {
434     struct Screen *screen = get_writing_screen(ctx);
435     char *row = screen->characters[ctx->cursor_row];
436     int ret;
437
438     SET_FLAG(screen->row_used,ctx->cursor_row);
439
440     ret = write_char(ctx, row, ctx->cursor_column, hi);
441     if( ret == 0 )
442         ctx->cursor_column++;
443
444     if(lo) {
445         ret = write_char(ctx, row, ctx->cursor_column, lo);
446         if ( ret == 0 )
447             ctx->cursor_column++;
448     }
449     write_char(ctx, row, ctx->cursor_column, 0);
450
451     /* reset prev command since character can repeat */
452     ctx->prev_cmd[0] = 0;
453     ctx->prev_cmd[1] = 0;
454     if (lo)
455        ff_dlog(ctx, "(%c,%c)\n",hi,lo);
456     else
457        ff_dlog(ctx, "(%c)\n",hi);
458 }
459
460 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
461 {
462     int ret = 0;
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 ) {
478         ctx->rollup = 2;
479         ctx->mode = CCMODE_ROLLUP_2;
480     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
481         ctx->rollup = 3;
482         ctx->mode = CCMODE_ROLLUP_3;
483     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
484         ctx->rollup = 4;
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);
499         roll_up(ctx);
500         ctx->screen_changed = 1;
501         ctx->cursor_column = 0;
502     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
503     /* end of caption */
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);
509     } else {
510     /* Ignoring all other non data code */
511         ff_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
512     }
513
514     /* set prev command */
515      ctx->prev_cmd[0] = hi;
516      ctx->prev_cmd[1] = lo;
517
518 #undef COR3
519     return ret;
520
521 }
522
523 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
524 {
525     CCaptionSubContext *ctx = avctx->priv_data;
526     AVSubtitle *sub = data;
527     uint8_t *bptr = NULL;
528     int len = avpkt->size;
529     int ret = 0;
530     int i;
531
532     if ( ctx->pktbuf->size < len) {
533         ret = av_buffer_realloc(&ctx->pktbuf, len);
534          if(ret < 0) {
535             av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
536             len = ctx->pktbuf->size;
537             ret = 0;
538         }
539     }
540     memcpy(ctx->pktbuf->data, avpkt->data, len);
541     bptr = ctx->pktbuf->data;
542
543
544     for (i  = 0; i < len; i += 3) {
545         uint8_t cc_type = *(bptr + i) & 3;
546         if (validate_cc_data_pair( bptr + i) )
547             continue;
548         /* ignoring data field 1 */
549         if(cc_type == 1)
550             continue;
551         else
552             process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
553         if(ctx->screen_changed && *ctx->buffer.str)
554         {
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);
559             if (ret < 0)
560                 return ret;
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);
564         }
565     }
566
567     *got_sub = sub->num_rects > 0;
568     return ret;
569 }
570
571 static const AVOption options[] = {
572     {NULL}
573 };
574
575 static const AVClass ccaption_dec_class = {
576     .class_name = "Closed caption Decoder",
577     .item_name  = av_default_item_name,
578     .option     = options,
579     .version    = LIBAVUTIL_VERSION_INT,
580 };
581
582 AVCodec ff_ccaption_decoder = {
583     .name           = "cc_dec",
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,
590     .decode         = decode,
591     .priv_class     = &ccaption_dec_class,
592 };