]> git.sesse.net Git - ffmpeg/blobdiff - libavcodec/libzvbi-teletextdec.c
Add a lavc Makefile dependency for the elbg filter.
[ffmpeg] / libavcodec / libzvbi-teletextdec.c
index 8b504b53cdad04a02e1be46d23e29e8a21fdb6cf..bf2dc063ab515ea3712698daf11078885d144fd8 100644 (file)
@@ -19,7 +19,9 @@
  */
 
 #include "avcodec.h"
+#include "libavcodec/ass.h"
 #include "libavutil/opt.h"
+#include "libavutil/bprint.h"
 #include "libavutil/intreadwrite.h"
 
 #include <libzvbi.h>
 #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
@@ -39,15 +50,17 @@ typedef struct TeletextContext
     char           *pgno;
     int             x_offset;
     int             y_offset;
-    char           *format;
-    int             format_id; /* 0 = bitmap, 1 = text */
+    int             format_id; /* 0 = bitmap, 1 = text/ass */
     int             chop_top;
     int             sub_duration; /* in msec */
     int             transparent_bg;
     int             chop_spaces;
 
     int             lines_processed;
-    AVSubtitleRect *sub_rect;
+    TeletextPage    *pages;
+    int             nb_pages;
+    int64_t         pts;
+    int             handler_ret;
 
     vbi_decoder *   vbi;
     vbi_dvb_demux * dx;
@@ -72,14 +85,54 @@ chop_spaces_utf8(const unsigned char* t, int 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)->ass);
+    av_freep(sub_rect);
+}
+
+static int
+create_ass_text(TeletextContext *ctx, const char *text, char **ass)
+{
+    int ret;
+    AVBPrint buf, buf2;
+    const int ts_start    = av_rescale_q(ctx->pts,          AV_TIME_BASE_Q,        (AVRational){1, 100});
+    const int ts_duration = av_rescale_q(ctx->sub_duration, (AVRational){1, 1000}, (AVRational){1, 100});
+
+    /* First we escape the plain text into buf. */
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+    ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0);
+
+    if (!av_bprint_is_complete(&buf)) {
+        av_bprint_finalize(&buf, NULL);
+        return AVERROR(ENOMEM);
+    }
+
+    /* Then we create the ass dialog line in buf2 from the escaped text in buf. */
+    av_bprint_init(&buf2, 0, AV_BPRINT_SIZE_UNLIMITED);
+    ff_ass_bprint_dialog(&buf2, buf.str, ts_start, ts_duration, 0);
+    av_bprint_finalize(&buf, NULL);
+
+    if (!av_bprint_is_complete(&buf2)) {
+        av_bprint_finalize(&buf2, NULL);
+        return AVERROR(ENOMEM);
+    }
+
+    if ((ret = av_bprint_finalize(&buf2, ass)) < 0)
+        return ret;
+
+    return 0;
+}
+
 // draw a page as text
 static int
-gen_sub_text(TeletextContext *ctx, vbi_page *page, int chop_top)
+gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top)
 {
-    AVSubtitleRect *sub_rect = ctx->sub_rect;
-    char *text;
     const char *in;
-    char *out;
+    AVBPrint buf;
     char *vbi_text = av_malloc(TEXT_MAXSZ);
     int sz;
 
@@ -97,11 +150,8 @@ gen_sub_text(TeletextContext *ctx, vbi_page *page, int chop_top)
     }
     vbi_text[sz] = '\0';
     in  = vbi_text;
-    out = text = av_malloc(TEXT_MAXSZ);
-    if (!text) {
-        av_free(vbi_text);
-        return AVERROR(ENOMEM);
-    }
+    av_bprint_init(&buf, 0, TEXT_MAXSZ);
+
     if (ctx->chop_spaces) {
         for (;;) {
             int nl, sz;
@@ -116,33 +166,38 @@ gen_sub_text(TeletextContext *ctx, vbi_page *page, int chop_top)
                 break;
             // skip trailing spaces
             sz = chop_spaces_utf8(in, nl);
-            memcpy(out, in, sz);
-            out += sz;
-            *out++ = '\n';
+            av_bprint_append_data(&buf, in, sz);
+            av_bprintf(&buf, "\n");
             in += nl;
         }
     } else {
-        strcpy(text, vbi_text);
-        out += sz;
-        *out++ = '\n';
+        av_bprintf(&buf, "%s\n", vbi_text);
     }
     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);
