[FFmpeg-devel] [RFC] XSUB subtitle decoder

Reimar Döffinger Reimar.Doeffinger
Sat Aug 4 20:55:20 CEST 2007


Hello,
here is my first approach it this, unfortunately there are a lot of
problems.
1) I have only one very short sample:
  http://samples.mplayerhq.hu/sub/small.divx
  The other sample on our server:
  http://samples.mplayerhq.hu/sub/DivX+Subtitles.divx
  I am fairly certain that this does not contain any subtitles at all
  (though it contains a subtitle stream).
2) ffplay is the only application to test, but unfortunately seems to be
   quite buggy:
   It does not check the boundaries if the subtitle before drawing
   it, so the small sample above crashes it since the y-Koordinate is
   too big. The reason for this is I think, that xsub gives
   Koordinates as on the DVD, i.e. assuming PAL resolution while
   ffplay assumes they are actual coordinates.
   The decoder can not do this transformation though since currently
   the actual movie resolution is not provided to it.
3) The start_display_time and end_display_time are relative to the
   packet pts. I lack a large enough sample to know if this works for
   xsub, but for the general case it seems like quite a strong
   assumption. Since packet pts is not passed to the decode function,
   a conversion is not possible here either.
4) For some reason, the decoder output the subtitle twice one below the
   other. It's quite possible that the subs are stored in this format
   to simply things with interlaced display, but I don't know for sure
   and need more samples.

Greetings,
Reimar D?ffinger
-------------- next part --------------
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index dccab43..f2f2862 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -210,6 +210,7 @@ OBJS-$(CONFIG_XAN_DPCM_DECODER)        += dpcm.o
 OBJS-$(CONFIG_XAN_WC3_DECODER)         += xan.o
 OBJS-$(CONFIG_XAN_WC4_DECODER)         += xan.o
 OBJS-$(CONFIG_XL_DECODER)              += xl.o
+OBJS-$(CONFIG_XSUB_DECODER)            += xsubdec.o
 OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
 OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
 OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 6a944e1..cb73660 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -160,6 +160,7 @@ void avcodec_register_all(void)
     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);
 
diff --git a/libavcodec/allcodecs.h b/libavcodec/allcodecs.h
index 98721ac..3b46256 100644
--- a/libavcodec/allcodecs.h
+++ b/libavcodec/allcodecs.h
@@ -204,6 +204,7 @@ extern AVCodec ws_snd1_decoder;
 extern AVCodec xan_dpcm_decoder;
 extern AVCodec xan_wc3_decoder;
 extern AVCodec xl_decoder;
