[FFmpeg-devel] [PATCH]Basic XSUB encoder (take 3)

Björn Axelsson gecko
Thu Feb 5 00:07:56 CET 2009


On Tue, 3 Feb 2009, Reimar D?ffinger wrote:

> On Tue, Feb 03, 2009 at 12:12:54AM +0100, Reimar D?ffinger wrote:
> > You should try to handle the margin case somehow nicer.
> > E.g.
> > >    for (y = 0; y < h; y++) {
> > >        x = 0;
> > >        while (x < w) {
> > >            x1 = x;
> > >            color = bitmap[x1++] & 3;
> > >            while (x1 < w && (bitmap[x1] & 3) == color) x1++;
> > >            len = x1 - x;
> > >            if (x == 0) {
> > >                if (color == MARGIN_COLOR) len += MARGIN;
> > >                else put_xsub_rle(&pb, MARGIN, MARGIN_COLOR);
> > >            }
> > >            // Run can't be longer than 255, unless it is the rest of a row
> > >            if (x1 == w && color == MARGIN_COLOR) len += MARGIN;
> > >            else len = FFMIN(len, 255);
> > >            put_xsub_rle(&pb, len, color);
> > >            x += len;
> > >        }
> > >        if (color != MARGIN_COLOR)
> > >            put_xsub_rle(&pb, MARGIN + odd, MARGIN_COLOR);
>
> Sorry, here
> > >            else len = FFMIN(len, 255);
> > >            put_xsub_rle(&pb, len, color);
> > >            x += len;
>
> should be
>
> > else if (len > 255) {
> >     x1 -= len - 255;
> >     len = 255;
> > }
> > put_xsub_rle(&pb, len, color);
> > x = x1;
>
> (and yes I know that's almost how the code was originally).
> Also replacing "color == MARGIN_COLOR" by "color == MARGIN_COLOR || !MARGIN"
> and "color != MARGIN_COLOR" by "color != MARGIN_COLORi && MARGIN"
> you can also support MARGIN == 0.

Thanks for the ideas. I don't like the additional code complexity due to
the padding, but now it is at least pretty efficient.

I went for a slightly different approach as the bitmap position mustn't be
changed when adding the left margin. The right margin is no problem, since
the line ends there. Odd-sized bitmaps must also be padded no matter what
MARGIN (now PADDING) is set to.

The buffer size check is done before each run is encoded. The check is
pessimistic with a small chance of a false positive if the buffer is just
one or two bytes larger than the encoded subtitle.
I also added a buffer check for the static header, and one for the
final padding row.

-- 
Bj?rn Axelsson
-------------- next part --------------
Index: ffmpeg.c
===================================================================
--- ffmpeg.c.orig	2009-02-04 23:22:06.000000000 +0100
+++ ffmpeg.c	2009-02-04 23:23:28.000000000 +0100
@@ -814,6 +814,7 @@
         nb = 1;
 
     for(i = 0; i < nb; i++) {
+        sub->pts = av_rescale_q(pts, ist->st->time_base, AV_TIME_BASE_Q);
         subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out,
                                                     subtitle_out_max_size, sub);
 
Index: libavcodec/Makefile
===================================================================
--- libavcodec/Makefile.orig	2009-02-04 23:22:06.000000000 +0100
+++ libavcodec/Makefile	2009-02-04 23:23:28.000000000 +0100
@@ -251,6 +251,7 @@
 OBJS-$(CONFIG_XAN_WC4_DECODER)         += xan.o
 OBJS-$(CONFIG_XL_DECODER)              += xl.o
 OBJS-$(CONFIG_XSUB_DECODER)            += xsubdec.o
+OBJS-$(CONFIG_XSUB_ENCODER)            += xsubenc.o
 OBJS-$(CONFIG_XVMC)                    += xvmcvideo.o
 OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
 OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
Index: libavcodec/xsubenc.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ libavcodec/xsubenc.c	2009-02-04 23:23:28.000000000 +0100
@@ -0,0 +1,223 @@
+/*
+ * DivX (XSUB) subtitle encoder
+ * Copyright (c) 2005 DivX, Inc.
+ * Copyright (c) 2009 Bjorn Axelsson
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "bitstream.h"
+
+/* For obscure reasons we pad the subtitles with two pixels on either side */
+#define PADDING 2
+#define PADDING_COLOR 0
+
+/** Encode a single color run. At most 16 bits will be used. */
+void put_xsub_rle(PutBitContext *pb, int len, int color)
+{
+    if (len <= 255)
+        put_bits(pb, 2 + ((ff_log2_tab[len] >> 1) << 2), len);
+    else
+        put_bits(pb, 14, 0);
+    put_bits(pb, 2, color);
+}
+
+/** Encode a 4-colour bitmap with XSUB rle.
+ * The encoded bitmap may be wider than the source bitmap due to padding. */
+static int xsub_encode_rle(uint8_t **buffer, int bufsize,
+        const uint8_t *bitmap, int linesize, int w, int h)
+{
+    PutBitContext pb;
+    int x0, x1, y, len, color = PADDING_COLOR;
+
+    init_put_bits(&pb, *buffer, bufsize);
+
+    for (y = 0; y < h; y++) {
+        x0 = 0;
+        while (x0 < w) {
+            // One run + padding will need 3 bytes in worst case
+            if (bufsize - ((put_bits_count(&pb) + 7) >> 3) < 3)
+                return -1;
+
+            x1 = x0;
+            color = bitmap[x1++] & 3;
+            while (x1 < w && (bitmap[x1] & 3) == color)
+                x1++;
+            len = x1 - x0;
+            if (PADDING && x0 == 0) {
+                if (color == PADDING_COLOR) {
+                    len += PADDING;
+                    x0  -= PADDING;
+                } else
+                    put_xsub_rle(&pb, PADDING, PADDING_COLOR);
+            }
+
+            // Run can't be longer than 255, unless it is the rest of a row
+            if (x1 == w && color == PADDING_COLOR)
+                len += PADDING + (w&1);
+            else
+                len = FFMIN(len, 255);
+            put_xsub_rle(&pb, len, color);
+
+            x0 += len;
+        }
+        if (color != PADDING_COLOR && (PADDING || (w&1)))
+            put_xsub_rle(&pb, PADDING + (w&1), PADDING_COLOR);
+
+        align_put_bits(&pb);
+
+        bitmap += linesize;
+    }
+
+    flush_put_bits(&pb);
+    *buffer += put_bits_count(&pb) / 8;
+    return 0;
+}
+
+static const int tc_divs[3] = { 1000, 60, 60 };
+static int make_tc(uint64_t ms, int *tc)
+{
+    int i;
+    for (i=0; i<3; i++) {
+        tc[i] = ms % tc_divs[i];
+        ms /= tc_divs[i];
+    }
+    tc[3] = ms;
+    return ms > 99;
+}
+
+static int xsub_encode(AVCodecContext *avctx, unsigned char *buf,
+                       int bufsize, void *data)
+{
+    AVSubtitle *h = (AVSubtitle *)data;
+    uint64_t startTime = h->pts / 1000; // FIXME: need better solution...
+    uint64_t endTime = startTime + h->end_display_time - h->start_display_time;
+    int start_tc[4], end_tc[4];
+
+    uint8_t *hdr = (uint8_t *)buf;
+    uint8_t *rlelenptr;
+    uint8_t *q;
+
+    uint16_t width, height;
+
+    int i;
+
+    if (h->num_rects == 0 || h->rects == NULL)
+        return -1;
+
+    // Buffer large enough to hold static header?
+    if (bufsize < 27 + 7*2 + 4*3) {
+        av_log(avctx, AV_LOG_ERROR, "Buffer too small for XSUB header.\n");
+        return -1;
+    }
+
+    // TODO: support multiple rects
+    if (h->num_rects > 1)
+        av_log(avctx, AV_LOG_WARNING, "Only single rects supported (%d in subtitle.)\n", h->num_rects);
+
+    // TODO: render text-based subtitles into bitmaps
+    if (!h->rects[0]->pict.data[0] || !h->rects[0]->pict.data[1]) {
+        av_log(avctx, AV_LOG_WARNING, "No subtitle bitmap available.\n");
+        return -1;
+    }
+
+    // TODO: color reduction, similar to dvdsub encoder
+    if (h->rects[0]->nb_colors > 4)
+        av_log(avctx, AV_LOG_WARNING, "No more than 4 subtitle colors supported (%d found.)\n", h->rects[0]->nb_colors);
+
+    // TODO: Palette swapping if color zero is not transparent
+    if (((uint32_t *)h->rects[0]->pict.data[1])[0] & 0xff)
+        av_log(avctx, AV_LOG_WARNING, "Color index 0 is not transparent. Transparency will be messed up.\n");
+
+    if (make_tc(startTime, start_tc) || make_tc(endTime, end_tc)) {
+        av_log(avctx, AV_LOG_WARNING, "Time code >= 100 hours.\n");
+        return -1;
+    }
+
+    snprintf(hdr, 28,
+        "[%02d:%02d:%02d.%03d-%02d:%02d:%02d.%03d]",
+        start_tc[3], start_tc[2], start_tc[1], start_tc[0],
+        end_tc[3],   end_tc[3],   end_tc[1],   end_tc[0]);
+    hdr += 27;
+
+    // Width and height must probably be multiples of 2.
+
+    // 2 pixels required on either side of subtitle.
+    // Possibly due to limitations of hardware renderers.
+    // TODO: check if the bitmap is already padded
+    width = ((h->rects[0]->w + 1) & ~1) + PADDING * 2;
+    height = (h->rects[0]->h + 1) & ~1;
+
+    bytestream_put_le16(&hdr, width);
+    bytestream_put_le16(&hdr, height);
+    bytestream_put_le16(&hdr, h->rects[0]->x);
+    bytestream_put_le16(&hdr, h->rects[0]->y);
+    bytestream_put_le16(&hdr, h->rects[0]->x + width);
+    bytestream_put_le16(&hdr, h->rects[0]->y + height);
+
+    rlelenptr = hdr; // Will store length of first field here later.
+    hdr+=2;
+
+    // Palette
+    for (i=0; i<4; i++)
+        bytestream_put_be24(&hdr, ((uint32_t *)h->rects[0]->pict.data[1])[i]);
+
+    // Bitmap
+    q = hdr;
+    if (xsub_encode_rle(&q, bufsize - (q-buf),
+                h->rects[0]->pict.data[0],
+                h->rects[0]->pict.linesize[0]*2,
+                h->rects[0]->w, (h->rects[0]->h + 1) / 2))
+        return -1;
+    bytestream_put_le16(&rlelenptr, q-hdr); // Length of first field
+
+    if (xsub_encode_rle(&q, bufsize - (q-buf),
+            h->rects[0]->pict.data[0] + h->rects[0]->pict.linesize[0],
+            h->rects[0]->pict.linesize[0]*2,
+            h->rects[0]->w, h->rects[0]->h / 2))
+        return -1;
+
+    // Enforce total height to be be multiple of 2
+    if (h->rects[0]->h & 1) {
+        if (bufsize - (q - buf) < 2)
+            return -1;
+        bytestream_put_le16(&q, PADDING_COLOR); // RLE empty line
+    }
+
+    return q-buf;
+}
+
+static av_cold int xsub_encoder_init(AVCodecContext *avctx)
+{
+    if (!avctx->codec_tag)
+        avctx->codec_tag = MKTAG('D','X','S','B');
+
+    return 0;
+}
+
+AVCodec xsub_encoder = {
+    "xsub",
+    CODEC_TYPE_SUBTITLE,
+    CODEC_ID_XSUB,
+    0,
+    xsub_encoder_init,
+    xsub_encode,
+    NULL,
+    .long_name = NULL_IF_CONFIG_SMALL("DivX subtitles (XSUB)"),
+};
Index: libavcodec/allcodecs.c
===================================================================
--- libavcodec/allcodecs.c.orig	2009-02-04 23:22:06.000000000 +0100
+++ libavcodec/allcodecs.c	2009-02-04 23:23:28.000000000 +0100
@@ -282,7 +282,7 @@
     /* subtitles */
     REGISTER_ENCDEC  (DVBSUB, dvbsub);
     REGISTER_ENCDEC  (DVDSUB, dvdsub);
-    REGISTER_DECODER (XSUB, xsub);
+    REGISTER_ENCDEC  (XSUB, xsub);
 
     /* external libraries */
     REGISTER_ENCDEC  (LIBAMR_NB, libamr_nb);
Index: libavcodec/avcodec.h
===================================================================
--- libavcodec/avcodec.h.orig	2009-02-04 23:22:06.000000000 +0100
+++ libavcodec/avcodec.h	2009-02-04 23:23:28.000000000 +0100
@@ -30,7 +30,7 @@
 #include "libavutil/avutil.h"
 
 #define LIBAVCODEC_VERSION_MAJOR 52
-#define LIBAVCODEC_VERSION_MINOR 11
+#define LIBAVCODEC_VERSION_MINOR 12
 #define LIBAVCODEC_VERSION_MICRO  0
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
@@ -2431,6 +2431,7 @@
     uint32_t end_display_time; /* relative to packet pts, in ms */
     unsigned num_rects;
     AVSubtitleRect **rects;
+    uint64_t pts;    ///< Same as packet pts, in AV_TIME_BASE
 } AVSubtitle;
 
 
Index: libavformat/avienc.c
===================================================================
--- libavformat/avienc.c.orig	2009-02-04 23:22:06.000000000 +0100
+++ libavformat/avienc.c	2009-02-04 23:23:28.000000000 +0100
@@ -82,6 +82,9 @@
     if (type == CODEC_TYPE_VIDEO) {
         tag[2] = 'd';
         tag[3] = 'c';
+    } else if (type == CODEC_TYPE_SUBTITLE) {
+        tag[2] = 's';
+        tag[3] = 'b';
     } else {
         tag[2] = 'w';
         tag[3] = 'b';
@@ -213,8 +216,10 @@
         case CODEC_TYPE_AUDIO: put_tag(pb, "auds"); break;
 //        case CODEC_TYPE_TEXT : put_tag(pb, "txts"); break;
         case CODEC_TYPE_DATA : put_tag(pb, "dats"); break;
+        case CODEC_TYPE_SUBTITLE: put_tag(pb, "vids"); break;
         }
-        if(stream->codec_type == CODEC_TYPE_VIDEO)
+        if(stream->codec_type == CODEC_TYPE_VIDEO
+                || stream->codec_type == CODEC_TYPE_SUBTITLE)
             put_le32(pb, stream->codec_tag);
         else
             put_le32(pb, 1);
@@ -254,6 +259,7 @@
         strf = start_tag(pb, "strf");
         switch(stream->codec_type) {
         case CODEC_TYPE_VIDEO:
+        case CODEC_TYPE_SUBTITLE:
             put_bmp_header(pb, stream, codec_bmp_tags, 0);
             break;
         case CODEC_TYPE_AUDIO:
Index: Changelog
===================================================================
--- Changelog.orig	2009-02-04 23:22:06.000000000 +0100
+++ Changelog	2009-02-04 23:23:28.000000000 +0100
@@ -146,6 +146,7 @@
 - hybrid WavPack support
 - R3D REDCODE demuxer
 - ALSA support for playback and record
+- DivX (XSUB) subtitle encoder
 
 version 0.4.9-pre1:
 
Index: doc/general.texi
===================================================================
--- doc/general.texi.orig	2009-02-04 23:22:06.000000000 +0100
+++ doc/general.texi	2009-02-04 23:23:28.000000000 +0100
@@ -455,7 +455,7 @@
 @item ASS/SSA      @tab X @tab X
 @item DVB          @tab X @tab X @tab X @tab X @tab X
 @item DVD          @tab X @tab X @tab X @tab X @tab X
- at item XSUB         @tab   @tab   @tab   @tab X @tab
+ at item XSUB         @tab   @tab   @tab X @tab X @tab
 @end multitable
 
 @code{X} means that the feature is supported.



More information about the ffmpeg-devel mailing list