Added preliminary subtitle decoder support to our avcodec wrapper.
authorLaurent Aimar <fenrir@videolan.org>
Thu, 27 Aug 2009 21:27:59 +0000 (23:27 +0200)
committerLaurent Aimar <fenrir@videolan.org>
Thu, 27 Aug 2009 21:46:33 +0000 (23:46 +0200)
Only blu-ray subtitle are enabled.
If other formats are to be enabled, they must be carefully tested.

modules/codec/avcodec/Modules.am
modules/codec/avcodec/avcodec.c
modules/codec/avcodec/avcodec.h
modules/codec/avcodec/fourcc.c
modules/codec/avcodec/subtitle.c [new file with mode: 0644]

index 2d707a0..013faca 100644 (file)
@@ -2,6 +2,7 @@ SOURCES_avcodec = \
        avcodec.c \
        avcodec.h \
        video.c \
+       subtitle.c \
        audio.c \
        deinterlace.c \
        avutil.h \
index 690febf..0f621cf 100644 (file)
@@ -280,6 +280,11 @@ static int OpenDecoder( vlc_object_t *p_this )
         i_result =  InitAudioDec ( p_dec, p_context, p_codec,
                                        i_codec_id, psz_namecodec );
         break;
+    case SPU_ES:
+        p_dec->pf_decode_sub = DecodeSubtitle;
+        i_result =  InitSubtitleDec( p_dec, p_context, p_codec,
+                                     i_codec_id, psz_namecodec );
+        break;
     default:
         i_result = VLC_EGENERIC;
     }
@@ -305,6 +310,9 @@ static void CloseDecoder( vlc_object_t *p_this )
     case VIDEO_ES:
          EndVideoDec ( p_dec );
         break;
+    case SPU_ES:
+         EndSubtitleDec( p_dec );
+        break;
     }
 
     if( p_sys->p_context )
index ef21d0b..f7ab24d 100644 (file)
@@ -33,6 +33,7 @@ int GetVlcChroma( video_format_t *fmt, const int i_ffmpeg_chroma );
 
 picture_t * DecodeVideo    ( decoder_t *, block_t ** );
 aout_buffer_t * DecodeAudio( decoder_t *, block_t ** );
+subpicture_t *DecodeSubtitle( decoder_t *p_dec, block_t ** );
 
 /* Video encoder module */
 int  OpenEncoder ( vlc_object_t * );
@@ -58,6 +59,12 @@ int InitAudioDec( decoder_t *p_dec, AVCodecContext *p_context,
                   AVCodec *p_codec, int i_codec_id, const char *psz_namecodec );
 void EndAudioDec( decoder_t *p_dec );
 
+/* Subtitle Decoder */
+int InitSubtitleDec( decoder_t *p_dec, AVCodecContext *p_context,
+                     AVCodec *p_codec, int i_codec_id, const char *psz_namecodec );
+void EndSubtitleDec( decoder_t *p_dec );
+
+
 /*****************************************************************************
  * Module descriptor help strings
  *****************************************************************************/
index e436325..70bdbaa 100644 (file)
@@ -371,13 +371,17 @@ static const struct
 #endif
 
     /* Subtitle streams */
-    /* Before this version, subs were too experimental */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( 52, 33, 0 )
+    { VLC_CODEC_BD_PG, CODEC_ID_HDMV_PGS_SUBTITLE, SPU_ES },
+#endif
+#if 0
     { VLC_CODEC_SPU, CODEC_ID_DVD_SUBTITLE, SPU_ES },
     { VLC_CODEC_DVBS, CODEC_ID_DVB_SUBTITLE, SPU_ES },
     { VLC_CODEC_SUBT, CODEC_ID_TEXT, SPU_ES },
     { VLC_CODEC_XSUB, CODEC_ID_XSUB, SPU_ES },
 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( 51, 50, 0 )
     { VLC_CODEC_SSA, CODEC_ID_SSA, SPU_ES },
+#endif
 #endif
 
     { 0, 0, UNKNOWN_ES }