+extern AVCodec xsub_decoder;
 extern AVCodec zmbv_decoder;
 
 /* PCM codecs */
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 57bffbb..80eb3e3 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -265,6 +265,7 @@ enum CodecID {
     CODEC_ID_DVD_SUBTITLE= 0x17000,
     CODEC_ID_DVB_SUBTITLE,
     CODEC_ID_TEXT,  /* raw UTF-8 text */
+    CODEC_ID_XSUB,
 
     CODEC_ID_MPEG2TS= 0x20000, /* _FAKE_ codec to indicate a raw MPEG-2 TS
                                 * stream (only used by libavformat) */
diff --git a/libavcodec/xsubdec.c b/libavcodec/xsubdec.c
new file mode 100644
index 0000000..134eb9a
--- /dev/null
+++ b/libavcodec/xsubdec.c
@@ -0,0 +1,112 @@
+#include "avcodec.h"
+#include "bitstream.h"
+#include "bytestream.h"
+
+static int decode_init(AVCodecContext *avctx) {
+    avctx->pix_fmt = PIX_FMT_PAL8;
+    return 0;
+}
+
+static const uint8_t tc_offsets[9] = { 0, 1, 3, 4, 6, 7, 9, 10, 11 };
+static const uint8_t tc_muls[9] = { 10, 6, 10, 6, 10, 6, 10, 10, 1 };
+
+static uint64_t parse_timecode(AVCodecContext *avctx, uint8_t *buf) {
+    int i;
+    int64_t ms = 0;
+    if (buf[2] != ':' || buf[5] != ':' || buf[8] != '.')
+        return AV_NOPTS_VALUE;
+    for (i = 0; i < sizeof(tc_offsets); i++) {
+        uint8_t c = buf[tc_offsets[i]] - '0';
+        if (c > 9) return AV_NOPTS_VALUE;
+        ms = (ms + c) * tc_muls[i];
+    }
+    return ms;
+}
+
+static int decode_frame(AVCodecContext *avctx, void *data, int *data_size,
+                        uint8_t *buf, int buf_size) {
+    AVSubtitle *sub = data;
+    uint8_t *buf_end = buf + buf_size;
+    uint8_t *bitmap;
+    int w, h, x, y, rlelen, i;
+    GetBitContext gb;
+
+    // check that at least header fits
+    if (buf_size < 27 + 7 * 2 + 4 * 3) {
+        av_log(avctx, AV_LOG_ERROR, "coded frame too small\n");
+        return -1;
+    }
+
+    // read start and end time
+    if (buf[0] != '[' || buf[13] != '-' || buf[26] != ']') {
+        av_log(avctx, AV_LOG_ERROR, "invalid time code\n");
+        return -1;
+    }
+    sub->start_display_time = parse_timecode(avctx, buf +  1);
+    sub->end_display_time   = parse_timecode(avctx, buf + 14);
+    buf += 27;
+
+    // read header
+    w = bytestream_get_le16(&buf);
+    h = bytestream_get_le16(&buf);
+    if (avcodec_check_dimensions(avctx, w, h) < 0)
+        return -1;
+    x = bytestream_get_le16(&buf);
+    y = bytestream_get_le16(&buf);
+    // skip bottom right position, it gives no new information
+    bytestream_get_le16(&buf);
+    bytestream_get_le16(&buf);
+    rlelen = bytestream_get_le16(&buf);
+
+    // allocate sub and set values
+    if (!sub->rects) {
+        sub->rects = av_mallocz(sizeof(AVSubtitleRect));
+        sub->num_rects = 1;
+    }
+    av_freep(&sub->rects[0].bitmap);
+    sub->rects[0].x = x; sub->rects[0].y = y;
+    sub->rects[0].w = w; sub->rects[0].h = h;
+    sub->rects[0].linesize = w;
+    sub->rects[0].bitmap = av_malloc(w * h);
+    sub->rects[0].nb_colors = 4;
+    sub->rects[0].rgba_palette = av_malloc(sub->rects[0].nb_colors * 4);
+
+    // read palette
+    for (i = 0; i < sub->rects[0].nb_colors; i++)
+        sub->rects[0].rgba_palette[i] = bytestream_get_be24(&buf);
+    // make all except background (first entry) non-transparent
+    for (i = 1; i < sub->rects[0].nb_colors; i++)
+        sub->rects[0].rgba_palette[i] |= 0xff000000;
+
+    // process RLE-compressed data
+    rlelen = FFMIN(rlelen, buf_end - buf);
+    init_get_bits(&gb, buf, rlelen * 8);
+    bitmap = sub->rects[0].bitmap;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; ) {
+            int log2 = ff_log2_tab[show_bits(&gb, 8)];
+            int run = get_bits(&gb, 14 - 4 * (log2 >> 1));
+            int colour = get_bits(&gb, 2);
+            run = FFMIN(run, w - x);
+            // run length 0 means till end of row
+            if (!run) run = w - x;
+            memset(bitmap, colour, run);
+            bitmap += run;
+            x += run;
+        }
+        align_get_bits(&gb);
+    }
+    *data_size = 1;
+    return buf_size;
+}
+
+AVCodec xsub_decoder = {
+    "xsub",
+    CODEC_TYPE_SUBTITLE,
+    CODEC_ID_XSUB,
+    0,
+    decode_init,
+    NULL,
+    NULL,
+    decode_frame,
+};
diff --git a/libavformat/avidec.c b/libavformat/avidec.c
index 53621c5..f428fab 100644
--- a/libavformat/avidec.c
+++ b/libavformat/avidec.c
@@ -412,6 +412,13 @@ static int avi_read_header(AVFormatContext *s, AVFormatParameters *ap)
                     get_le32(pb); /* ClrUsed */
                     get_le32(pb); /* ClrImportant */
 
+                    if (tag1 == MKTAG('D', 'X', 'S', 'B')) {
+                        st->codec->codec_type = CODEC_TYPE_SUBTITLE;
+                        st->codec->codec_tag = tag1;
+                        st->codec->codec_id = CODEC_ID_XSUB;
+                        break;
+                    }
+
                     if(size > 10*4 && size<(1<<30)){
                         st->codec->extradata_size= size - 10*4;
                         st->codec->extradata= av_malloc(st->codec->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE);



More information about the ffmpeg-devel mailing list