+
+    if (!av_bprint_is_complete(&buf)) {
+        av_bprint_finalize(&buf, NULL);
+        return AVERROR(ENOMEM);
+    }
+
+    if (buf.len) {
+        int ret;
+        sub_rect->type = SUBTITLE_ASS;
+        if ((ret = create_ass_text(ctx, buf.str, &sub_rect->ass)) < 0) {
+            av_bprint_finalize(&buf, NULL);
+            return ret;
+        }
+        av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass);
     } else {
         sub_rect->type = SUBTITLE_NONE;
-        av_free(text);
     }
+    av_bprint_finalize(&buf, NULL);
     return 0;
 }
 
 static void
-fix_transparency(TeletextContext *ctx, vbi_page *page, int chop_top, uint8_t transparent_color, int resx, int resy)
+fix_transparency(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top, uint8_t transparent_color, int resx, int resy)
 {
-    AVSubtitleRect *sub_rect = ctx->sub_rect;
     int iy;
 
     // Hack for transparency, inspired by VLC code...
@@ -173,9 +228,8 @@ fix_transparency(TeletextContext *ctx, vbi_page *page, int chop_top, uint8_t tra
 
 // draw a page as bitmap
 static int
-gen_sub_bitmap(TeletextContext *ctx, vbi_page *page, int chop_top)
+gen_sub_bitmap(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top)
 {
-    AVSubtitleRect *sub_rect = ctx->sub_rect;
     int resx = page->columns * 12;
     int resy = (page->rows - chop_top) * 10;
     uint8_t ci, cmax = 0;
@@ -206,7 +260,7 @@ gen_sub_bitmap(TeletextContext *ctx, vbi_page *page, int chop_top)
                             0, chop_top, page->columns, page->rows - chop_top,
                             /*reveal*/ 1, /*flash*/ 1);
 
-    fix_transparency(ctx, page, chop_top, cmax, resx, resy);
+    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;
@@ -239,6 +293,7 @@ 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];
@@ -253,6 +308,8 @@ handler(vbi_event *ev, void *user_data)
 
     if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str))
         return;
+    if (ctx->handler_ret < 0)
+        return;
 
     /* Fetch the page.  */
     res = vbi_fetch_vt_page(ctx->vbi, &page,
@@ -279,18 +336,34 @@ handler(vbi_event *ev, void *user_data)
     av_log(ctx, AV_LOG_DEBUG, "%d x %d page chop:%d\n",
            page.columns, page.rows, chop_top);
 
-    if (!ctx->sub_rect) {
-        ctx->sub_rect = av_mallocz(sizeof(*ctx->sub_rect));
-        if (ctx->sub_rect) {
-            res = (ctx->format_id == 0) ?
-                gen_sub_bitmap(ctx, &page, chop_top) :
-                gen_sub_text  (ctx, &page, chop_top);
-            if (res)
-                av_freep(&ctx->sub_rect);
+    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 < 0) {
+                    av_freep(&cur_page->sub_rect);
+                    ctx->handler_ret = res;
+                } else {
+                    ctx->pages[ctx->nb_pages++] = *cur_page;
+                }
+            } else {
+                ctx->handler_ret = AVERROR(ENOMEM);
+            }
+        } else {
+            ctx->handler_ret = AVERROR(ENOMEM);
         }
     } else {
-        // FIXME: Multiple teletext pages in a single packet, some kind of buffering should be done instead of dropping the page...
-        av_log(ctx, AV_LOG_WARNING, "Missed page %s.%02x.\n", pgno_str, ev->ev.ttx_page.subno & 0xFF);
+        //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);
+        ctx->handler_ret = AVERROR(ENOSYS);
     }
 
     vbi_unref_page(&page);
