]> git.sesse.net Git - ffmpeg/blob - libavcodec/ccaption_dec.c
Merge commit '8eb57dc9d5ea13c12573e0759da0f7e79825af98'
[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/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 {
38     CCMODE_POPON,
39     CCMODE_PAINTON,
40     CCMODE_ROLLUP_2,
41     CCMODE_ROLLUP_3,
42     CCMODE_ROLLUP_4,
43     CCMODE_TEXT,
44 };
45
46 enum cc_color_code {
47     CCCOL_WHITE,
48     CCCOL_GREEN,
49     CCCOL_BLUE,
50     CCCOL_CYAN,
51     CCCOL_RED,
52     CCCOL_YELLOW,
53     CCCOL_MAGENTA,
54     CCCOL_USERDEFINED,
55     CCCOL_BLACK,
56     CCCOL_TRANSPARENT,
57 };
58
59 enum cc_font {
60     CCFONT_REGULAR,
61     CCFONT_ITALICS,
62     CCFONT_UNDERLINED,
63     CCFONT_UNDERLINED_ITALICS,
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 */
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];
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];
155     int active_screen;
156     uint8_t cursor_row;
157     uint8_t cursor_column;
158     uint8_t cursor_color;
159     uint8_t cursor_font;
160     AVBPrint buffer;
161     int screen_changed;
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 */
170     AVBufferRef *pktbuf;
171 }CCaptionSubContext;
172
173
174 static av_cold int init_decoder(AVCodecContext *avctx)
175 {
176     int ret;
177     CCaptionSubContext *ctx = avctx->priv_data;
178
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;
182     ctx->rollup = 2;
183     ret = ff_ass_subtitle_header_default(avctx);
184     if(ret < 0) {
185         return ret;
186     }
187     /* allocate pkt buffer */
188     ctx->pktbuf = av_buffer_alloc(128);
189     if( !ctx->pktbuf) {
190         ret = AVERROR(ENOMEM);
191     }
192     return ret;
193 }
194
195 static av_cold int close_decoder(AVCodecContext *avctx)
196 {
197     CCaptionSubContext *ctx = avctx->priv_data;
198     av_bprint_finalize( &ctx->buffer, NULL);
199     av_buffer_unref(&ctx->pktbuf);
200     return 0;
201 }
202
203 /**
204  * @param ctx closed caption context just to print log
205  */
206 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
207 {
208     if(col < SCREEN_COLUMNS) {
209         row[col] = ch;
210         return 0;
211     }
212     /* We have extra space at end only for null character */
213     else if ( col == SCREEN_COLUMNS && ch == 0) {
214         row[col] = ch;
215         return 0;
216     }
217     else {
218         av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
219         return AVERROR_INVALIDDATA;
220     }
221 }
222
223 /**
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.
229  */
230 static int validate_cc_data_pair (uint8_t *cc_data_pair)
231 {
232     uint8_t cc_valid = (*cc_data_pair & 4) >>2;
233     uint8_t cc_type = *cc_data_pair & 3;
234
235     if (!cc_valid)
236         return AVERROR_INVALIDDATA;
237
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;
242         }
243         if (!parity_table[cc_data_pair[1]]) {
244             cc_data_pair[1]=0x7F;
245         }
246     }
247
248     //Skip non-data
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;
252
253     //skip 708 data
254     if(cc_type == 3 || cc_type == 2 )
255         return AVERROR_PATCHWELCOME;
256
257     /* remove parity bit */
258     cc_data_pair[1] &= 0x7F;
259     cc_data_pair[2] &= 0x7F;
260
261
262     return 0;
263
264 }
265
266 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
267 {
268     switch (ctx->mode) {
269     case CCMODE_POPON:
270         // use Inactive screen
271         return ctx->screen + !ctx->active_screen;
272     case CCMODE_PAINTON:
273     case CCMODE_ROLLUP_2:
274     case CCMODE_ROLLUP_3:
275     case CCMODE_ROLLUP_4:
276     case CCMODE_TEXT:
277         // use active screen
278         return ctx->screen + ctx->active_screen;
279     }
280     /* It was never an option */
281     return NULL;
282 }
283
284 static void roll_up(CCaptionSubContext *ctx)
285 {
286     struct Screen *screen;
287     int i, keep_lines;
288
289     if(ctx->mode == CCMODE_TEXT)
290         return;
291
292     screen = get_writing_screen(ctx);
293
294     /* +1 signify cursor_row starts from 0
295      * Can't keep lines less then row cursor pos
296      */
297     keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
298
299     for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
300         UNSET_FLAG(screen->row_used, i);
301
302
303     for( i = 0; i < keep_lines && screen->row_used; i++ ) {
304         const int i_row = ctx->cursor_row - keep_lines + i + 1;
305
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);
311
312     }
313     UNSET_FLAG(screen->row_used, ctx->cursor_row);
314
315 }
316
317 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
318 {
319     int i;
320     int ret = 0;
321     struct Screen *screen = ctx->screen + ctx->active_screen;
322     ctx->start_time = ctx->startv_time;
323
324     for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
325     {
326         if(CHECK_FLAG(screen->row_used,i)) {
327             char *str = screen->characters[i];
328             /* skip space */
329             while (*str == ' ')
330                 str++;
331
332             av_bprintf(&ctx->buffer, "%s\\N", str);
333             ret = av_bprint_is_complete(&ctx->buffer);
334             if( ret == 0) {
335                 ret = AVERROR(ENOMEM);
336                 break;
337             }
338         }
339
340     }
341     ctx->startv_time = pts;
342     ctx->end_time = pts;
343     return ret;
344 }
345
346 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
347 {
348     int i = lo - 0x20;
349     int ret;
350     struct Screen *screen = get_writing_screen(ctx);
351     char *row = screen->characters[ctx->cursor_row];
352
353     if( i >= 32)
354         return;
355
356     ctx->cursor_color =  pac2_attribs[i][0];
357     ctx->cursor_font = pac2_attribs[i][1];
358
359     SET_FLAG(screen->row_used,ctx->cursor_row);
360     ret = write_char(ctx, row, ctx->cursor_column, ' ');
361     if(ret == 0)
362         ctx->cursor_column++;
363 }
364
365 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
366 {
367     static const int8_t row_map[] = {
368         11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
369     };
370     const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
371     struct Screen *screen = get_writing_screen(ctx);
372     char *row;
373     int indent,i,ret;
374
375     if( row_map[index] <= 0 ) {
376         av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
377         return;
378     }
379
380     lo &= 0x1f;
381
382     ctx->cursor_row = row_map[index] - 1;
383     ctx->cursor_color =  pac2_attribs[lo][0];
384     ctx->cursor_font = pac2_attribs[lo][1];
385     ctx->cursor_column = 0;
386     indent = pac2_attribs[lo][2];
387     row = screen->characters[ctx->cursor_row];
388     for(i = 0;i < indent; i++) {
389         ret = write_char(ctx, row, ctx->cursor_column, ' ');
390         if(  ret == 0 )
391             ctx->cursor_column++;
392     }
393
394 }
395
396 /**
397  * @param pts it is required to set end time
398  */
399 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
400 {
401     int ret = 0;
402     struct Screen *screen = ctx->screen + ctx->active_screen;
403
404     reap_screen(ctx, pts);
405     screen->row_used = 0;
406     ctx->screen_changed = 1;
407     return ret;
408 }
409
410 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
411 {
412     int ret;
413     ret = handle_edm(ctx,pts);
414     ctx->active_screen = !ctx->active_screen;
415     ctx->cursor_column = 0;
416     return ret;
417 }
418
419 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
420 {
421     struct Screen *screen = get_writing_screen(ctx);
422     char *row = screen->characters[ctx->cursor_row];
423     write_char(ctx, row, ctx->cursor_column, 0);
424
425 }
426
427 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
428 {
429     struct Screen *screen = get_writing_screen(ctx);
430     char *row = screen->characters[ctx->cursor_row];
431     int ret;
432
433     SET_FLAG(screen->row_used,ctx->cursor_row);
434
435     ret = write_char(ctx, row, ctx->cursor_column, hi);
436     if( ret == 0 )
437         ctx->cursor_column++;
438
439     if(lo) {
440         ret = write_char(ctx, row, ctx->cursor_column, lo);
441         if ( ret == 0 )
442             ctx->cursor_column++;
443     }
444     write_char(ctx, row, ctx->cursor_column, 0);
445
446     /* reset prev command since character can repeat */
447     ctx->prev_cmd[0] = 0;
448     ctx->prev_cmd[1] = 0;
449     if (lo)
450        av_dlog(ctx, "(%c,%c)\n",hi,lo);
451     else
452        av_dlog(ctx, "(%c)\n",hi);
453 }
454
455 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
456 {
457     int ret = 0;
458 #define COR3(var, with1, with2, with3)  ( (var) == (with1) ||  (var) == (with2) || (var) == (with3) )
459     if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
460     /* ignore redundant command */
461     } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
462               ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
463         handle_pac(ctx, hi, lo);
464     } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
465                 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
466         handle_textattr(ctx, hi, lo);
467     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
468     /* resume caption loading */
469         ctx->mode = CCMODE_POPON;
470     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
471         handle_delete_end_of_row(ctx, hi, lo);
472     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
473         ctx->rollup = 2;
474         ctx->mode = CCMODE_ROLLUP_2;
475     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
476         ctx->rollup = 3;
477         ctx->mode = CCMODE_ROLLUP_3;
478     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
479         ctx->rollup = 4;
480         ctx->mode = CCMODE_ROLLUP_4;
481     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
482     /* resume direct captioning */
483         ctx->mode = CCMODE_PAINTON;
484     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
485     /* resume text display */
486         ctx->mode = CCMODE_TEXT;
487     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
488     /* erase display memory */
489         ret = handle_edm(ctx, pts);
490     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
491     /* carriage return */
492         av_dlog(ctx, "carriage return\n");
493         reap_screen(ctx, pts);
494         roll_up(ctx);
495         ctx->screen_changed = 1;
496         ctx->cursor_column = 0;
497     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
498     /* end of caption */
499         av_dlog(ctx, "handle_eoc\n");
500         ret = handle_eoc(ctx, pts);
501     } else if (hi>=0x20) {
502     /* Standard characters (always in pairs) */
503         handle_char(ctx, hi, lo, pts);
504     } else {
505     /* Ignoring all other non data code */
506         av_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
507     }
508
509     /* set prev command */
510      ctx->prev_cmd[0] = hi;
511      ctx->prev_cmd[1] = lo;
512
513 #undef COR3
514     return ret;
515
516 }
517
518 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
519 {
520     CCaptionSubContext *ctx = avctx->priv_data;
521     AVSubtitle *sub = data;
522     uint8_t *bptr = NULL;
523     int len = avpkt->size;
524     int ret = 0;
525     int i;
526
527     if ( ctx->pktbuf->size < len) {
528         ret = av_buffer_realloc(&ctx->pktbuf, len);
529          if(ret < 0) {
530             av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
531             len = ctx->pktbuf->size;
532             ret = 0;
533         }
534     }
535     memcpy(ctx->pktbuf->data, avpkt->data, len);
536     bptr = ctx->pktbuf->data;
537
538
539     for (i  = 0; i < len; i += 3) {
540         uint8_t cc_type = *(bptr + i) & 3;
541         if (validate_cc_data_pair( bptr + i) )
542             continue;
543         /* ignoring data field 1 */
544         if(cc_type == 1)
545             continue;
546         else
547             process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
548         if(ctx->screen_changed && *ctx->buffer.str)
549         {
550             int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
551             int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
552             av_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
553             ret = ff_ass_add_rect(sub, ctx->buffer.str, start_time, end_time - start_time , 0);
554             if (ret < 0)
555                 return ret;
556             sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
557             ctx->screen_changed = 0;
558             av_bprint_clear(&ctx->buffer);
559         }
560     }
561
562     *got_sub = sub->num_rects > 0;
563     return ret;
564 }
565
566 static const AVOption options[] = {
567     {NULL}
568 };
569
570 static const AVClass ccaption_dec_class = {
571     .class_name = "Closed caption Decoder",
572     .item_name  = av_default_item_name,
573     .option     = options,
574     .version    = LIBAVUTIL_VERSION_INT,
575 };
576
577 AVCodec ff_ccaption_decoder = {
578     .name           = "cc_dec",
579     .long_name      = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
580     .type           = AVMEDIA_TYPE_SUBTITLE,
581     .id             = AV_CODEC_ID_EIA_608,
582     .priv_data_size = sizeof(CCaptionSubContext),
583     .init           = init_decoder,
584     .close          = close_decoder,
585     .decode         = decode,
586     .priv_class     = &ccaption_dec_class,
587 };