| 1 | /* |
| 2 | * Teletext decoding for ffmpeg |
| 3 | * Copyright (c) 2005-2010, 2012 Wolfram Gloger |
| 4 | * Copyright (c) 2013 Marton Balint |
| 5 | * |
| 6 | * This library is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public |
| 8 | * License as published by the Free Software Foundation; either |
| 9 | * version 2 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * This library is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with this library; if not, write to the Free Software |
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 19 | */ |
| 20 | |
| 21 | #include "avcodec.h" |
| 22 | #include "libavcodec/ass.h" |
| 23 | #include "libavutil/opt.h" |
| 24 | #include "libavutil/bprint.h" |
| 25 | #include "libavutil/intreadwrite.h" |
| 26 | #include "libavutil/log.h" |
| 27 | |
| 28 | #include <libzvbi.h> |
| 29 | |
| 30 | #define TEXT_MAXSZ (25 * (56 + 1) * 4 + 2) |
| 31 | #define VBI_NB_COLORS 40 |
| 32 | #define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) |
| 33 | #define VBI_R(rgba) (((rgba) >> 0) & 0xFF) |
| 34 | #define VBI_G(rgba) (((rgba) >> 8) & 0xFF) |
| 35 | #define VBI_B(rgba) (((rgba) >> 16) & 0xFF) |
| 36 | #define VBI_A(rgba) (((rgba) >> 24) & 0xFF) |
| 37 | #define MAX_BUFFERED_PAGES 25 |
| 38 | #define BITMAP_CHAR_WIDTH 12 |
| 39 | #define BITMAP_CHAR_HEIGHT 10 |
| 40 | #define MAX_SLICES 64 |
| 41 | |
| 42 | typedef struct TeletextPage |
| 43 | { |
| 44 | AVSubtitleRect *sub_rect; |
| 45 | int pgno; |
| 46 | int subno; |
| 47 | int64_t pts; |
| 48 | } TeletextPage; |
| 49 | |
| 50 | typedef struct TeletextContext |
| 51 | { |
| 52 | AVClass *class; |
| 53 | char *pgno; |
| 54 | int x_offset; |
| 55 | int y_offset; |
| 56 | int format_id; /* 0 = bitmap, 1 = text/ass */ |
| 57 | int chop_top; |
| 58 | int sub_duration; /* in msec */ |
| 59 | int transparent_bg; |
| 60 | int chop_spaces; |
| 61 | |
| 62 | int lines_processed; |
| 63 | TeletextPage *pages; |
| 64 | int nb_pages; |
| 65 | int64_t pts; |
| 66 | int handler_ret; |
| 67 | |
| 68 | vbi_decoder * vbi; |
| 69 | #ifdef DEBUG |
| 70 | vbi_export * ex; |
| 71 | #endif |
| 72 | vbi_sliced sliced[MAX_SLICES]; |
| 73 | } TeletextContext; |
| 74 | |
| 75 | static int chop_spaces_utf8(const unsigned char* t, int len) |
| 76 | { |
| 77 | t += len; |
| 78 | while (len > 0) { |
| 79 | if (*--t != ' ' || (len-1 > 0 && *(t-1) & 0x80)) |
| 80 | break; |
| 81 | --len; |
| 82 | } |
| 83 | return len; |
| 84 | } |
| 85 | |
| 86 | static void subtitle_rect_free(AVSubtitleRect **sub_rect) |
| 87 | { |
| 88 | av_freep(&(*sub_rect)->pict.data[0]); |
| 89 | av_freep(&(*sub_rect)->pict.data[1]); |
| 90 | av_freep(&(*sub_rect)->ass); |
| 91 | av_freep(sub_rect); |
| 92 | } |
| 93 | |
| 94 | static int create_ass_text(TeletextContext *ctx, const char *text, char **ass) |
| 95 | { |
| 96 | int ret; |
| 97 | AVBPrint buf, buf2; |
| 98 | const int ts_start = av_rescale_q(ctx->pts, AV_TIME_BASE_Q, (AVRational){1, 100}); |
| 99 | const int ts_duration = av_rescale_q(ctx->sub_duration, (AVRational){1, 1000}, (AVRational){1, 100}); |
| 100 | |
| 101 | /* First we escape the plain text into buf. */ |
| 102 | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); |
| 103 | ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0); |
| 104 | |
| 105 | if (!av_bprint_is_complete(&buf)) { |
| 106 | av_bprint_finalize(&buf, NULL); |
| 107 | return AVERROR(ENOMEM); |
| 108 | } |
| 109 | |
| 110 | /* Then we create the ass dialog line in buf2 from the escaped text in buf. */ |
| 111 | av_bprint_init(&buf2, 0, AV_BPRINT_SIZE_UNLIMITED); |
| 112 | ff_ass_bprint_dialog(&buf2, buf.str, ts_start, ts_duration, 0); |
| 113 | av_bprint_finalize(&buf, NULL); |
| 114 | |
| 115 | if (!av_bprint_is_complete(&buf2)) { |
| 116 | av_bprint_finalize(&buf2, NULL); |
| 117 | return AVERROR(ENOMEM); |
| 118 | } |
| 119 | |
| 120 | if ((ret = av_bprint_finalize(&buf2, ass)) < 0) |
| 121 | return ret; |
| 122 | |
| 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | /* Draw a page as text */ |
| 127 | static int gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top) |
| 128 | { |
| 129 | const char *in; |
| 130 | AVBPrint buf; |
| 131 | char *vbi_text = av_malloc(TEXT_MAXSZ); |
| 132 | int sz; |
| 133 | |
| 134 | if (!vbi_text) |
| 135 | return AVERROR(ENOMEM); |
| 136 | |
| 137 | sz = vbi_print_page_region(page, vbi_text, TEXT_MAXSZ-1, "UTF-8", |
| 138 | /*table mode*/ TRUE, FALSE, |
| 139 | 0, chop_top, |
| 140 | page->columns, page->rows-chop_top); |
| 141 | if (sz <= 0) { |
| 142 | av_log(ctx, AV_LOG_ERROR, "vbi_print error\n"); |
| 143 | av_free(vbi_text); |
| 144 | return AVERROR_EXTERNAL; |
| 145 | } |
| 146 | vbi_text[sz] = '\0'; |
| 147 | in = vbi_text; |
| 148 | av_bprint_init(&buf, 0, TEXT_MAXSZ); |
| 149 | |
| 150 | if (ctx->chop_spaces) { |
| 151 | for (;;) { |
| 152 | int nl, sz; |
| 153 | |
| 154 | // skip leading spaces and newlines |
| 155 | in += strspn(in, " \n"); |
| 156 | // compute end of row |
| 157 | for (nl = 0; in[nl]; ++nl) |
| 158 | if (in[nl] == '\n' && (nl==0 || !(in[nl-1] & 0x80))) |
| 159 | break; |
| 160 | if (!in[nl]) |
| 161 | break; |
| 162 | // skip trailing spaces |
| 163 | sz = chop_spaces_utf8(in, nl); |
| 164 | av_bprint_append_data(&buf, in, sz); |
| 165 | av_bprintf(&buf, "\n"); |
| 166 | in += nl; |
| 167 | } |
| 168 | } else { |
| 169 | av_bprintf(&buf, "%s\n", vbi_text); |
| 170 | } |
| 171 | av_free(vbi_text); |
| 172 | |
| 173 | if (!av_bprint_is_complete(&buf)) { |
| 174 | av_bprint_finalize(&buf, NULL); |
| 175 | return AVERROR(ENOMEM); |
| 176 | } |
| 177 | |
| 178 | if (buf.len) { |
| 179 | int ret; |
| 180 | sub_rect->type = SUBTITLE_ASS; |
| 181 | if ((ret = create_ass_text(ctx, buf.str, &sub_rect->ass)) < 0) { |
| 182 | av_bprint_finalize(&buf, NULL); |
| 183 | return ret; |
| 184 | } |
| 185 | av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass); |
| 186 | } else { |
| 187 | sub_rect->type = SUBTITLE_NONE; |
| 188 | } |
| 189 | av_bprint_finalize(&buf, NULL); |
| 190 | return 0; |
| 191 | } |
| 192 | |
| 193 | static void fix_transparency(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, |
| 194 | int chop_top, uint8_t transparent_color, int resx, int resy) |
| 195 | { |
| 196 | int iy; |
| 197 | |
| 198 | // Hack for transparency, inspired by VLC code... |
| 199 | for (iy = 0; iy < resy; iy++) { |
| 200 | uint8_t *pixel = sub_rect->pict.data[0] + iy * sub_rect->pict.linesize[0]; |
| 201 | vbi_char *vc = page->text + (iy / BITMAP_CHAR_HEIGHT + chop_top) * page->columns; |
| 202 | vbi_char *vcnext = vc + page->columns; |
| 203 | for (; vc < vcnext; vc++) { |
| 204 | uint8_t *pixelnext = pixel + BITMAP_CHAR_WIDTH; |
| 205 | switch (vc->opacity) { |
| 206 | case VBI_TRANSPARENT_SPACE: |
| 207 | memset(pixel, transparent_color, BITMAP_CHAR_WIDTH); |
| 208 | break; |
| 209 | case VBI_OPAQUE: |
| 210 | case VBI_SEMI_TRANSPARENT: |
| 211 | if (!ctx->transparent_bg) |
| 212 | break; |
| 213 | case VBI_TRANSPARENT_FULL: |
| 214 | for(; pixel < pixelnext; pixel++) |
| 215 | if (*pixel == vc->background) |
| 216 | *pixel = transparent_color; |
| 217 | break; |
| 218 | } |
| 219 | pixel = pixelnext; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | /* Draw a page as bitmap */ |
| 225 | static int gen_sub_bitmap(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top) |
| 226 | { |
| 227 | int resx = page->columns * BITMAP_CHAR_WIDTH; |
| 228 | int resy = (page->rows - chop_top) * BITMAP_CHAR_HEIGHT; |
| 229 | uint8_t ci, cmax = 0; |
| 230 | int ret; |
| 231 | vbi_char *vc = page->text + (chop_top * page->columns); |
| 232 | vbi_char *vcend = page->text + (page->rows * page->columns); |
| 233 | |
| 234 | for (; vc < vcend; vc++) { |
| 235 | if (vc->opacity != VBI_TRANSPARENT_SPACE) { |
| 236 | cmax = VBI_NB_COLORS; |
| 237 | break; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | if (cmax == 0) { |
| 242 | av_log(ctx, AV_LOG_DEBUG, "dropping empty page %3x\n", page->pgno); |
| 243 | sub_rect->type = SUBTITLE_NONE; |
| 244 | return 0; |
| 245 | } |
| 246 | |
| 247 | if ((ret = avpicture_alloc(&sub_rect->pict, AV_PIX_FMT_PAL8, resx, resy)) < 0) |
| 248 | return ret; |
| 249 | // Yes, we want to allocate the palette on our own because AVSubtitle works this way |
| 250 | sub_rect->pict.data[1] = NULL; |
| 251 | |
| 252 | vbi_draw_vt_page_region(page, VBI_PIXFMT_PAL8, |
| 253 | sub_rect->pict.data[0], sub_rect->pict.linesize[0], |
| 254 | 0, chop_top, page->columns, page->rows - chop_top, |
| 255 | /*reveal*/ 1, /*flash*/ 1); |
| 256 | |
| 257 | fix_transparency(ctx, sub_rect, page, chop_top, cmax, resx, resy); |
| 258 | sub_rect->x = ctx->x_offset; |
| 259 | sub_rect->y = ctx->y_offset + chop_top * BITMAP_CHAR_HEIGHT; |
| 260 | sub_rect->w = resx; |
| 261 | sub_rect->h = resy; |
| 262 | sub_rect->nb_colors = (int)cmax + 1; |
| 263 | sub_rect->pict.data[1] = av_mallocz(AVPALETTE_SIZE); |
| 264 | if (!sub_rect->pict.data[1]) { |
| 265 | av_freep(&sub_rect->pict.data[0]); |
| 266 | return AVERROR(ENOMEM); |
| 267 | } |
| 268 | for (ci = 0; ci < cmax; ci++) { |
| 269 | int r, g, b, a; |
| 270 | |
| 271 | r = VBI_R(page->color_map[ci]); |
| 272 | g = VBI_G(page->color_map[ci]); |
| 273 | b = VBI_B(page->color_map[ci]); |
| 274 | a = VBI_A(page->color_map[ci]); |
| 275 | ((uint32_t *)sub_rect->pict.data[1])[ci] = RGBA(r, g, b, a); |
| 276 | av_dlog(ctx, "palette %0x\n", ((uint32_t *)sub_rect->pict.data[1])[ci]); |
| 277 | } |
| 278 | ((uint32_t *)sub_rect->pict.data[1])[cmax] = RGBA(0, 0, 0, 0); |
| 279 | sub_rect->type = SUBTITLE_BITMAP; |
| 280 | return 0; |
| 281 | } |
| 282 | |
| 283 | static void handler(vbi_event *ev, void *user_data) |
| 284 | { |
| 285 | TeletextContext *ctx = user_data; |
| 286 | TeletextPage *new_pages; |
| 287 | vbi_page page; |
| 288 | int res; |
| 289 | char pgno_str[12]; |
| 290 | vbi_subno subno; |
| 291 | vbi_page_type vpt; |
| 292 | int chop_top; |
| 293 | char *lang; |
| 294 | |
| 295 | snprintf(pgno_str, sizeof pgno_str, "%03x", ev->ev.ttx_page.pgno); |
| 296 | av_log(ctx, AV_LOG_DEBUG, "decoded page %s.%02x\n", |
| 297 | pgno_str, ev->ev.ttx_page.subno & 0xFF); |
| 298 | |
| 299 | if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str)) |
| 300 | return; |
| 301 | if (ctx->handler_ret < 0) |
| 302 | return; |
| 303 | |
| 304 | res = vbi_fetch_vt_page(ctx->vbi, &page, |
| 305 | ev->ev.ttx_page.pgno, |
| 306 | ev->ev.ttx_page.subno, |
| 307 | VBI_WST_LEVEL_3p5, 25, TRUE); |
| 308 | |
| 309 | if (!res) |
| 310 | return; |
| 311 | |
| 312 | #ifdef DEBUG |
| 313 | fprintf(stderr, "\nSaving res=%d dy0=%d dy1=%d...\n", |
| 314 | res, page.dirty.y0, page.dirty.y1); |
| 315 | fflush(stderr); |
| 316 | |
| 317 | if (!vbi_export_stdio(ctx->ex, stderr, &page)) |
| 318 | fprintf(stderr, "failed: %s\n", vbi_export_errstr(ctx->ex)); |
| 319 | #endif |
| 320 | |
| 321 | vpt = vbi_classify_page(ctx->vbi, ev->ev.ttx_page.pgno, &subno, &lang); |
| 322 | chop_top = ctx->chop_top || |
| 323 | ((page.rows > 1) && (vpt == VBI_SUBTITLE_PAGE)); |
| 324 | |
| 325 | av_log(ctx, AV_LOG_DEBUG, "%d x %d page chop:%d\n", |
| 326 | page.columns, page.rows, chop_top); |
| 327 | |
| 328 | if (ctx->nb_pages < MAX_BUFFERED_PAGES) { |
| 329 | if ((new_pages = av_realloc_array(ctx->pages, ctx->nb_pages + 1, sizeof(TeletextPage)))) { |
| 330 | TeletextPage *cur_page = new_pages + ctx->nb_pages; |
| 331 | ctx->pages = new_pages; |
| 332 | cur_page->sub_rect = av_mallocz(sizeof(*cur_page->sub_rect)); |
| 333 | cur_page->pts = ctx->pts; |
| 334 | cur_page->pgno = ev->ev.ttx_page.pgno; |
| 335 | cur_page->subno = ev->ev.ttx_page.subno; |
| 336 | if (cur_page->sub_rect) { |
| 337 | res = (ctx->format_id == 0) ? |
| 338 | gen_sub_bitmap(ctx, cur_page->sub_rect, &page, chop_top) : |
| 339 | gen_sub_text (ctx, cur_page->sub_rect, &page, chop_top); |
| 340 | if (res < 0) { |
| 341 | av_freep(&cur_page->sub_rect); |
| 342 | ctx->handler_ret = res; |
| 343 | } else { |
| 344 | ctx->pages[ctx->nb_pages++] = *cur_page; |
| 345 | } |
| 346 | } else { |
| 347 | ctx->handler_ret = AVERROR(ENOMEM); |
| 348 | } |
| 349 | } else { |
| 350 | ctx->handler_ret = AVERROR(ENOMEM); |
| 351 | } |
| 352 | } else { |
| 353 | //TODO: If multiple packets contain more than one page, pages may got queued up, and this may happen... |
| 354 | av_log(ctx, AV_LOG_ERROR, "Buffered too many pages, dropping page %s.\n", pgno_str); |
| 355 | ctx->handler_ret = AVERROR(ENOSYS); |
| 356 | } |
| 357 | |
| 358 | vbi_unref_page(&page); |
| 359 | } |
| 360 | |
| 361 | static inline int data_identifier_is_teletext(int data_identifier) { |
| 362 | /* See EN 301 775 section 4.4.2. */ |
| 363 | return (data_identifier >= 0x10 && data_identifier <= 0x1F || |
| 364 | data_identifier >= 0x99 && data_identifier <= 0x9B); |
| 365 | } |
| 366 | |
| 367 | static int slice_to_vbi_lines(TeletextContext *ctx, uint8_t* buf, int size) |
| 368 | { |
| 369 | int lines = 0; |
| 370 | while (size >= 2 && lines < MAX_SLICES) { |
| 371 | int data_unit_id = buf[0]; |
| 372 | int data_unit_length = buf[1]; |
| 373 | if (data_unit_length + 2 > size) |
| 374 | return AVERROR_INVALIDDATA; |
| 375 | if (data_unit_id == 0x02 || data_unit_id == 0x03) { |
| 376 | if (data_unit_length != 0x2c) |
| 377 | return AVERROR_INVALIDDATA; |
| 378 | else { |
| 379 | int line_offset = buf[2] & 0x1f; |
| 380 | int field_parity = buf[2] & 0x20; |
| 381 | int i; |
| 382 | ctx->sliced[lines].id = VBI_SLICED_TELETEXT_B; |
| 383 | ctx->sliced[lines].line = (line_offset > 0 ? (line_offset + (field_parity ? 0 : 313)) : 0); |
| 384 | for (i = 0; i < 42; i++) |
| 385 | ctx->sliced[lines].data[i] = vbi_rev8(buf[4 + i]); |
| 386 | lines++; |
| 387 | } |
| 388 | } |
| 389 | size -= data_unit_length + 2; |
| 390 | buf += data_unit_length + 2; |
| 391 | } |
| 392 | if (size) |
| 393 | av_log(ctx, AV_LOG_WARNING, "%d bytes remained after slicing data\n", size); |
| 394 | return lines; |
| 395 | } |
| 396 | |
| 397 | static int teletext_decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPacket *pkt) |
| 398 | { |
| 399 | TeletextContext *ctx = avctx->priv_data; |
| 400 | AVSubtitle *sub = data; |
| 401 | int ret = 0; |
| 402 | |
| 403 | if (!ctx->vbi) { |
| 404 | if (!(ctx->vbi = vbi_decoder_new())) |
| 405 | return AVERROR(ENOMEM); |
| 406 | if (!vbi_event_handler_add(ctx->vbi, VBI_EVENT_TTX_PAGE, handler, ctx)) { |
| 407 | vbi_decoder_delete(ctx->vbi); |
| 408 | ctx->vbi = NULL; |
| 409 | return AVERROR(ENOMEM); |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | if (avctx->pkt_timebase.den && pkt->pts != AV_NOPTS_VALUE) |
| 414 | ctx->pts = av_rescale_q(pkt->pts, avctx->pkt_timebase, AV_TIME_BASE_Q); |
| 415 | |
| 416 | if (pkt->size) { |
| 417 | int lines; |
| 418 | const int full_pes_size = pkt->size + 45; /* PES header is 45 bytes */ |
| 419 | |
| 420 | // We allow unreasonably big packets, even if the standard only allows a max size of 1472 |
| 421 | if (full_pes_size < 184 || full_pes_size > 65504 || full_pes_size % 184 != 0) |
| 422 | return AVERROR_INVALIDDATA; |
| 423 | |
| 424 | ctx->handler_ret = pkt->size; |
| 425 | |
| 426 | if (data_identifier_is_teletext(*pkt->data)) { |
| 427 | if ((lines = slice_to_vbi_lines(ctx, pkt->data + 1, pkt->size - 1)) < 0) |
| 428 | return lines; |
| 429 | av_dlog(avctx, "ctx=%p buf_size=%d lines=%u pkt_pts=%7.3f\n", |
| 430 | ctx, pkt->size, lines, (double)pkt->pts/90000.0); |
| 431 | if (lines > 0) { |
| 432 | #ifdef DEBUG |
| 433 | int i; |
| 434 | av_log(avctx, AV_LOG_DEBUG, "line numbers:"); |
| 435 | for(i = 0; i < lines; i++) |
| 436 | av_log(avctx, AV_LOG_DEBUG, " %d", ctx->sliced[i].line); |
| 437 | av_log(avctx, AV_LOG_DEBUG, "\n"); |
| 438 | #endif |
| 439 | vbi_decode(ctx->vbi, ctx->sliced, lines, 0.0); |
| 440 | ctx->lines_processed += lines; |
| 441 | } |
| 442 | } |
| 443 | ctx->pts = AV_NOPTS_VALUE; |
| 444 | ret = ctx->handler_ret; |
| 445 | } |
| 446 | |
| 447 | if (ret < 0) |
| 448 | return ret; |
| 449 | |
| 450 | // is there a subtitle to pass? |
| 451 | if (ctx->nb_pages) { |
| 452 | int i; |
| 453 | sub->format = ctx->format_id; |
| 454 | sub->start_display_time = 0; |
| 455 | sub->end_display_time = ctx->sub_duration; |
| 456 | sub->num_rects = 0; |
| 457 | sub->pts = ctx->pages->pts; |
| 458 | |
| 459 | if (ctx->pages->sub_rect->type != SUBTITLE_NONE) { |
| 460 | sub->rects = av_malloc(sizeof(*sub->rects)); |
| 461 | if (sub->rects) { |
| 462 | sub->num_rects = 1; |
| 463 | sub->rects[0] = ctx->pages->sub_rect; |
| 464 | } else { |
| 465 | ret = AVERROR(ENOMEM); |
| 466 | } |
| 467 | } else { |
| 468 | av_log(avctx, AV_LOG_DEBUG, "sending empty sub\n"); |
| 469 | sub->rects = NULL; |
| 470 | } |
| 471 | if (!sub->rects) // no rect was passed |
| 472 | subtitle_rect_free(&ctx->pages->sub_rect); |
| 473 | |
| 474 | for (i = 0; i < ctx->nb_pages - 1; i++) |
| 475 | ctx->pages[i] = ctx->pages[i + 1]; |
| 476 | ctx->nb_pages--; |
| 477 | |
| 478 | if (ret >= 0) |
| 479 | *data_size = 1; |
| 480 | } else |
| 481 | *data_size = 0; |
| 482 | |
| 483 | return ret; |
| 484 | } |
| 485 | |
| 486 | static int teletext_init_decoder(AVCodecContext *avctx) |
| 487 | { |
| 488 | TeletextContext *ctx = avctx->priv_data; |
| 489 | unsigned int maj, min, rev; |
| 490 | |
| 491 | vbi_version(&maj, &min, &rev); |
| 492 | if (!(maj > 0 || min > 2 || min == 2 && rev >= 26)) { |
| 493 | av_log(avctx, AV_LOG_ERROR, "decoder needs zvbi version >= 0.2.26.\n"); |
| 494 | return AVERROR_EXTERNAL; |
| 495 | } |
| 496 | |
| 497 | if (ctx->format_id == 0) { |
| 498 | avctx->width = 41 * BITMAP_CHAR_WIDTH; |
| 499 | avctx->height = 25 * BITMAP_CHAR_HEIGHT; |
| 500 | } |
| 501 | |
| 502 | ctx->vbi = NULL; |
| 503 | ctx->pts = AV_NOPTS_VALUE; |
| 504 | |
| 505 | #ifdef DEBUG |
| 506 | { |
| 507 | char *t; |
| 508 | ctx->ex = vbi_export_new("text", &t); |
| 509 | } |
| 510 | #endif |
| 511 | av_log(avctx, AV_LOG_VERBOSE, "page filter: %s\n", ctx->pgno); |
| 512 | return (ctx->format_id == 1) ? ff_ass_subtitle_header_default(avctx) : 0; |
| 513 | } |
| 514 | |
| 515 | static int teletext_close_decoder(AVCodecContext *avctx) |
| 516 | { |
| 517 | TeletextContext *ctx = avctx->priv_data; |
| 518 | |
| 519 | av_dlog(avctx, "lines_total=%u\n", ctx->lines_processed); |
| 520 | while (ctx->nb_pages) |
| 521 | subtitle_rect_free(&ctx->pages[--ctx->nb_pages].sub_rect); |
| 522 | av_freep(&ctx->pages); |
| 523 | |
| 524 | vbi_decoder_delete(ctx->vbi); |
| 525 | ctx->vbi = NULL; |
| 526 | ctx->pts = AV_NOPTS_VALUE; |
| 527 | return 0; |
| 528 | } |
| 529 | |
| 530 | static void teletext_flush(AVCodecContext *avctx) |
| 531 | { |
| 532 | teletext_close_decoder(avctx); |
| 533 | } |
| 534 | |
| 535 | #define OFFSET(x) offsetof(TeletextContext, x) |
| 536 | #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM |
| 537 | static const AVOption options[] = { |
| 538 | {"txt_page", "list of teletext page numbers to decode, * is all", OFFSET(pgno), AV_OPT_TYPE_STRING, {.str = "*"}, 0, 0, SD}, |
| 539 | {"txt_chop_top", "discards the top teletext line", OFFSET(chop_top), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
| 540 | {"txt_format", "format of the subtitles (bitmap or text)", OFFSET(format_id), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD, "txt_format"}, |
| 541 | {"bitmap", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, SD, "txt_format"}, |
| 542 | {"text", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, 0, 0, SD, "txt_format"}, |
| 543 | {"txt_left", "x offset of generated bitmaps", OFFSET(x_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
| 544 | {"txt_top", "y offset of generated bitmaps", OFFSET(y_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
| 545 | {"txt_chop_spaces", "chops leading and trailing spaces from text", OFFSET(chop_spaces), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
| 546 | {"txt_duration", "display duration of teletext pages in msecs", OFFSET(sub_duration), AV_OPT_TYPE_INT, {.i64 = 30000}, 0, 86400000, SD}, |
| 547 | {"txt_transparent", "force transparent background of the teletext", OFFSET(transparent_bg), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD}, |
| 548 | { NULL }, |
| 549 | }; |
| 550 | |
| 551 | static const AVClass teletext_class = { |
| 552 | .class_name = "libzvbi_teletextdec", |
| 553 | .item_name = av_default_item_name, |
| 554 | .option = options, |
| 555 | .version = LIBAVUTIL_VERSION_INT, |
| 556 | }; |
| 557 | |
| 558 | AVCodec ff_libzvbi_teletext_decoder = { |
| 559 | .name = "libzvbi_teletextdec", |
| 560 | .long_name = NULL_IF_CONFIG_SMALL("Libzvbi DVB teletext decoder"), |
| 561 | .type = AVMEDIA_TYPE_SUBTITLE, |
| 562 | .id = AV_CODEC_ID_DVB_TELETEXT, |
| 563 | .priv_data_size = sizeof(TeletextContext), |
| 564 | .init = teletext_init_decoder, |
| 565 | .close = teletext_close_decoder, |
| 566 | .decode = teletext_decode_frame, |
| 567 | .capabilities = CODEC_CAP_DELAY, |
| 568 | .flush = teletext_flush, |
| 569 | .priv_class= &teletext_class, |
| 570 | }; |