diff --git a/modules/codec/avcodec/subtitle.c b/modules/codec/avcodec/subtitle.c
new file mode 100644 (file)
index 0000000..f3f8a86
--- /dev/null
@@ -0,0 +1,274 @@
+/*****************************************************************************
+ * subtitle.c: subtitle decoder using ffmpeg library
+ *****************************************************************************
+ * Copyright (C) 2009 Laurent Aimar
+ * $Id$
+ *
+ * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <assert.h>
+
+#include <vlc_common.h>
+#include <vlc_codec.h>
+#include <vlc_avcodec.h>
+#include <vlc_osd.h>
+
+/* ffmpeg header */
+#ifdef HAVE_LIBAVCODEC_AVCODEC_H
+#   include <libavcodec/avcodec.h>
+#   ifdef HAVE_AVCODEC_VAAPI
+#       include <libavcodec/vaapi.h>
+#   endif
+#elif defined(HAVE_FFMPEG_AVCODEC_H)
+#   include <ffmpeg/avcodec.h>
+#else
+#   include <avcodec.h>
+#endif
+
+#include "avcodec.h"
+
+struct decoder_sys_t {
+    FFMPEG_COMMON_MEMBERS
+};
+
+static subpicture_t *ConvertSubtitle(decoder_t *, AVSubtitle *, mtime_t pts);
+
+/**
+ * Initialize subtitle decoder
+ */
+int InitSubtitleDec(decoder_t *dec, AVCodecContext *context,
+                    AVCodec *codec, int codec_id, const char *namecodec)
+{
+    decoder_sys_t *sys;
+
+    /* */
+    dec->p_sys = sys = malloc(sizeof(*sys));
+    if (!sys)
+        return VLC_ENOMEM;
+
+    sys->p_context = context;
+    sys->p_codec = codec;
+    sys->i_codec_id = codec_id;
+    sys->psz_namecodec = namecodec;
+    sys->b_delayed_open = false;
+
+    /* */
+    context->extradata_size = 0;
+    context->extradata = NULL;
+
+    /* */
+    vlc_avcodec_lock();
+    if (avcodec_open(context, codec) < 0) {
+        vlc_avcodec_unlock();
+        msg_Err(dec, "cannot open codec (%s)", namecodec);
+        free(context->extradata);
+        free(sys);
+        return VLC_EGENERIC;
+    }
+    vlc_avcodec_unlock();
+
+    /* */
+    msg_Dbg(dec, "ffmpeg codec (%s) started", namecodec);
+    dec->fmt_out.i_cat = SPU_ES;
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * Decode one subtitle
+ */
+subpicture_t *DecodeSubtitle(decoder_t *dec, block_t **block_ptr)
+{
+    decoder_sys_t *sys = dec->p_sys;
+
+    if (!block_ptr || !*block_ptr)
+        return NULL;
+
+    block_t *block = *block_ptr;
+
+    if (block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED)) {
+        block_Release(block);
+        avcodec_flush_buffers(sys->p_context);
+        return NULL;
+    }
+
+    if (block->i_buffer <= 0) {
+        block_Release(block);
+        return NULL;
+    }
+
+    *block_ptr =
+    block      = block_Realloc(block,
+                               0,
+                               block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE);
+    if (!block)
+        return NULL;
+    block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE;
+    memset(&block->p_buffer[block->i_buffer], 0, FF_INPUT_BUFFER_PADDING_SIZE);
+
+    /* */
+    AVSubtitle subtitle;
+    memset(&subtitle, 0, sizeof(subtitle));
+
+    AVPacket pkt;
+    av_init_packet(&pkt);
+    pkt.data = block->p_buffer;
+    pkt.size = block->i_buffer;
+
+    int has_subtitle = 0;
+    int used = avcodec_decode_subtitle2(sys->p_context,
+                                        &subtitle, &has_subtitle, &pkt);
+
+    if (used < 0) {
+        msg_Warn(dec, "cannot decode one subtitle (%zu bytes)",
+                 block->i_buffer);
+
+        block_Release(block);
+        return NULL;
+    } else if ((size_t)used > block->i_buffer) {
+        used = block->i_buffer;
+    }
+
+    block->i_buffer -= used;
+    block->p_buffer += used;
+
+    /* */
+    subpicture_t *spu = NULL;
+    if (has_subtitle)
+        spu = ConvertSubtitle(dec, &subtitle,
+                              block->i_pts > 0 ? block->i_pts : block->i_dts);
+
+    /* */
+    if (!spu)
+        block_Release(block);
+    return spu;
+}
+
+/**
+ * Clean up private data
+ */
+void EndSubtitleDec(decoder_t *dec)
+{
+    VLC_UNUSED(dec);
+}
+
+/**
+ * Convert a RGBA ffmpeg region to our format.
+ */
+static subpicture_region_t *ConvertRegionRGBA(AVSubtitleRect *ffregion)
+{
+    video_format_t fmt;
+
+    memset(&fmt, 0, sizeof(fmt));
+    fmt.i_chroma         = VLC_FOURCC('R','G','B','A');
+    fmt.i_aspect         = 0;
+    fmt.i_width          =
+    fmt.i_visible_width  = ffregion->w;
+    fmt.i_height         =
+    fmt.i_visible_height = ffregion->h;
+    fmt.i_x_offset       = 0;
+    fmt.i_y_offset       = 0;
+
+    subpicture_region_t *region = subpicture_region_New(&fmt);
+    if (!region)
+        return NULL;
+
+    region->i_x = ffregion->x;
+    region->i_y = ffregion->y;
+    region->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
+
+    const plane_t *p = &region->p_picture->p[0];
+    for (int y = 0; y < ffregion->h; y++) {
+        for (int x = 0; x < ffregion->w; x++) {
+            /* I don't think don't have paletized RGB_A_ */
+            const uint8_t index = ffregion->pict.data[0][y * ffregion->w+x];
+            assert(index < ffregion->nb_colors);
+
+            uint32_t color;
+            memcpy(&color, &ffregion->pict.data[1][4*index], 4);
+
+            uint8_t *p_rgba = &p->p_pixels[y * p->i_pitch + x * p->i_pixel_pitch];
+            p_rgba[0] = (color >> 16) & 0xff;
+            p_rgba[1] = (color >>  8) & 0xff;
+            p_rgba[2] = (color >>  0) & 0xff;
+            p_rgba[3] = (color >> 24) & 0xff;
+        }
+    }
+
+    return region;
+}
+
+/**
+ * Convert a ffmpeg subtitle to our format.
+ */
+static subpicture_t *ConvertSubtitle(decoder_t *dec, AVSubtitle *ffsub, mtime_t pts)
+{
+    subpicture_t *spu = decoder_NewSubpicture(dec);
+    if (!spu)
+        return NULL;
+
+    //msg_Err(dec, "%lld %d %d",
+    //        pts, ffsub->start_display_time, ffsub->end_display_time);
+    spu->i_start    = pts + ffsub->start_display_time * INT64_C(1000);
+    spu->i_stop     = pts + ffsub->end_display_time * INT64_C(1000);
+    spu->b_absolute = true; /* FIXME How to set it right ? */
+    spu->b_ephemer  = true; /* FIXME How to set it right ? */
+    spu->i_original_picture_width =
+        dec->fmt_in.subs.spu.i_original_frame_width;
+    spu->i_original_picture_height =
+        dec->fmt_in.subs.spu.i_original_frame_height;
+
+    subpicture_region_t **region_next = &spu->p_region;
+
+    for (unsigned i = 0; i < ffsub->num_rects; i++) {
+        AVSubtitleRect *rec = ffsub->rects[i];
+
+        msg_Err(dec, "SUBS RECT[%d]: %dx%d @%dx%d",
+                 i, rec->w, rec->h, rec->x, rec->y);
+
+        subpicture_region_t *region;
+        switch (ffsub->format) {
+        case 0:
+            region = ConvertRegionRGBA(rec);
+            break;
+        default:
+            msg_Warn(dec, "unsupported subtitle type");
+            region = NULL;
+            break;
+        }
+        if (region) {
+            *region_next = region;
+            region_next = &region->p_next;
+        }
+        /* Free AVSubtitleRect
+         * FIXME isn't there an avcodec function ? */
+        free(rec->pict.data[0]); /* Plane */
+        free(rec->pict.data[1]); /* Palette */
+        free(rec);
+    }
+    free(ffsub->rects);
+
+    return spu;
+}
+