blob: 09ceb1b3bf1c20ce67defffd0f481165a9015e0b
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 | static const AVRational ms_tb = {1, 1000}; |
34 | |
35 | /* |
36 | * TODO list |
37 | * 1) handle font and color completely |
38 | */ |
39 | enum cc_mode { |
40 | CCMODE_POPON, |
41 | CCMODE_PAINTON, |
42 | CCMODE_ROLLUP, |
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 | enum cc_charset { |
67 | CCSET_BASIC_AMERICAN, |
68 | CCSET_SPECIAL_AMERICAN, |
69 | CCSET_EXTENDED_SPANISH_FRENCH_MISC, |
70 | CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH, |
71 | }; |
72 | |
73 | static const char *charset_overrides[4][128] = |
74 | { |
75 | [CCSET_BASIC_AMERICAN] = { |
76 | [0x27] = "\u2019", |
77 | [0x2a] = "\u00e1", |
78 | [0x5c] = "\u00e9", |
79 | [0x5e] = "\u00ed", |
80 | [0x5f] = "\u00f3", |
81 | [0x60] = "\u00fa", |
82 | [0x7b] = "\u00e7", |
83 | [0x7c] = "\u00f7", |
84 | [0x7d] = "\u00d1", |
85 | [0x7e] = "\u00f1", |
86 | [0x7f] = "\u2588" |
87 | }, |
88 | [CCSET_SPECIAL_AMERICAN] = { |
89 | [0x30] = "\u00ae", |
90 | [0x31] = "\u00b0", |
91 | [0x32] = "\u00bd", |
92 | [0x33] = "\u00bf", |
93 | [0x34] = "\u2122", |
94 | [0x35] = "\u00a2", |
95 | [0x36] = "\u00a3", |
96 | [0x37] = "\u266a", |
97 | [0x38] = "\u00e0", |
98 | [0x39] = "\u00A0", |
99 | [0x3a] = "\u00e8", |
100 | [0x3b] = "\u00e2", |
101 | [0x3c] = "\u00ea", |
102 | [0x3d] = "\u00ee", |
103 | [0x3e] = "\u00f4", |
104 | [0x3f] = "\u00fb", |
105 | }, |
106 | [CCSET_EXTENDED_SPANISH_FRENCH_MISC] = { |
107 | [0x20] = "\u00c1", |
108 | [0x21] = "\u00c9", |
109 | [0x22] = "\u00d3", |
110 | [0x23] = "\u00da", |
111 | [0x24] = "\u00dc", |
112 | [0x25] = "\u00fc", |
113 | [0x26] = "\u00b4", |
114 | [0x27] = "\u00a1", |
115 | [0x28] = "*", |
116 | [0x29] = "\u2018", |
117 | [0x2a] = "-", |
118 | [0x2b] = "\u00a9", |
119 | [0x2c] = "\u2120", |
120 | [0x2d] = "\u00b7", |
121 | [0x2e] = "\u201c", |
122 | [0x2f] = "\u201d", |
123 | [0x30] = "\u00c0", |
124 | [0x31] = "\u00c2", |
125 | [0x32] = "\u00c7", |
126 | [0x33] = "\u00c8", |
127 | [0x34] = "\u00ca", |
128 | [0x35] = "\u00cb", |
129 | [0x36] = "\u00eb", |
130 | [0x37] = "\u00ce", |
131 | [0x38] = "\u00cf", |
132 | [0x39] = "\u00ef", |
133 | [0x3a] = "\u00d4", |
134 | [0x3b] = "\u00d9", |
135 | [0x3c] = "\u00f9", |
136 | [0x3d] = "\u00db", |
137 | [0x3e] = "\u00ab", |
138 | [0x3f] = "\u00bb", |
139 | }, |
140 | [CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH] = { |
141 | [0x20] = "\u00c3", |
142 | [0x21] = "\u00e3", |
143 | [0x22] = "\u00cd", |
144 | [0x23] = "\u00cc", |
145 | [0x24] = "\u00ec", |
146 | [0x25] = "\u00d2", |
147 | [0x26] = "\u00f2", |
148 | [0x27] = "\u00d5", |
149 | [0x28] = "\u00f5", |
150 | [0x29] = "{", |
151 | [0x2a] = "}", |
152 | [0x2b] = "\\", |
153 | [0x2c] = "^", |
154 | [0x2d] = "_", |
155 | [0x2e] = "|", |
156 | [0x2f] = "~", |
157 | [0x30] = "\u00c4", |
158 | [0x31] = "\u00e4", |
159 | [0x32] = "\u00d6", |
160 | [0x33] = "\u00f6", |
161 | [0x34] = "\u00df", |
162 | [0x35] = "\u00a5", |
163 | [0x36] = "\u00a4", |
164 | [0x37] = "\u00a6", |
165 | [0x38] = "\u00c5", |
166 | [0x39] = "\u00e5", |
167 | [0x3a] = "\u00d8", |
168 | [0x3b] = "\u00f8", |
169 | [0x3c] = "\u250c", |
170 | [0x3d] = "\u2510", |
171 | [0x3e] = "\u2514", |
172 | [0x3f] = "\u2518", |
173 | }, |
174 | }; |
175 | |
176 | static const unsigned char pac2_attribs[32][3] = // Color, font, ident |
177 | { |
178 | { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x40 || 0x60 |
179 | { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x41 || 0x61 |
180 | { CCCOL_GREEN, CCFONT_REGULAR, 0 }, // 0x42 || 0x62 |
181 | { CCCOL_GREEN, CCFONT_UNDERLINED, 0 }, // 0x43 || 0x63 |
182 | { CCCOL_BLUE, CCFONT_REGULAR, 0 }, // 0x44 || 0x64 |
183 | { CCCOL_BLUE, CCFONT_UNDERLINED, 0 }, // 0x45 || 0x65 |
184 | { CCCOL_CYAN, CCFONT_REGULAR, 0 }, // 0x46 || 0x66 |
185 | { CCCOL_CYAN, CCFONT_UNDERLINED, 0 }, // 0x47 || 0x67 |
186 | { CCCOL_RED, CCFONT_REGULAR, 0 }, // 0x48 || 0x68 |
187 | { CCCOL_RED, CCFONT_UNDERLINED, 0 }, // 0x49 || 0x69 |
188 | { CCCOL_YELLOW, CCFONT_REGULAR, 0 }, // 0x4a || 0x6a |
189 | { CCCOL_YELLOW, CCFONT_UNDERLINED, 0 }, // 0x4b || 0x6b |
190 | { CCCOL_MAGENTA, CCFONT_REGULAR, 0 }, // 0x4c || 0x6c |
191 | { CCCOL_MAGENTA, CCFONT_UNDERLINED, 0 }, // 0x4d || 0x6d |
192 | { CCCOL_WHITE, CCFONT_ITALICS, 0 }, // 0x4e || 0x6e |
193 | { CCCOL_WHITE, CCFONT_UNDERLINED_ITALICS, 0 }, // 0x4f || 0x6f |
194 | { CCCOL_WHITE, CCFONT_REGULAR, 0 }, // 0x50 || 0x70 |
195 | { CCCOL_WHITE, CCFONT_UNDERLINED, 0 }, // 0x51 || 0x71 |
196 | { CCCOL_WHITE, CCFONT_REGULAR, 4 }, // 0x52 || 0x72 |
197 | { CCCOL_WHITE, CCFONT_UNDERLINED, 4 }, // 0x53 || 0x73 |
198 | { CCCOL_WHITE, CCFONT_REGULAR, 8 }, // 0x54 || 0x74 |
199 | { CCCOL_WHITE, CCFONT_UNDERLINED, 8 }, // 0x55 || 0x75 |
200 | { CCCOL_WHITE, CCFONT_REGULAR, 12 }, // 0x56 || 0x76 |
201 | { CCCOL_WHITE, CCFONT_UNDERLINED, 12 }, // 0x57 || 0x77 |
202 | { CCCOL_WHITE, CCFONT_REGULAR, 16 }, // 0x58 || 0x78 |
203 | { CCCOL_WHITE, CCFONT_UNDERLINED, 16 }, // 0x59 || 0x79 |
204 | { CCCOL_WHITE, CCFONT_REGULAR, 20 }, // 0x5a || 0x7a |
205 | { CCCOL_WHITE, CCFONT_UNDERLINED, 20 }, // 0x5b || 0x7b |
206 | { CCCOL_WHITE, CCFONT_REGULAR, 24 }, // 0x5c || 0x7c |
207 | { CCCOL_WHITE, CCFONT_UNDERLINED, 24 }, // 0x5d || 0x7d |
208 | { CCCOL_WHITE, CCFONT_REGULAR, 28 }, // 0x5e || 0x7e |
209 | { CCCOL_WHITE, CCFONT_UNDERLINED, 28 } // 0x5f || 0x7f |
210 | /* total 32 entries */ |
211 | }; |
212 | |
213 | struct Screen { |
214 | /* +1 is used to compensate null character of string */ |
215 | uint8_t characters[SCREEN_ROWS][SCREEN_COLUMNS+1]; |
216 | uint8_t charsets[SCREEN_ROWS][SCREEN_COLUMNS+1]; |
217 | uint8_t colors[SCREEN_ROWS][SCREEN_COLUMNS+1]; |
218 | uint8_t fonts[SCREEN_ROWS][SCREEN_COLUMNS+1]; |
219 | /* |
220 | * Bitmask of used rows; if a bit is not set, the |
221 | * corresponding row is not used. |
222 | * for setting row 1 use row | (1 << 0) |
223 | * for setting row 15 use row | (1 << 14) |
224 | */ |
225 | int16_t row_used; |
226 | }; |
227 | |
228 | typedef struct CCaptionSubContext { |
229 | AVClass *class; |
230 | int real_time; |
231 | struct Screen screen[2]; |
232 | int active_screen; |
233 | uint8_t cursor_row; |
234 | uint8_t cursor_column; |
235 | uint8_t cursor_color; |
236 | uint8_t cursor_font; |
237 | uint8_t cursor_charset; |
238 | AVBPrint buffer; |
239 | int buffer_changed; |
240 | int rollup; |
241 | enum cc_mode mode; |
242 | int64_t start_time; |
243 | /* visible screen time */ |
244 | int64_t startv_time; |
245 | int64_t end_time; |
246 | int screen_touched; |
247 | int64_t last_real_time; |
248 | char prev_cmd[2]; |
249 | /* buffer to store pkt data */ |
250 | uint8_t *pktbuf; |
251 | int pktbuf_size; |
252 | int readorder; |
253 | } CCaptionSubContext; |
254 | |
255 | |
256 | static av_cold int init_decoder(AVCodecContext *avctx) |
257 | { |
258 | int ret; |
259 | CCaptionSubContext *ctx = avctx->priv_data; |
260 | |
261 | av_bprint_init(&ctx->buffer, 0, AV_BPRINT_SIZE_UNLIMITED); |
262 | /* taking by default roll up to 2 */ |
263 | ctx->mode = CCMODE_ROLLUP; |
264 | ctx->rollup = 2; |
265 | ctx->cursor_row = 10; |
266 | ret = ff_ass_subtitle_header(avctx, "Monospace", |
267 | ASS_DEFAULT_FONT_SIZE, |
268 | ASS_DEFAULT_COLOR, |
269 | ASS_DEFAULT_BACK_COLOR, |
270 | ASS_DEFAULT_BOLD, |
271 | ASS_DEFAULT_ITALIC, |
272 | ASS_DEFAULT_UNDERLINE, |
273 | 3, |
274 | ASS_DEFAULT_ALIGNMENT); |
275 | if (ret < 0) { |
276 | return ret; |
277 | } |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | static av_cold int close_decoder(AVCodecContext *avctx) |
283 | { |
284 | CCaptionSubContext *ctx = avctx->priv_data; |
285 | av_bprint_finalize(&ctx->buffer, NULL); |
286 | av_freep(&ctx->pktbuf); |
287 | ctx->pktbuf_size = 0; |
288 | return 0; |
289 | } |
290 | |
291 | static void flush_decoder(AVCodecContext *avctx) |
292 | { |
293 | CCaptionSubContext *ctx = avctx->priv_data; |
294 | ctx->screen[0].row_used = 0; |
295 | ctx->screen[1].row_used = 0; |
296 | ctx->prev_cmd[0] = 0; |
297 | ctx->prev_cmd[1] = 0; |
298 | ctx->mode = CCMODE_ROLLUP; |
299 | ctx->rollup = 2; |
300 | ctx->cursor_row = 10; |
301 | ctx->cursor_column = 0; |
302 | ctx->cursor_font = 0; |
303 | ctx->cursor_color = 0; |
304 | ctx->cursor_charset = 0; |
305 | ctx->active_screen = 0; |
306 | ctx->last_real_time = 0; |
307 | ctx->screen_touched = 0; |
308 | ctx->buffer_changed = 0; |
309 | if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP)) |
310 | ctx->readorder = 0; |
311 | av_bprint_clear(&ctx->buffer); |
312 | } |
313 | |
314 | /** |
315 | * @param ctx closed caption context just to print log |
316 | */ |
317 | static void write_char(CCaptionSubContext *ctx, struct Screen *screen, char ch) |
318 | { |
319 | uint8_t col = ctx->cursor_column; |
320 | char *row = screen->characters[ctx->cursor_row]; |
321 | char *font = screen->fonts[ctx->cursor_row]; |
322 | char *charset = screen->charsets[ctx->cursor_row]; |
323 | |
324 | if (col < SCREEN_COLUMNS) { |
325 | row[col] = ch; |
326 | font[col] = ctx->cursor_font; |
327 | charset[col] = ctx->cursor_charset; |
328 | ctx->cursor_charset = CCSET_BASIC_AMERICAN; |
329 | if (ch) ctx->cursor_column++; |
330 | return; |
331 | } |
332 | /* We have extra space at end only for null character */ |
333 | else if (col == SCREEN_COLUMNS && ch == 0) { |
334 | row[col] = ch; |
335 | return; |
336 | } |
337 | else { |
338 | av_log(ctx, AV_LOG_WARNING, "Data Ignored since exceeding screen width\n"); |
339 | return; |
340 | } |
341 | } |
342 | |
343 | /** |
344 | * This function after validating parity bit, also remove it from data pair. |
345 | * The first byte doesn't pass parity, we replace it with a solid blank |
346 | * and process the pair. |
347 | * If the second byte doesn't pass parity, it returns INVALIDDATA |
348 | * user can ignore the whole pair and pass the other pair. |
349 | */ |
350 | static int validate_cc_data_pair(uint8_t *cc_data_pair) |
351 | { |
352 | uint8_t cc_valid = (*cc_data_pair & 4) >>2; |
353 | uint8_t cc_type = *cc_data_pair & 3; |
354 | |
355 | if (!cc_valid) |
356 | return AVERROR_INVALIDDATA; |
357 | |
358 | // if EIA-608 data then verify parity. |
359 | if (cc_type==0 || cc_type==1) { |
360 | if (!av_parity(cc_data_pair[2])) { |
361 | return AVERROR_INVALIDDATA; |
362 | } |
363 | if (!av_parity(cc_data_pair[1])) { |
364 | cc_data_pair[1]=0x7F; |
365 | } |
366 | } |
367 | |
368 | //Skip non-data |
369 | if ((cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD) |
370 | && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0) |
371 | return AVERROR_PATCHWELCOME; |
372 | |
373 | //skip 708 data |
374 | if (cc_type == 3 || cc_type == 2) |
375 | return AVERROR_PATCHWELCOME; |
376 | |
377 | /* remove parity bit */ |
378 | cc_data_pair[1] &= 0x7F; |
379 | cc_data_pair[2] &= 0x7F; |
380 | |
381 | return 0; |
382 | } |
383 | |
384 | static struct Screen *get_writing_screen(CCaptionSubContext *ctx) |
385 | { |
386 | switch (ctx->mode) { |
387 | case CCMODE_POPON: |
388 | // use Inactive screen |
389 | return ctx->screen + !ctx->active_screen; |
390 | case CCMODE_PAINTON: |
391 | case CCMODE_ROLLUP: |
392 | case CCMODE_TEXT: |
393 | // use active screen |
394 | return ctx->screen + ctx->active_screen; |
395 | } |
396 | /* It was never an option */ |
397 | return NULL; |
398 | } |
399 | |
400 | static void roll_up(CCaptionSubContext *ctx) |
401 | { |
402 | struct Screen *screen; |
403 | int i, keep_lines; |
404 | |
405 | if (ctx->mode == CCMODE_TEXT) |
406 | return; |
407 | |
408 | screen = get_writing_screen(ctx); |
409 | |
410 | /* +1 signify cursor_row starts from 0 |
411 | * Can't keep lines less then row cursor pos |
412 | */ |
413 | keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup); |
414 | |
415 | for (i = 0; i < SCREEN_ROWS; i++) { |
416 | if (i > ctx->cursor_row - keep_lines && i <= ctx->cursor_row) |
417 | continue; |
418 | UNSET_FLAG(screen->row_used, i); |
419 | } |
420 | |
421 | for (i = 0; i < keep_lines && screen->row_used; i++) { |
422 | const int i_row = ctx->cursor_row - keep_lines + i + 1; |
423 | |
424 | memcpy(screen->characters[i_row], screen->characters[i_row+1], SCREEN_COLUMNS); |
425 | memcpy(screen->colors[i_row], screen->colors[i_row+1], SCREEN_COLUMNS); |
426 | memcpy(screen->fonts[i_row], screen->fonts[i_row+1], SCREEN_COLUMNS); |
427 | memcpy(screen->charsets[i_row], screen->charsets[i_row+1], SCREEN_COLUMNS); |
428 | if (CHECK_FLAG(screen->row_used, i_row + 1)) |
429 | SET_FLAG(screen->row_used, i_row); |
430 | } |
431 | |
432 | UNSET_FLAG(screen->row_used, ctx->cursor_row); |
433 | } |
434 | |
435 | static int capture_screen(CCaptionSubContext *ctx) |
436 | { |
437 | int i, j, tab = 0; |
438 | struct Screen *screen = ctx->screen + ctx->active_screen; |
439 | enum cc_font prev_font = CCFONT_REGULAR; |
440 | av_bprint_clear(&ctx->buffer); |
441 | |
442 | for (i = 0; screen->row_used && i < SCREEN_ROWS; i++) |
443 | { |
444 | if (CHECK_FLAG(screen->row_used, i)) { |
445 | const char *row = screen->characters[i]; |
446 | const char *charset = screen->charsets[i]; |
447 | j = 0; |
448 | while (row[j] == ' ' && charset[j] == CCSET_BASIC_AMERICAN) |
449 | j++; |
450 | if (!tab || j < tab) |
451 | tab = j; |
452 | } |
453 | } |
454 | |
455 | for (i = 0; screen->row_used && i < SCREEN_ROWS; i++) |
456 | { |
457 | if (CHECK_FLAG(screen->row_used, i)) { |
458 | const char *row = screen->characters[i]; |
459 | const char *font = screen->fonts[i]; |
460 | const char *charset = screen->charsets[i]; |
461 | const char *override; |
462 | int x, y, seen_char = 0; |
463 | j = 0; |
464 | |
465 | /* skip leading space */ |
466 | while (row[j] == ' ' && charset[j] == CCSET_BASIC_AMERICAN && j < tab) |
467 | j++; |
468 | |
469 | x = ASS_DEFAULT_PLAYRESX * (0.1 + 0.0250 * j); |
470 | y = ASS_DEFAULT_PLAYRESY * (0.1 + 0.0533 * i); |
471 | av_bprintf(&ctx->buffer, "{\\an7}{\\pos(%d,%d)}", x, y); |
472 | |
473 | for (; j < SCREEN_COLUMNS; j++) { |
474 | const char *e_tag = "", *s_tag = ""; |
475 | |
476 | if (row[j] == 0) |
477 | break; |
478 | |
479 | if (prev_font != font[j]) { |
480 | switch (prev_font) { |
481 | case CCFONT_ITALICS: |
482 | e_tag = "{\\i0}"; |
483 | break; |
484 | case CCFONT_UNDERLINED: |
485 | e_tag = "{\\u0}"; |
486 | break; |
487 | case CCFONT_UNDERLINED_ITALICS: |
488 | e_tag = "{\\u0}{\\i0}"; |
489 | break; |
490 | } |
491 | switch (font[j]) { |
492 | case CCFONT_ITALICS: |
493 | s_tag = "{\\i1}"; |
494 | break; |
495 | case CCFONT_UNDERLINED: |
496 | s_tag = "{\\u1}"; |
497 | break; |
498 | case CCFONT_UNDERLINED_ITALICS: |
499 | s_tag = "{\\u1}{\\i1}"; |
500 | break; |
501 | } |
502 | } |
503 | prev_font = font[j]; |
504 | override = charset_overrides[(int)charset[j]][(int)row[j]]; |
505 | if (override) { |
506 | av_bprintf(&ctx->buffer, "%s%s%s", e_tag, s_tag, override); |
507 | seen_char = 1; |
508 | } else if (row[j] == ' ' && !seen_char) { |
509 | av_bprintf(&ctx->buffer, "%s%s\\h", e_tag, s_tag); |
510 | } else { |
511 | av_bprintf(&ctx->buffer, "%s%s%c", e_tag, s_tag, row[j]); |
512 | seen_char = 1; |
513 | } |
514 | |
515 | } |
516 | av_bprintf(&ctx->buffer, "\\N"); |
517 | } |
518 | } |
519 | if (!av_bprint_is_complete(&ctx->buffer)) |
520 | return AVERROR(ENOMEM); |
521 | if (screen->row_used && ctx->buffer.len >= 2) { |
522 | ctx->buffer.len -= 2; |
523 | ctx->buffer.str[ctx->buffer.len] = 0; |
524 | } |
525 | ctx->buffer_changed = 1; |
526 | return 0; |
527 | } |
528 | |
529 | static int reap_screen(CCaptionSubContext *ctx, int64_t pts) |
530 | { |
531 | ctx->start_time = ctx->startv_time; |
532 | ctx->startv_time = pts; |
533 | ctx->end_time = pts; |
534 | return capture_screen(ctx); |
535 | } |
536 | |
537 | static void handle_textattr(CCaptionSubContext *ctx, uint8_t hi, uint8_t lo) |
538 | { |
539 | int i = lo - 0x20; |
540 | struct Screen *screen = get_writing_screen(ctx); |
541 | |
542 | if (i >= 32) |
543 | return; |
544 | |
545 | ctx->cursor_color = pac2_attribs[i][0]; |
546 | ctx->cursor_font = pac2_attribs[i][1]; |
547 | |
548 | SET_FLAG(screen->row_used, ctx->cursor_row); |
549 | write_char(ctx, screen, ' '); |
550 | } |
551 | |
552 | static void handle_pac(CCaptionSubContext *ctx, uint8_t hi, uint8_t lo) |
553 | { |
554 | static const int8_t row_map[] = { |
555 | 11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10 |
556 | }; |
557 | const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 ); |
558 | struct Screen *screen = get_writing_screen(ctx); |
559 | int indent, i; |
560 | |
561 | if (row_map[index] <= 0) { |
562 | av_log(ctx, AV_LOG_DEBUG, "Invalid pac index encountered\n"); |
563 | return; |
564 | } |
565 | |
566 | lo &= 0x1f; |
567 | |
568 | ctx->cursor_row = row_map[index] - 1; |
569 | ctx->cursor_color = pac2_attribs[lo][0]; |
570 | ctx->cursor_font = pac2_attribs[lo][1]; |
571 | ctx->cursor_charset = CCSET_BASIC_AMERICAN; |
572 | ctx->cursor_column = 0; |
573 | indent = pac2_attribs[lo][2]; |
574 | for (i = 0; i < indent; i++) { |
575 | write_char(ctx, screen, ' '); |
576 | } |
577 | } |
578 | |
579 | /** |
580 | * @param pts it is required to set end time |
581 | */ |
582 | static void handle_edm(CCaptionSubContext *ctx, int64_t pts) |
583 | { |
584 | struct Screen *screen = ctx->screen + ctx->active_screen; |
585 | |
586 | // In buffered mode, keep writing to screen until it is wiped. |
587 | // Before wiping the display, capture contents to emit subtitle. |
588 | if (!ctx->real_time) |
589 | reap_screen(ctx, pts); |
590 | |
591 | screen->row_used = 0; |
592 | |
593 | // In realtime mode, emit an empty caption so the last one doesn't |
594 | // stay on the screen. |
595 | if (ctx->real_time) |
596 | reap_screen(ctx, pts); |
597 | } |
598 | |
599 | static void handle_eoc(CCaptionSubContext *ctx, int64_t pts) |
600 | { |
601 | // In buffered mode, we wait til the *next* EOC and |
602 | // reap what was already on the screen since the last EOC. |
603 | if (!ctx->real_time) |
604 | handle_edm(ctx,pts); |
605 | |
606 | ctx->active_screen = !ctx->active_screen; |
607 | ctx->cursor_column = 0; |
608 | |
609 | // In realtime mode, we display the buffered contents (after |
610 | // flipping the buffer to active above) as soon as EOC arrives. |
611 | if (ctx->real_time) |
612 | reap_screen(ctx, pts); |
613 | } |
614 | |
615 | static void handle_delete_end_of_row(CCaptionSubContext *ctx, char hi, char lo) |
616 | { |
617 | struct Screen *screen = get_writing_screen(ctx); |
618 | write_char(ctx, screen, 0); |
619 | } |
620 | |
621 | static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts) |
622 | { |
623 | struct Screen *screen = get_writing_screen(ctx); |
624 | |
625 | SET_FLAG(screen->row_used, ctx->cursor_row); |
626 | |
627 | switch (hi) { |
628 | case 0x11: |
629 | ctx->cursor_charset = CCSET_SPECIAL_AMERICAN; |
630 | break; |
631 | case 0x12: |
632 | if (ctx->cursor_column > 0) |
633 | ctx->cursor_column -= 1; |
634 | ctx->cursor_charset = CCSET_EXTENDED_SPANISH_FRENCH_MISC; |
635 | break; |
636 | case 0x13: |
637 | if (ctx->cursor_column > 0) |
638 | ctx->cursor_column -= 1; |
639 | ctx->cursor_charset = CCSET_EXTENDED_PORTUGUESE_GERMAN_DANISH; |
640 | break; |
641 | default: |
642 | ctx->cursor_charset = CCSET_BASIC_AMERICAN; |
643 | write_char(ctx, screen, hi); |
644 | break; |
645 | } |
646 | |
647 | if (lo) { |
648 | write_char(ctx, screen, lo); |
649 | } |
650 | write_char(ctx, screen, 0); |
651 | |
652 | if (ctx->mode != CCMODE_POPON) |
653 | ctx->screen_touched = 1; |
654 | |
655 | if (lo) |
656 | ff_dlog(ctx, "(%c,%c)\n", hi, lo); |
657 | else |
658 | ff_dlog(ctx, "(%c)\n", hi); |
659 | } |
660 | |
661 | static void process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo) |
662 | { |
663 | if (hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) { |
664 | /* ignore redundant command */ |
665 | return; |
666 | } |
667 | |
668 | /* set prev command */ |
669 | ctx->prev_cmd[0] = hi; |
670 | ctx->prev_cmd[1] = lo; |
671 | |
672 | if ( (hi == 0x10 && (lo >= 0x40 && lo <= 0x5f)) || |
673 | ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) { |
674 | handle_pac(ctx, hi, lo); |
675 | } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) || |
676 | ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) { |
677 | handle_textattr(ctx, hi, lo); |
678 | } else if (hi == 0x14 || hi == 0x15 || hi == 0x1c) { |
679 | switch (lo) { |
680 | case 0x20: |
681 | /* resume caption loading */ |
682 | ctx->mode = CCMODE_POPON; |
683 | break; |
684 | case 0x24: |
685 | handle_delete_end_of_row(ctx, hi, lo); |
686 | break; |
687 | case 0x25: |
688 | case 0x26: |
689 | case 0x27: |
690 | ctx->rollup = lo - 0x23; |
691 | ctx->mode = CCMODE_ROLLUP; |
692 | break; |
693 | case 0x29: |
694 | /* resume direct captioning */ |
695 | ctx->mode = CCMODE_PAINTON; |
696 | break; |
697 | case 0x2b: |
698 | /* resume text display */ |
699 | ctx->mode = CCMODE_TEXT; |
700 | break; |
701 | case 0x2c: |
702 | /* erase display memory */ |
703 | handle_edm(ctx, pts); |
704 | break; |
705 | case 0x2d: |
706 | /* carriage return */ |
707 | ff_dlog(ctx, "carriage return\n"); |
708 | if (!ctx->real_time) |
709 | reap_screen(ctx, pts); |
710 | roll_up(ctx); |
711 | ctx->cursor_column = 0; |
712 | break; |
713 | case 0x2e: |
714 | /* erase buffered (non displayed) memory */ |
715 | // Only in realtime mode. In buffered mode, we re-use the inactive screen |
716 | // for our own buffering. |
717 | if (ctx->real_time) { |
718 | struct Screen *screen = ctx->screen + !ctx->active_screen; |
719 | screen->row_used = 0; |
720 | } |
721 | break; |
722 | case 0x2f: |
723 | /* end of caption */ |
724 | ff_dlog(ctx, "handle_eoc\n"); |
725 | handle_eoc(ctx, pts); |
726 | break; |
727 | default: |
728 | ff_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo); |
729 | break; |
730 | } |
731 | } else if (hi >= 0x11 && hi <= 0x13) { |
732 | /* Special characters */ |
733 | handle_char(ctx, hi, lo, pts); |
734 | } else if (hi >= 0x20) { |
735 | /* Standard characters (always in pairs) */ |
736 | handle_char(ctx, hi, lo, pts); |
737 | ctx->prev_cmd[0] = ctx->prev_cmd[1] = 0; |
738 | } else if (hi == 0x17 && lo >= 0x21 && lo <= 0x23) { |
739 | int i; |
740 | /* Tab offsets (spacing) */ |
741 | for (i = 0; i < lo - 0x20; i++) { |
742 | handle_char(ctx, ' ', 0, pts); |
743 | } |
744 | } else { |
745 | /* Ignoring all other non data code */ |
746 | ff_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo); |
747 | } |
748 | } |
749 | |
750 | static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt) |
751 | { |
752 | CCaptionSubContext *ctx = avctx->priv_data; |
753 | AVSubtitle *sub = data; |
754 | const int64_t start_time = sub->pts; |
755 | uint8_t *bptr = NULL; |
756 | int len = avpkt->size; |
757 | int ret = 0; |
758 | int i; |
759 | |
760 | av_fast_padded_malloc(&ctx->pktbuf, &ctx->pktbuf_size, len); |
761 | if (!ctx->pktbuf) { |
762 | av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n", len, ctx->pktbuf_size); |
763 | return AVERROR(ENOMEM); |
764 | } |
765 | memcpy(ctx->pktbuf, avpkt->data, len); |
766 | bptr = ctx->pktbuf; |
767 | |
768 | for (i = 0; i < len; i += 3) { |
769 | uint8_t cc_type = *(bptr + i) & 3; |
770 | if (validate_cc_data_pair(bptr + i)) |
771 | continue; |
772 | /* ignoring data field 1 */ |
773 | if(cc_type == 1) |
774 | continue; |
775 | else |
776 | process_cc608(ctx, start_time, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f); |
777 | |
778 | if (!ctx->buffer_changed) |
779 | continue; |
780 | ctx->buffer_changed = 0; |
781 | |
782 | if (*ctx->buffer.str || ctx->real_time) |
783 | { |
784 | ff_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str); |
785 | ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL); |
786 | if (ret < 0) |
787 | return ret; |
788 | sub->pts = ctx->start_time; |
789 | if (!ctx->real_time) |
790 | sub->end_display_time = av_rescale_q(ctx->end_time - ctx->start_time, |
791 | AV_TIME_BASE_Q, ms_tb); |
792 | else |
793 | sub->end_display_time = -1; |
794 | ctx->buffer_changed = 0; |
795 | ctx->last_real_time = sub->pts; |
796 | ctx->screen_touched = 0; |
797 | } |
798 | } |
799 | |
800 | if (ctx->real_time && ctx->screen_touched && |
801 | sub->pts > ctx->last_real_time + av_rescale_q(200, ms_tb, AV_TIME_BASE_Q)) { |
802 | ctx->last_real_time = sub->pts; |
803 | ctx->screen_touched = 0; |
804 | |
805 | capture_screen(ctx); |
806 | ctx->buffer_changed = 0; |
807 | |
808 | ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL); |
809 | if (ret < 0) |
810 | return ret; |
811 | sub->end_display_time = -1; |
812 | } |
813 | |
814 | *got_sub = sub->num_rects > 0; |
815 | return ret; |
816 | } |
817 | |
818 | #define OFFSET(x) offsetof(CCaptionSubContext, x) |
819 | #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM |
820 | static const AVOption options[] = { |
821 | { "real_time", "emit subtitle events as they are decoded for real-time display", OFFSET(real_time), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD }, |
822 | {NULL} |
823 | }; |
824 | |
825 | static const AVClass ccaption_dec_class = { |
826 | .class_name = "Closed caption Decoder", |
827 | .item_name = av_default_item_name, |
828 | .option = options, |
829 | .version = LIBAVUTIL_VERSION_INT, |
830 | }; |
831 | |
832 | AVCodec ff_ccaption_decoder = { |
833 | .name = "cc_dec", |
834 | .long_name = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708)"), |
835 | .type = AVMEDIA_TYPE_SUBTITLE, |
836 | .id = AV_CODEC_ID_EIA_608, |
837 | .priv_data_size = sizeof(CCaptionSubContext), |
838 | .init = init_decoder, |
839 | .close = close_decoder, |
840 | .flush = flush_decoder, |
841 | .decode = decode, |
842 | .priv_class = &ccaption_dec_class, |
843 | }; |
844 |