1
mirror of https://git.videolan.org/git/ffmpeg.git synced 2024-08-19 07:45:05 +02:00
ffmpeg/libavcodec/libzvbi-teletextdec.c
Marton Balint a494757b34 libzvbi-teletextdec: support multiple teletext pages in a single packet
After this patch, if a packet contains multiple teletext pages, the teletext
decoder can return the fist page and store the remaining pages in memory, and
return them to the user on the next calls to avcodec_decode_subtitle2.

Signed-off-by: Marton Balint <cus@passwd.hu>
2013-11-10 18:57:51 +01:00

517 lines
17 KiB
C

/*
* Teletext decoding for ffmpeg
* Copyright (c) 2005-2010, 2012 Wolfram Gloger
* Copyright (c) 2013 Marton Balint
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "avcodec.h"
#include "libavutil/opt.h"
#include "libavutil/intreadwrite.h"
#include <libzvbi.h>
#define TEXT_MAXSZ (25 * (56 + 1) * 4 + 2)
#define VBI_NB_COLORS 40
#define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
#define VBI_R(rgba) (((rgba) >> 0) & 0xFF)
#define VBI_G(rgba) (((rgba) >> 8) & 0xFF)
#define VBI_B(rgba) (((rgba) >> 16) & 0xFF)
#define VBI_A(rgba) (((rgba) >> 24) & 0xFF)
#define MAX_BUFFERED_PAGES 25
typedef struct TeletextPage
{
AVSubtitleRect *sub_rect;
int pgno;
int subno;
int64_t pts;
} TeletextPage;
/* main data structure */
typedef struct TeletextContext
{
AVClass *class;
char *pgno;
int x_offset;
int y_offset;
char *format;
int format_id; /* 0 = bitmap, 1 = text */
int chop_top;
int sub_duration; /* in msec */
int transparent_bg;
int chop_spaces;
int lines_processed;
TeletextPage *pages;
int nb_pages;
int64_t pts;
vbi_decoder * vbi;
vbi_dvb_demux * dx;
#ifdef DEBUG
vbi_export * ex;
#endif
/* Don't even _think_ about making sliced stack-local! */
vbi_sliced sliced[64];
} TeletextContext;
/************************************************************************/
static int
chop_spaces_utf8(const unsigned char* t, int len)
{
t += len;
while (len > 0) {
if (*--t != ' ' || (len-1 > 0 && *(t-1) & 0x80))
break;
--len;
}
return len;
}
static void
subtitle_rect_free(AVSubtitleRect **sub_rect)
{
av_freep(&(*sub_rect)->pict.data[0]);
av_freep(&(*sub_rect)->pict.data[1]);
av_freep(&(*sub_rect)->text);
av_freep(sub_rect);
}
// draw a page as text
static int
gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top)
{
char *text;
const char *in;
char *out;
char *vbi_text = av_malloc(TEXT_MAXSZ);
int sz;
if (!vbi_text)
return AVERROR(ENOMEM);
sz = vbi_print_page_region(page, vbi_text, TEXT_MAXSZ-1, "UTF-8",
/*table mode*/ TRUE, FALSE,
0, chop_top,
page->columns, page->rows-chop_top);
if (sz <= 0) {
av_log(ctx, AV_LOG_ERROR, "vbi_print error\n");
av_free(vbi_text);
return AVERROR_EXTERNAL;
}
vbi_text[sz] = '\0';
in = vbi_text;
out = text = av_malloc(TEXT_MAXSZ);
if (!text) {
av_free(vbi_text);
return AVERROR(ENOMEM);
}
if (ctx->chop_spaces) {
for (;;) {
int nl, sz;
// skip leading spaces and newlines
in += strspn(in, " \n");
// compute end of row
for (nl = 0; in[nl]; ++nl)
if (in[nl] == '\n' && (nl==0 || !(in[nl-1] & 0x80)))
break;
if (!in[nl])
break;
// skip trailing spaces
sz = chop_spaces_utf8(in, nl);
memcpy(out, in, sz);
out += sz;
*out++ = '\n';
in += nl;
}
} else {
strcpy(text, vbi_text);
out += sz;
*out++ = '\n';
}
av_free(vbi_text);
*out = '\0';
if (out > text) {
sub_rect->type = SUBTITLE_TEXT;
sub_rect->text = text;
av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", text);
} else {
sub_rect->type = SUBTITLE_NONE;
av_free(text);
}
return 0;
}
static void
fix_transparency(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top, uint8_t transparent_color, int resx, int resy)
{
int iy;
// Hack for transparency, inspired by VLC code...
for (iy = 0; iy < resy; iy++) {
uint8_t *pixel = sub_rect->pict.data[0] + iy * sub_rect->pict.linesize[0];
vbi_char *vc = page->text + (iy / 10 + chop_top) * page->columns;
vbi_char *vcnext = vc + page->columns;
for (; vc < vcnext; vc++) {
uint8_t *pixelnext = pixel + 12;
switch (vc->opacity) {
case VBI_TRANSPARENT_SPACE:
memset(pixel, transparent_color, 12);
break;
case VBI_OPAQUE:
case VBI_SEMI_TRANSPARENT:
if (!ctx->transparent_bg)
break;
case VBI_TRANSPARENT_FULL:
for(; pixel < pixelnext; pixel++)
if (*pixel == vc->background)
*pixel = transparent_color;
break;
}
pixel = pixelnext;
}
}
}
// draw a page as bitmap
static int
gen_sub_bitmap(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top)
{
int resx = page->columns * 12;
int resy = (page->rows - chop_top) * 10;
uint8_t ci, cmax = 0;
int ret;
vbi_char *vc = page->text + (chop_top * page->columns);
vbi_char *vcend = page->text + (page->rows * page->columns);
for (; vc < vcend; vc++) {
if (vc->opacity != VBI_TRANSPARENT_SPACE) {
cmax = VBI_NB_COLORS;
break;
}
}
if (cmax == 0) {
av_log(ctx, AV_LOG_DEBUG, "dropping empty page %3x\n", page->pgno);
sub_rect->type = SUBTITLE_NONE;
return 0;
}
if ((ret = avpicture_alloc(&sub_rect->pict, AV_PIX_FMT_PAL8, resx, resy)) < 0)
return ret;
// Yes, we want to allocate the palette on our own because AVSubtitle works this way
sub_rect->pict.data[1] = NULL;
vbi_draw_vt_page_region(page, VBI_PIXFMT_PAL8,
sub_rect->pict.data[0], sub_rect->pict.linesize[0],
0, chop_top, page->columns, page->rows - chop_top,
/*reveal*/ 1, /*flash*/ 1);
fix_transparency(ctx, sub_rect, page, chop_top, cmax, resx, resy);
sub_rect->x = ctx->x_offset;
sub_rect->y = ctx->y_offset;
sub_rect->w = resx;
sub_rect->h = resy;
sub_rect->nb_colors = (int)cmax + 1;
sub_rect->pict.data[1] = av_mallocz(AVPALETTE_SIZE);
if (!sub_rect->pict.data[1]) {
av_freep(&sub_rect->pict.data[0]);
return AVERROR(ENOMEM);
}
for (ci = 0; ci < cmax; ci++) {
int r, g, b, a;
r = VBI_R(page->color_map[ci]);
g = VBI_G(page->color_map[ci]);
b = VBI_B(page->color_map[ci]);
a = VBI_A(page->color_map[ci]);
((uint32_t *)sub_rect->pict.data[1])[ci] = RGBA(r, g, b, a);
#ifdef DEBUG
av_log(ctx, AV_LOG_DEBUG, "palette %0x\n",
((uint32_t *)sub_rect->pict.data[1])[ci]);
#endif
}
((uint32_t *)sub_rect->pict.data[1])[cmax] = RGBA(0, 0, 0, 0);
sub_rect->type = SUBTITLE_BITMAP;
return 0;
}
static void
handler(vbi_event *ev, void *user_data)
{
TeletextContext *ctx = user_data;
TeletextPage *new_pages;
vbi_page page;
int res;
char pgno_str[12];
vbi_subno subno;
vbi_page_type vpt;
int chop_top;
char *lang;
snprintf(pgno_str, sizeof pgno_str, "%03x", ev->ev.ttx_page.pgno);
av_log(ctx, AV_LOG_DEBUG, "decoded page %s.%02x\n",
pgno_str, ev->ev.ttx_page.subno & 0xFF);
if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str))
return;
/* Fetch the page. */
res = vbi_fetch_vt_page(ctx->vbi, &page,
ev->ev.ttx_page.pgno,
ev->ev.ttx_page.subno,
VBI_WST_LEVEL_3p5, 25, TRUE);
if (!res)
return;
#ifdef DEBUG
fprintf(stderr, "\nSaving res=%d dy0=%d dy1=%d...\n",
res, page.dirty.y0, page.dirty.y1);
fflush(stderr);
if (!vbi_export_stdio(ctx->ex, stderr, &page))
fprintf(stderr, "failed: %s\n", vbi_export_errstr(ctx->ex));
#endif
vpt = vbi_classify_page(ctx->vbi, ev->ev.ttx_page.pgno, &subno, &lang);
chop_top = ctx->chop_top ||
((page.rows > 1) && (vpt == VBI_SUBTITLE_PAGE));
av_log(ctx, AV_LOG_DEBUG, "%d x %d page chop:%d\n",
page.columns, page.rows, chop_top);
if (ctx->nb_pages < MAX_BUFFERED_PAGES) {
if ((new_pages = av_realloc_array(ctx->pages, ctx->nb_pages + 1, sizeof(TeletextPage)))) {
TeletextPage *cur_page = new_pages + ctx->nb_pages;
ctx->pages = new_pages;
cur_page->sub_rect = av_mallocz(sizeof(*cur_page->sub_rect));
cur_page->pts = ctx->pts;
cur_page->pgno = ev->ev.ttx_page.pgno;
cur_page->subno = ev->ev.ttx_page.subno;
if (cur_page->sub_rect) {
res = (ctx->format_id == 0) ?
gen_sub_bitmap(ctx, cur_page->sub_rect, &page, chop_top) :
gen_sub_text (ctx, cur_page->sub_rect, &page, chop_top);
if (res)
av_freep(&cur_page->sub_rect);
else
ctx->pages[ctx->nb_pages++] = *cur_page;
}
} else {
av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory to to buffer pages\n");
}
} else {
//TODO: If multiple packets contain more than one page, pages may got queued up, and this may happen...
av_log(ctx, AV_LOG_ERROR, "Buffered too many pages, dropping page %s.\n", pgno_str);
}
vbi_unref_page(&page);
}
static int
teletext_decode_frame(AVCodecContext *avctx,
void *data, int *data_size,
AVPacket *pkt)
{
TeletextContext *ctx = avctx->priv_data;
AVSubtitle *sub = data;
const uint8_t *buf = pkt->data;
int left = pkt->size;
uint8_t pesheader[45] = {0x00, 0x00, 0x01, 0xbd, 0x00, 0x00, 0x85, 0x80, 0x24, 0x21, 0x00, 0x01, 0x00, 0x01};
int pesheader_size = sizeof(pesheader);
const uint8_t *pesheader_buf = pesheader;
int ret = 0;
if (!ctx->vbi) {
if (!(ctx->vbi = vbi_decoder_new()))
return AVERROR(ENOMEM);
if (!vbi_event_handler_add(ctx->vbi, VBI_EVENT_TTX_PAGE, handler, ctx)) {
vbi_decoder_delete(ctx->vbi);
ctx->vbi = NULL;
return AVERROR(ENOMEM);
}
}
if (!ctx->dx && (!(ctx->dx = vbi_dvb_pes_demux_new (/* callback */ NULL, NULL))))
return AVERROR(ENOMEM);
if (avctx->pkt_timebase.den && pkt->pts != AV_NOPTS_VALUE)
ctx->pts = av_rescale_q(pkt->pts, avctx->pkt_timebase, AV_TIME_BASE_Q);
if (left) {
// We allow unreasonably big packets, even if the standard only allows a max size of 1472
if ((pesheader_size + left) < 184 || (pesheader_size + left) > 65504 || (pesheader_size + left) % 184 != 0)
return AVERROR_INVALIDDATA;
memset(pesheader + 14, 0xff, pesheader_size - 14);
AV_WB16(pesheader + 4, left + pesheader_size - 6);
/* PTS is deliberately left as 0 in the PES header, otherwise libzvbi uses
* it to detect dropped frames. Unforunatey the guessed packet PTS values
* (see mpegts demuxer) are not accurate enough to pass that test. */
vbi_dvb_demux_cor(ctx->dx, ctx->sliced, 64, NULL, &pesheader_buf, &pesheader_size);
while (left > 0) {
int64_t pts = 0;
unsigned int lines = vbi_dvb_demux_cor(ctx->dx, ctx->sliced, 64, &pts, &buf, &left);
#ifdef DEBUG
av_log(avctx, AV_LOG_DEBUG,
"ctx=%p buf_size=%d left=%u lines=%u pts=%f pkt_pts=%f\n",
ctx, pkt->size, left, lines, (double)pts/90000.0, (double)pkt->pts/90000.0);
#endif
if (lines > 0) {
#ifdef DEBUGx
int i;
for(i=0; i<lines; ++i)
av_log(avctx, AV_LOG_DEBUG,
"lines=%d id=%x\n", i, ctx->sliced[i].id);
#endif
vbi_decode(ctx->vbi, ctx->sliced, lines, (double)pts/90000.0);
ctx->lines_processed += lines;
}
}
ctx->pts = AV_NOPTS_VALUE;
ret = pkt->size;
}
// is there a subtitle to pass?
if (ctx->nb_pages) {
int i;
sub->format = (ctx->pages->sub_rect->type == SUBTITLE_TEXT ? 1: 0);
sub->start_display_time = 0;
sub->end_display_time = ctx->sub_duration;
sub->num_rects = 0;
sub->pts = ctx->pages->pts;
if (ctx->pages->sub_rect->type != SUBTITLE_NONE) {
sub->rects = av_malloc(sizeof(*sub->rects) * 1);
if (sub->rects) {
sub->num_rects = 1;
sub->rects[0] = ctx->pages->sub_rect;
}
} else {
av_log(avctx, AV_LOG_DEBUG, "sending empty sub\n");
sub->rects = NULL;
}
if (!sub->rects) // no rect was passed
subtitle_rect_free(&ctx->pages->sub_rect);
for (i = 0; i < ctx->nb_pages - 1; i++)
ctx->pages[i] = ctx->pages[i + 1];
ctx->nb_pages--;
*data_size = 1;
} else
*data_size = 0;
return ret;
}
static int teletext_init_decoder(AVCodecContext *avctx)
{
TeletextContext *ctx = avctx->priv_data;
unsigned int maj, min, rev;
vbi_version(&maj, &min, &rev);
if (!(maj > 0 || min > 2 || min == 2 && rev >= 26)) {
av_log(avctx, AV_LOG_ERROR, "decoder needs zvbi version >= 0.2.26.\n");
return AVERROR_EXTERNAL;
}
ctx->dx = NULL;
ctx->vbi = NULL;
ctx->pts = AV_NOPTS_VALUE;
if (!strcmp(ctx->format, "bitmap")) {
ctx->format_id = 0;
} else if (!strcmp(ctx->format, "text")) {
ctx->format_id = 1;
} else {
av_log(avctx, AV_LOG_ERROR, "unkown format %s\n", ctx->format);
return AVERROR_OPTION_NOT_FOUND;
}
#ifdef DEBUG
{
char *t;
ctx->ex = vbi_export_new("text", &t);
}
#endif
av_log(avctx, AV_LOG_VERBOSE, "page filter: %s\n", ctx->pgno);
return 0;
}
static int teletext_close_decoder(AVCodecContext *avctx)
{
TeletextContext *ctx = avctx->priv_data;
#ifdef DEBUG
av_log(avctx, AV_LOG_DEBUG, "lines_total=%u\n", ctx->lines_processed);
#endif
while (ctx->nb_pages)
subtitle_rect_free(&ctx->pages[--ctx->nb_pages].sub_rect);
av_freep(&ctx->pages);
vbi_dvb_demux_delete(ctx->dx);
vbi_decoder_delete(ctx->vbi);
ctx->dx = NULL;
ctx->vbi = NULL;
ctx->pts = AV_NOPTS_VALUE;
return 0;
}
static void teletext_flush(AVCodecContext *avctx)
{
teletext_close_decoder(avctx);
}
#define OFFSET(x) offsetof(TeletextContext, x)
#define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
{"txt_page", "list of teletext page numbers to decode, * is all", OFFSET(pgno), AV_OPT_TYPE_STRING, {.str = "*"}, 0, 0, SD},
{"txt_chop_top", "discards the top teletext line", OFFSET(chop_top), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD},
{"txt_format", "format of the subtitles (bitmap or text)", OFFSET(format), AV_OPT_TYPE_STRING, {.str = "bitmap"}, 0, 0, SD},
{"txt_left", "x offset of generated bitmaps", OFFSET(x_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD},
{"txt_top", "y offset of generated bitmaps", OFFSET(y_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD},
{"txt_chop_spaces", "chops leading and trailing spaces from text", OFFSET(chop_spaces), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD},
{"txt_duration", "display duration of teletext pages in msecs", OFFSET(sub_duration), AV_OPT_TYPE_INT, {.i64 = 30000}, 0, 86400000, SD},
{"txt_transparent", "force transparent background of the teletext", OFFSET(transparent_bg), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD},
{ NULL },
};
static const AVClass teletext_class = {
.class_name = "libzvbi_teletextdec",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
AVCodec ff_libzvbi_teletext_decoder = {
.name = "libzvbi_teletextdec",
.long_name = NULL_IF_CONFIG_SMALL("Libzvbi DVB teletext decoder"),
.type = AVMEDIA_TYPE_SUBTITLE,
.id = AV_CODEC_ID_DVB_TELETEXT,
.priv_data_size = sizeof(TeletextContext),
.init = teletext_init_decoder,
.close = teletext_close_decoder,
.decode = teletext_decode_frame,
.capabilities = CODEC_CAP_DELAY,
.flush = teletext_flush,
.priv_class= &teletext_class,
};