@@ -304,10 +377,11 @@ teletext_decode_frame(AVCodecContext *avctx,
     TeletextContext *ctx = avctx->priv_data;
     AVSubtitle      *sub = data;
     const uint8_t   *buf = pkt->data;
-    unsigned int    left = pkt->size;
+    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()))
@@ -321,64 +395,84 @@ teletext_decode_frame(AVCodecContext *avctx,
     if (!ctx->dx && (!(ctx->dx = vbi_dvb_pes_demux_new (/* callback */ NULL, NULL))))
         return AVERROR(ENOMEM);
 
-    // 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;
+    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);
+        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);
+        /* 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);
+        ctx->handler_ret = pkt->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);
+            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) {
+            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);
+                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;
+                vbi_decode(ctx->vbi, ctx->sliced, lines, (double)pts/90000.0);
+                ctx->lines_processed += lines;
+            }
         }
+        ctx->pts = AV_NOPTS_VALUE;
+        ret = ctx->handler_ret;
     }
 
+    if (ret < 0)
+        return ret;
+
     // is there a subtitle to pass?
-    if (ctx->sub_rect) {
-        sub->format = (ctx->sub_rect->type == SUBTITLE_TEXT ? 1: 0);
+    if (ctx->nb_pages) {
+        int i;
+        sub->format = ctx->format_id;
         sub->start_display_time = 0;
         sub->end_display_time = ctx->sub_duration;
         sub->num_rects = 0;
+        sub->pts = ctx->pages->pts;
 
-        if (ctx->sub_rect->type != SUBTITLE_NONE) {
+        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->sub_rect;
+                sub->rects[0] = ctx->pages->sub_rect;
+            } else {
+                ret = AVERROR(ENOMEM);
             }
         } else {
             av_log(avctx, AV_LOG_DEBUG, "sending empty sub\n");
             sub->rects = NULL;
         }
         if (!sub->rects) // no rect was passed
-            av_free(ctx->sub_rect);
-        ctx->sub_rect = NULL;
+            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;
+        if (ret >= 0)
+            *data_size = 1;
     } else
         *data_size = 0;
 
-    return pkt->size;
+    return ret;
 }
 
 static int teletext_init_decoder(AVCodecContext *avctx)
@@ -394,15 +488,7 @@ static int teletext_init_decoder(AVCodecContext *avctx)
 
     ctx->dx = NULL;
     ctx->vbi = NULL;
-    ctx->sub_rect = NULL;
-    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;
-    }
+    ctx->pts = AV_NOPTS_VALUE;
 
 #ifdef DEBUG
     {
@@ -411,7 +497,7 @@ static int teletext_init_decoder(AVCodecContext *avctx)
     }
 #endif
     av_log(avctx, AV_LOG_VERBOSE, "page filter: %s\n", ctx->pgno);
-    return 0;
+    return (ctx->format_id == 1) ? ff_ass_subtitle_header_default(avctx) : 0;
 }
 
 static int teletext_close_decoder(AVCodecContext *avctx)
@@ -421,11 +507,15 @@ static int teletext_close_decoder(AVCodecContext *avctx)
 #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;
 }
 
@@ -439,7 +529,9 @@ static void teletext_flush(AVCodecContext *avctx)
 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_format",      "format of the subtitles (bitmap or text)",          OFFSET(format_id),      AV_OPT_TYPE_INT,    {.i64 = 0},        0, 1,        SD,  "txt_format"},
+    {"bitmap",          NULL,                                                0,                      AV_OPT_TYPE_CONST,  {.i64 = 0},        0, 0,        SD,  "txt_format"},
+    {"text",            NULL,                                                0,                      AV_OPT_TYPE_CONST,  {.i64 = 1},        0, 0,        SD,  "txt_format"},
     {"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},
@@ -464,6 +556,7 @@ AVCodec ff_libzvbi_teletext_decoder = {
     .init      = teletext_init_decoder,
     .close     = teletext_close_decoder,
     .decode    = teletext_decode_frame,
+    .capabilities = CODEC_CAP_DELAY,
     .flush     = teletext_flush,
     .priv_class= &teletext_class,
 };