Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||
19 | */ |
||
20 | |||
21 | #include "avcodec.h" |
||
22 | #include "libavutil/opt.h" |
||
23 | #include "libavutil/intreadwrite.h" |
||
24 | |||
25 | #include |
||
26 | |||
27 | #define TEXT_MAXSZ (25 * (56 + 1) * 4 + 2) |
||
28 | #define VBI_NB_COLORS 40 |
||
29 | #define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) |
||
30 | #define VBI_R(rgba) (((rgba) >> 0) & 0xFF) |
||
31 | #define VBI_G(rgba) (((rgba) >> 8) & 0xFF) |
||
32 | #define VBI_B(rgba) (((rgba) >> 16) & 0xFF) |
||
33 | #define VBI_A(rgba) (((rgba) >> 24) & 0xFF) |
||
34 | |||
35 | /* main data structure */ |
||
36 | typedef struct TeletextContext |
||
37 | { |
||
38 | AVClass *class; |
||
39 | char *pgno; |
||
40 | int x_offset; |
||
41 | int y_offset; |
||
42 | char *format; |
||
43 | int format_id; /* 0 = bitmap, 1 = text */ |
||
44 | int chop_top; |
||
45 | int sub_duration; /* in msec */ |
||
46 | int transparent_bg; |
||
47 | int chop_spaces; |
||
48 | |||
49 | int lines_processed; |
||
50 | AVSubtitleRect *sub_rect; |
||
51 | |||
52 | vbi_decoder * vbi; |
||
53 | vbi_dvb_demux * dx; |
||
54 | #ifdef DEBUG |
||
55 | vbi_export * ex; |
||
56 | #endif |
||
57 | /* Don't even _think_ about making sliced stack-local! */ |
||
58 | vbi_sliced sliced[64]; |
||
59 | } TeletextContext; |
||
60 | |||
61 | /************************************************************************/ |
||
62 | |||
63 | static int |
||
64 | chop_spaces_utf8(const unsigned char* t, int len) |
||
65 | { |
||
66 | t += len; |
||
67 | while (len > 0) { |
||
68 | if (*--t != ' ' || (len-1 > 0 && *(t-1) & 0x80)) |
||
69 | break; |
||
70 | --len; |
||
71 | } |
||
72 | return len; |
||
73 | } |
||
74 | |||
75 | // draw a page as text |
||
76 | static int |
||
77 | gen_sub_text(TeletextContext *ctx, vbi_page *page, int chop_top) |
||
78 | { |
||
79 | AVSubtitleRect *sub_rect = ctx->sub_rect; |
||
80 | char *text; |
||
81 | const char *in; |
||
82 | char *out; |
||
83 | char *vbi_text = av_malloc(TEXT_MAXSZ); |
||
84 | int sz; |
||
85 | |||
86 | if (!vbi_text) |
||
87 | return AVERROR(ENOMEM); |
||
88 | |||
89 | sz = vbi_print_page_region(page, vbi_text, TEXT_MAXSZ-1, "UTF-8", |
||
90 | /*table mode*/ TRUE, FALSE, |
||
91 | 0, chop_top, |
||
92 | page->columns, page->rows-chop_top); |
||
93 | if (sz <= 0) { |
||
94 | av_log(ctx, AV_LOG_ERROR, "vbi_print error\n"); |
||
95 | av_free(vbi_text); |
||
96 | return AVERROR_EXTERNAL; |
||
97 | } |
||
98 | vbi_text[sz] = '\0'; |
||
99 | in = vbi_text; |
||
100 | out = text = av_malloc(TEXT_MAXSZ); |
||
101 | if (!text) { |
||
102 | av_free(vbi_text); |
||
103 | return AVERROR(ENOMEM); |
||
104 | } |
||
105 | if (ctx->chop_spaces) { |
||
106 | for (;;) { |
||
107 | int nl, sz; |
||
108 | |||
109 | // skip leading spaces and newlines |
||
110 | in += strspn(in, " \n"); |
||
111 | // compute end of row |
||
112 | for (nl = 0; in[nl]; ++nl) |
||
113 | if (in[nl] == '\n' && (nl==0 || !(in[nl-1] & 0x80))) |
||
114 | break; |
||
115 | if (!in[nl]) |
||
116 | break; |
||
117 | // skip trailing spaces |
||
118 | sz = chop_spaces_utf8(in, nl); |
||
119 | memcpy(out, in, sz); |
||
120 | out += sz; |
||
121 | *out++ = '\n'; |
||
122 | in += nl; |
||
123 | } |
||
124 | } else { |
||
125 | strcpy(text, vbi_text); |
||
126 | out += sz; |
||
127 | *out++ = '\n'; |
||
128 | } |
||
129 | av_free(vbi_text); |
||
130 | *out = '\0'; |
||
131 | if (out > text) { |
||
132 | sub_rect->type = SUBTITLE_TEXT; |
||
133 | sub_rect->text = text; |
||
134 | av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", text); |
||
135 | } else { |
||
136 | sub_rect->type = SUBTITLE_NONE; |
||
137 | av_free(text); |
||
138 | } |
||
139 | return 0; |
||
140 | } |
||
141 | |||
142 | static void |
||
143 | fix_transparency(TeletextContext *ctx, vbi_page *page, int chop_top, uint8_t transparent_color, int resx, int resy) |
||
144 | { |
||
145 | AVSubtitleRect *sub_rect = ctx->sub_rect; |
||
146 | int iy; |
||
147 | |||
148 | // Hack for transparency, inspired by VLC code... |
||
149 | for (iy = 0; iy < resy; iy++) { |
||
150 | uint8_t *pixel = sub_rect->pict.data[0] + iy * sub_rect->pict.linesize[0]; |
||
151 | vbi_char *vc = page->text + (iy / 10 + chop_top) * page->columns; |
||
152 | vbi_char *vcnext = vc + page->columns; |
||
153 | for (; vc < vcnext; vc++) { |
||
154 | uint8_t *pixelnext = pixel + 12; |
||
155 | switch (vc->opacity) { |
||
156 | case VBI_TRANSPARENT_SPACE: |
||
157 | memset(pixel, transparent_color, 12); |
||
158 | break; |
||
159 | case VBI_OPAQUE: |
||
160 | case VBI_SEMI_TRANSPARENT: |
||
161 | if (!ctx->transparent_bg) |
||
162 | break; |
||
163 | case VBI_TRANSPARENT_FULL: |
||
164 | for(; pixel < pixelnext; pixel++) |
||
165 | if (*pixel == vc->background) |
||
166 | *pixel = transparent_color; |
||
167 | break; |
||
168 | } |
||
169 | pixel = pixelnext; |
||
170 | } |
||
171 | } |
||
172 | } |
||
173 | |||
174 | // draw a page as bitmap |
||
175 | static int |
||
176 | gen_sub_bitmap(TeletextContext *ctx, vbi_page *page, int chop_top) |
||
177 | { |
||
178 | AVSubtitleRect *sub_rect = ctx->sub_rect; |
||
179 | int resx = page->columns * 12; |
||
180 | int resy = (page->rows - chop_top) * 10; |
||
181 | uint8_t ci, cmax = 0; |
||
182 | int ret; |
||
183 | vbi_char *vc = page->text + (chop_top * page->columns); |
||
184 | vbi_char *vcend = page->text + (page->rows * page->columns); |
||
185 | |||
186 | for (; vc < vcend; vc++) { |
||
187 | if (vc->opacity != VBI_TRANSPARENT_SPACE) { |
||
188 | cmax = VBI_NB_COLORS; |
||
189 | break; |
||
190 | } |
||
191 | } |
||
192 | |||
193 | if (cmax == 0) { |
||
194 | av_log(ctx, AV_LOG_DEBUG, "dropping empty page %3x\n", page->pgno); |
||
195 | sub_rect->type = SUBTITLE_NONE; |
||
196 | return 0; |
||
197 | } |
||
198 | |||
199 | if ((ret = avpicture_alloc(&sub_rect->pict, AV_PIX_FMT_PAL8, resx, resy)) < 0) |
||
200 | return ret; |
||
201 | // Yes, we want to allocate the palette on our own because AVSubtitle works this way |
||
202 | sub_rect->pict.data[1] = NULL; |
||
203 | |||
204 | vbi_draw_vt_page_region(page, VBI_PIXFMT_PAL8, |
||
205 | sub_rect->pict.data[0], sub_rect->pict.linesize[0], |
||
206 | 0, chop_top, page->columns, page->rows - chop_top, |
||
207 | /*reveal*/ 1, /*flash*/ 1); |
||
208 | |||
209 | fix_transparency(ctx, page, chop_top, cmax, resx, resy); |
||
210 | sub_rect->x = ctx->x_offset; |
||
211 | sub_rect->y = ctx->y_offset; |
||
212 | sub_rect->w = resx; |
||
213 | sub_rect->h = resy; |
||
214 | sub_rect->nb_colors = (int)cmax + 1; |
||
215 | sub_rect->pict.data[1] = av_mallocz(AVPALETTE_SIZE); |
||
216 | if (!sub_rect->pict.data[1]) { |
||
217 | av_freep(&sub_rect->pict.data[0]); |
||
218 | return AVERROR(ENOMEM); |
||
219 | } |
||
220 | for (ci = 0; ci < cmax; ci++) { |
||
221 | int r, g, b, a; |
||
222 | |||
223 | r = VBI_R(page->color_map[ci]); |
||
224 | g = VBI_G(page->color_map[ci]); |
||
225 | b = VBI_B(page->color_map[ci]); |
||
226 | a = VBI_A(page->color_map[ci]); |
||
227 | ((uint32_t *)sub_rect->pict.data[1])[ci] = RGBA(r, g, b, a); |
||
228 | #ifdef DEBUG |
||
229 | av_log(ctx, AV_LOG_DEBUG, "palette %0x\n", |
||
230 | ((uint32_t *)sub_rect->pict.data[1])[ci]); |
||
231 | #endif |
||
232 | } |
||
233 | ((uint32_t *)sub_rect->pict.data[1])[cmax] = RGBA(0, 0, 0, 0); |
||
234 | sub_rect->type = SUBTITLE_BITMAP; |
||
235 | return 0; |
||
236 | } |
||
237 | |||
238 | static void |
||
239 | handler(vbi_event *ev, void *user_data) |
||
240 | { |
||
241 | TeletextContext *ctx = user_data; |
||
242 | vbi_page page; |
||
243 | int res; |
||
244 | char pgno_str[12]; |
||
245 | vbi_subno subno; |
||
246 | vbi_page_type vpt; |
||
247 | int chop_top; |
||
248 | char *lang; |
||
249 | |||
250 | snprintf(pgno_str, sizeof pgno_str, "%03x", ev->ev.ttx_page.pgno); |
||
251 | av_log(ctx, AV_LOG_DEBUG, "decoded page %s.%02x\n", |
||
252 | pgno_str, ev->ev.ttx_page.subno & 0xFF); |
||
253 | |||
254 | if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str)) |
||
255 | return; |
||
256 | |||
257 | /* Fetch the page. */ |
||
258 | res = vbi_fetch_vt_page(ctx->vbi, &page, |
||
259 | ev->ev.ttx_page.pgno, |
||
260 | ev->ev.ttx_page.subno, |
||
261 | VBI_WST_LEVEL_3p5, 25, TRUE); |
||
262 | |||
263 | if (!res) |
||
264 | return; |
||
265 | |||
266 | #ifdef DEBUG |
||
267 | fprintf(stderr, "\nSaving res=%d dy0=%d dy1=%d...\n", |
||
268 | res, page.dirty.y0, page.dirty.y1); |
||
269 | fflush(stderr); |
||
270 | |||
271 | if (!vbi_export_stdio(ctx->ex, stderr, &page)) |
||
272 | fprintf(stderr, "failed: %s\n", vbi_export_errstr(ctx->ex)); |
||
273 | #endif |
||
274 | |||
275 | vpt = vbi_classify_page(ctx->vbi, ev->ev.ttx_page.pgno, &subno, &lang); |
||
276 | chop_top = ctx->chop_top || |
||
277 | ((page.rows > 1) && (vpt == VBI_SUBTITLE_PAGE)); |
||
278 | |||
279 | av_log(ctx, AV_LOG_DEBUG, "%d x %d page chop:%d\n", |
||
280 | page.columns, page.rows, chop_top); |
||
281 | |||
282 | if (!ctx->sub_rect) { |
||
283 | ctx->sub_rect = av_mallocz(sizeof(*ctx->sub_rect)); |
||
284 | if (ctx->sub_rect) { |
||
285 | res = (ctx->format_id == 0) ? |
||
286 | gen_sub_bitmap(ctx, &page, chop_top) : |
||
287 | gen_sub_text (ctx, &page, chop_top); |
||
288 | if (res) |
||
289 | av_freep(&ctx->sub_rect); |
||
290 | } |
||
291 | } else { |
||
292 | // FIXME: Multiple teletext pages in a single packet, some kind of buffering should be done instead of dropping the page... |
||
293 | av_log(ctx, AV_LOG_WARNING, "Missed page %s.%02x.\n", pgno_str, ev->ev.ttx_page.subno & 0xFF); |
||
294 | } |
||
295 | |||
296 | vbi_unref_page(&page); |
||
297 | } |
||
298 | |||
299 | static int |
||
300 | teletext_decode_frame(AVCodecContext *avctx, |
||
301 | void *data, int *data_size, |
||
302 | AVPacket *pkt) |
||
303 | { |
||
304 | TeletextContext *ctx = avctx->priv_data; |
||
305 | AVSubtitle *sub = data; |
||
306 | const uint8_t *buf = pkt->data; |
||
307 | unsigned int left = pkt->size; |
||
308 | uint8_t pesheader[45] = {0x00, 0x00, 0x01, 0xbd, 0x00, 0x00, 0x85, 0x80, 0x24, 0x21, 0x00, 0x01, 0x00, 0x01}; |
||
309 | int pesheader_size = sizeof(pesheader); |
||
310 | const uint8_t *pesheader_buf = pesheader; |
||
311 | |||
312 | if (!ctx->vbi) { |
||
313 | if (!(ctx->vbi = vbi_decoder_new())) |
||
314 | return AVERROR(ENOMEM); |
||
315 | if (!vbi_event_handler_add(ctx->vbi, VBI_EVENT_TTX_PAGE, handler, ctx)) { |
||
316 | vbi_decoder_delete(ctx->vbi); |
||
317 | ctx->vbi = NULL; |
||
318 | return AVERROR(ENOMEM); |
||
319 | } |
||
320 | } |
||
321 | if (!ctx->dx && (!(ctx->dx = vbi_dvb_pes_demux_new (/* callback */ NULL, NULL)))) |
||
322 | return AVERROR(ENOMEM); |
||
323 | |||
324 | // We allow unreasonably big packets, even if the standard only allows a max size of 1472 |
||
325 | if ((pesheader_size + left) < 184 || (pesheader_size + left) > 65504 || (pesheader_size + left) % 184 != 0) |
||
326 | return AVERROR_INVALIDDATA; |
||
327 | |||
328 | memset(pesheader + 14, 0xff, pesheader_size - 14); |
||
329 | AV_WB16(pesheader + 4, left + pesheader_size - 6); |
||
330 | |||
331 | /* PTS is deliberately left as 0 in the PES header, otherwise libzvbi uses |
||
332 | * it to detect dropped frames. Unforunatey the guessed packet PTS values |
||
333 | * (see mpegts demuxer) are not accurate enough to pass that test. */ |
||
334 | vbi_dvb_demux_cor(ctx->dx, ctx->sliced, 64, NULL, &pesheader_buf, &pesheader_size); |
||
335 | |||
336 | while (left > 0) { |
||
337 | int64_t pts = 0; |
||
338 | unsigned int lines = vbi_dvb_demux_cor(ctx->dx, ctx->sliced, 64, &pts, &buf, &left); |
||
339 | #ifdef DEBUG |
||
340 | av_log(avctx, AV_LOG_DEBUG, |
||
341 | "ctx=%p buf_size=%d left=%u lines=%u pts=%f pkt_pts=%f\n", |
||
342 | ctx, pkt->size, left, lines, (double)pts/90000.0, (double)pkt->pts/90000.0); |
||
343 | #endif |
||
344 | if (lines > 0) { |
||
345 | #ifdef DEBUGx |
||
346 | int i; |
||
347 | for(i=0; i |
||
348 | av_log(avctx, AV_LOG_DEBUG, |
||
349 | "lines=%d id=%x\n", i, ctx->sliced[i].id); |
||
350 | #endif |
||
351 | vbi_decode(ctx->vbi, ctx->sliced, lines, (double)pts/90000.0); |
||
352 | ctx->lines_processed += lines; |
||
353 | } |
||
354 | } |
||
355 | |||
356 | // is there a subtitle to pass? |
||
357 | if (ctx->sub_rect) { |
||
358 | sub->format = (ctx->sub_rect->type == SUBTITLE_TEXT ? 1: 0); |
||
359 | sub->start_display_time = 0; |
||
360 | sub->end_display_time = ctx->sub_duration; |
||
361 | sub->num_rects = 0; |
||
362 | |||
363 | if (ctx->sub_rect->type != SUBTITLE_NONE) { |
||
364 | sub->rects = av_malloc(sizeof(*sub->rects) * 1); |
||
365 | if (sub->rects) { |
||
366 | sub->num_rects = 1; |
||
367 | sub->rects[0] = ctx->sub_rect; |
||
368 | } |
||
369 | } else { |
||
370 | av_log(avctx, AV_LOG_DEBUG, "sending empty sub\n"); |
||
371 | sub->rects = NULL; |
||
372 | } |
||
373 | if (!sub->rects) // no rect was passed |
||
374 | av_free(ctx->sub_rect); |
||
375 | ctx->sub_rect = NULL; |
||
376 | |||
377 | *data_size = 1; |
||
378 | } else |
||
379 | *data_size = 0; |
||
380 | |||
381 | return pkt->size; |
||
382 | } |
||
383 | |||
384 | static int teletext_init_decoder(AVCodecContext *avctx) |
||
385 | { |
||
386 | TeletextContext *ctx = avctx->priv_data; |
||
387 | unsigned int maj, min, rev; |
||
388 | |||
389 | vbi_version(&maj, &min, &rev); |
||
390 | if (!(maj > 0 || min > 2 || min == 2 && rev >= 26)) { |
||
391 | av_log(avctx, AV_LOG_ERROR, "decoder needs zvbi version >= 0.2.26.\n"); |
||
392 | return AVERROR_EXTERNAL; |
||
393 | } |
||
394 | |||
395 | ctx->dx = NULL; |
||
396 | ctx->vbi = NULL; |
||
397 | ctx->sub_rect = NULL; |
||
398 | if (!strcmp(ctx->format, "bitmap")) { |
||
399 | ctx->format_id = 0; |
||
400 | } else if (!strcmp(ctx->format, "text")) { |
||
401 | ctx->format_id = 1; |
||
402 | } else { |
||
403 | av_log(avctx, AV_LOG_ERROR, "unkown format %s\n", ctx->format); |
||
404 | return AVERROR_OPTION_NOT_FOUND; |
||
405 | } |
||
406 | |||
407 | #ifdef DEBUG |
||
408 | { |
||
409 | char *t; |
||
410 | ctx->ex = vbi_export_new("text", &t); |
||
411 | } |
||
412 | #endif |
||
413 | av_log(avctx, AV_LOG_VERBOSE, "page filter: %s\n", ctx->pgno); |
||
414 | return 0; |
||
415 | } |
||
416 | |||
417 | static int teletext_close_decoder(AVCodecContext *avctx) |
||
418 | { |
||
419 | TeletextContext *ctx = avctx->priv_data; |
||
420 | |||
421 | #ifdef DEBUG |
||
422 | av_log(avctx, AV_LOG_DEBUG, "lines_total=%u\n", ctx->lines_processed); |
||
423 | #endif |
||
424 | |||
425 | vbi_dvb_demux_delete(ctx->dx); |
||
426 | vbi_decoder_delete(ctx->vbi); |
||
427 | ctx->dx = NULL; |
||
428 | ctx->vbi = NULL; |
||
429 | return 0; |
||
430 | } |
||
431 | |||
432 | static void teletext_flush(AVCodecContext *avctx) |
||
433 | { |
||
434 | teletext_close_decoder(avctx); |
||
435 | } |
||
436 | |||
437 | #define OFFSET(x) offsetof(TeletextContext, x) |
||
438 | #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM |
||
439 | static const AVOption options[] = { |
||
440 | {"txt_page", "list of teletext page numbers to decode, * is all", OFFSET(pgno), AV_OPT_TYPE_STRING, {.str = "*"}, 0, 0, SD}, |
||
441 | {"txt_chop_top", "discards the top teletext line", OFFSET(chop_top), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
||
442 | {"txt_format", "format of the subtitles (bitmap or text)", OFFSET(format), AV_OPT_TYPE_STRING, {.str = "bitmap"}, 0, 0, SD}, |
||
443 | {"txt_left", "x offset of generated bitmaps", OFFSET(x_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
||
444 | {"txt_top", "y offset of generated bitmaps", OFFSET(y_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
||
445 | {"txt_chop_spaces", "chops leading and trailing spaces from text", OFFSET(chop_spaces), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
||
446 | {"txt_duration", "display duration of teletext pages in msecs", OFFSET(sub_duration), AV_OPT_TYPE_INT, {.i64 = 30000}, 0, 86400000, SD}, |
||
447 | {"txt_transparent", "force transparent background of the teletext", OFFSET(transparent_bg), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD}, |
||
448 | { NULL }, |
||
449 | }; |
||
450 | |||
451 | static const AVClass teletext_class = { |
||
452 | .class_name = "libzvbi_teletextdec", |
||
453 | .item_name = av_default_item_name, |
||
454 | .option = options, |
||
455 | .version = LIBAVUTIL_VERSION_INT, |
||
456 | }; |
||
457 | |||
458 | AVCodec ff_libzvbi_teletext_decoder = { |
||
459 | .name = "libzvbi_teletextdec", |
||
460 | .long_name = NULL_IF_CONFIG_SMALL("Libzvbi DVB teletext decoder"), |
||
461 | .type = AVMEDIA_TYPE_SUBTITLE, |
||
462 | .id = CODEC_ID_DVB_TELETEXT, |
||
463 | .priv_data_size = sizeof(TeletextContext), |
||
464 | .init = teletext_init_decoder, |
||
465 | .close = teletext_close_decoder, |
||
466 | .decode = teletext_decode_frame, |
||
467 | .flush = teletext_flush, |
||
468 | .priv_class= &teletext_class, |
||
469 | };>>>>>>>=>><>><>><> |