]> git.sesse.net Git - ffmpeg/blob - libavcodec/ccaption_dec.c
avcodec/vc1: Simplify code setting and using extend_x/y
[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         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
197 static av_cold int close_decoder(AVCodecContext *avctx)
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
268 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
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
348 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
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
579 AVCodec ff_ccaption_decoder = {
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 };