[FFmpeg-devel] [PATCH]Basic XSUB encoder

Björn Axelsson gecko
Sun Feb 1 23:39:46 CET 2009


This is a cleanup of a patch submitted earlier by Steve Lhomme / DivX
Corp [1].

With this command line I can encode an avi with subtitles which displays
somewhat correctly on a PS3 and in ffplay:
./ffmpeg -i Starship_Troopers.vob -s 1280x536 -vtag DX50 -vcodec mpeg4 -an
-scodec xsub -y test.avi

Explanations and remaining issues:
* The -s parameter is for the PS3 which I can't get to play files
with lower resolution when encoded with ffmpeg.
* The PS3 places the subs on the middle of the screen, possibly because of
 the scaling of the video stream.
* ffplay places the subs on the left hand side of the middle of the
screen.
* ffplay seems to get the timing of the subtitles wrong. Since it displays
the original vob subtitles correctly, and the PS3 displays the xsub
subtitles correctly my guess is that there's something wrong with demuxing
or decoding of xsubs in ffmpeg.
* An extra field for the absolute pts is needed in AVSubtitle for the
encoder. Any ideas for a better solution are welcome.

The sample is from:
http://samples.mplayerhq.hu/MPEG-VOB/ClosedCaptions/Starship_Troopers.vob

The patch passes make test but make checkheaders fails on alsa-audio.h
which I doubt is my fault...

[1]:
http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2005-December/005639.html
-- 
Bj?rn Axelsson
-------------- next part --------------
Index: ffmpeg.c
===================================================================
--- ffmpeg.c.orig	2009-02-01 22:22:51.000000000 +0100
+++ ffmpeg.c	2009-02-01 22:31:44.000000000 +0100
@@ -814,6 +814,7 @@
         nb = 1;
 
     for(i = 0; i < nb; i++) {
+        sub->pts = pts;
         subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out,
                                                     subtitle_out_max_size, sub);
 
Index: libavcodec/Makefile
===================================================================
--- libavcodec/Makefile.orig	2009-02-01 22:22:51.000000000 +0100
+++ libavcodec/Makefile	2009-02-01 22:31:44.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-01 23:22:13.000000000 +0100
@@ -0,0 +1,223 @@
+/*
+ * DivX subtitle encoding for ffmpeg
+ * Copyright (c) 2005 DivX, Inc.
+ * Copyright (c) 2009 Bjorn Axelsson
+ *
+ * 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 "bytestream.h"
+
+// Adapted from dvdsubenc.c
+// ncnt is the nibble counter
+#define PUTNIBBLE(val)\
+do {\
+    if (ncnt++ & 1)\
+        *q++ = bitbuf | ((val) & 0x0f);\
+    else\
+        bitbuf = (val) << 4;\
+} while(0)
+
+static void xsub_encode_rle(uint8_t **pq,
+                            const uint8_t *bitmap, int linesize,
+                            int w, int h)
+{
+    uint8_t *q;
+    int x, y, len, x1, color;
+    int ncnt = 0;
+    uint8_t bitbuf = 0;
+
+    q = *pq;
+
+    for(y = 0; y < h; y++) {
+        x = 0;
+
+        while (x < w) {
+            x1 = x;
+            color = bitmap[x1++] & 0x03;
+
+            while (x1 < w && (bitmap[x1] & 0x03) == color)
+                x1++;
+            len = x1 - x;
+
+            // Run can't be longer than 255, unless it is the rest of a row
+            if(x1 < w && len > 255) {
+                int diff = len - 255;
+                len -= diff;
+                x1  -= diff;
+            }
+
+            if (len <=3) {
+                // 2-bit len, 2-bit color
+                PUTNIBBLE((len << 2) | color);
+            } else if (len <= 15) {
+                // 2-bit zero, 4-bit len, 2-bit color
+                PUTNIBBLE( (len & 0x0c) >> 2);
+                PUTNIBBLE(((len & 0x03) << 2) | color);
+            } else if (len <= 63) {
+                // 4-bit zero, 6-bit len, 2-bit color
+                PUTNIBBLE(0);
+                PUTNIBBLE( (len & 0x3c) >> 2);
+                PUTNIBBLE(((len & 0x03) << 2) | color);
+            } else if (len <= 255) {
+                // 6-bit zero, 8-bit len, 2-bit color
+                PUTNIBBLE(0);
+                PUTNIBBLE( (len & 0xc0) >> 6);
+                PUTNIBBLE( (len & 0x3c) >> 2);
+                PUTNIBBLE(((len & 0x03) << 2) | color);
+            } else {
+                // 14-bit zero and 2-bit color
+                PUTNIBBLE(0);
+                PUTNIBBLE(0);
+                PUTNIBBLE(0);
+                PUTNIBBLE(color);
+            }
+
+            x += len;
+        }
+
+        // byte align
+        if (ncnt & 1)
+            PUTNIBBLE(0);
+
+        bitmap += linesize;
+    }
+
+    *pq = q;
+}
+
+static const int tc_divs[3] = { 1000, 60, 60 };
+static void 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;
+}
+
+static int encode_xsub_subtitles(AVCodecContext *avctx, uint8_t *outbuf, AVSubtitle *h)
+{
+    uint64_t startTime = h->pts / 90; // 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 = outbuf;
+    uint8_t *rlelenptr;
+    uint8_t *q;
+    uint8_t *resized_bitmap;
+
+    uint16_t width;
+    uint16_t height;
+
+    int i;
+
+    if (h->num_rects == 0 || h->rects == NULL)
+        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
+    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, "Max 4 subtitle colors supported (%d found)\n", h->rects[0]->nb_colors);
+
+    make_tc(startTime, start_tc);
+    make_tc(endTime, end_tc);
+    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 be powers of 2
+    // 2 pixels required on either side of subtitle
+    width = h->rects[0]->w + 4;
+    if (width & 1)
+        width++;
+    height = h->rects[0]->h;
+    if (height & 1)
+        height++;
+
+    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;
+    hdr+=2;
+
+    // Palette
+    for(i=0; i<4; i++)
+        bytestream_put_be24(&hdr, ((uint32_t *)h->rects[0]->pict.data[1])[i]);
+
+    // Bitmap
+    resized_bitmap = av_malloc(width * height);
+    memset(resized_bitmap, 0, width * height);
+    for (i = 0; i < h->rects[0]->h; i++)
+        memcpy(resized_bitmap + (width * i + 2), h->rects[0]->pict.data[0] + (h->rects[0]->w * i), h->rects[0]->w);
+    q = hdr;
+    xsub_encode_rle(&q, resized_bitmap, width*2, width, height/2);
+    bytestream_put_le16(&rlelenptr, q-hdr); // Length of first field
+    xsub_encode_rle(&q, resized_bitmap+width, width*2, width, height/2);
+    av_free(resized_bitmap);
+
+    return (q-outbuf);
+}
+
+static int xsub_init_encoder(AVCodecContext *avctx)
+{
+    if(! avctx->codec_tag)
+        avctx->codec_tag = MKTAG('D','X','S','B');
+
+    /* Those are used by put_bmp_header. Don't know how important they are. */
+    avctx->width = 720;
+    avctx->height = 576;
+
+    return 0;
+}
+
+static int xsub_close_encoder(AVCodecContext *avctx)
+{
+    return 0;
+}
+
+static int xsub_encode(AVCodecContext *avctx,
+                       unsigned char *buf, int buf_size, void *data)
+{
+    AVSubtitle *sub = data;
+    return encode_xsub_subtitles(avctx, buf, sub);
+}
+
+AVCodec xsub_encoder = {
+    "xsub",
+    CODEC_TYPE_SUBTITLE,
+    CODEC_ID_XSUB,
+    0,
+    xsub_init_encoder,
+    xsub_encode,
+    xsub_close_encoder,
+};
Index: libavcodec/allcodecs.c
===================================================================
--- libavcodec/allcodecs.c.orig	2009-02-01 22:22:51.000000000 +0100
+++ libavcodec/allcodecs.c	2009-02-01 22:31:44.000000000 +0100
@@ -174,7 +174,6 @@
     REGISTER_DECODER (WNV1, wnv1);
     REGISTER_DECODER (XAN_WC3, xan_wc3);
     REGISTER_DECODER (XL, xl);
-    REGISTER_DECODER (XSUB, xsub);
     REGISTER_ENCDEC  (ZLIB, zlib);
     REGISTER_ENCDEC  (ZMBV, zmbv);
 
@@ -283,6 +282,7 @@
     /* subtitles */
     REGISTER_ENCDEC  (DVBSUB, dvbsub);
     REGISTER_ENCDEC  (DVDSUB, dvdsub);
+    REGISTER_ENCDEC  (XSUB, xsub);
 
     /* external libraries */
     REGISTER_ENCDEC  (LIBAMR_NB, libamr_nb);
Index: libavcodec/avcodec.h
===================================================================
--- libavcodec/avcodec.h.orig	2009-02-01 22:22:51.000000000 +0100
+++ libavcodec/avcodec.h	2009-02-01 22:31:44.000000000 +0100
@@ -2431,6 +2431,7 @@
     uint32_t end_display_time; /* relative to packet pts, in ms */
     unsigned num_rects;
     AVSubtitleRect **rects;
+    uint64_t pts;
 } AVSubtitle;
 
 
Index: libavformat/avienc.c
===================================================================
--- libavformat/avienc.c.orig	2009-02-01 22:22:51.000000000 +0100
+++ libavformat/avienc.c	2009-02-01 22:34:01.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:



More information about the ffmpeg-devel mailing list