]> git.sesse.net Git - vlc/commitdiff
Added SCTE-27 decoder.
authorLaurent Aimar <fenrir@videolan.org>
Thu, 6 Dec 2012 21:28:20 +0000 (22:28 +0100)
committerLaurent Aimar <fenrir@videolan.org>
Wed, 12 Dec 2012 20:22:12 +0000 (21:22 +0100)
modules/codec/Modules.am
modules/codec/scte27.c [new file with mode: 0644]

index d701a26806c9fe91e0076d4e4a68db1a8dc4dded..2f624f9ab47156f826896a38b869949b3832db8e 100644 (file)
@@ -56,6 +56,7 @@ SOURCES_dmo = dmo/dmo.c dmo/dmo.h dmo/buffer.c
 SOURCES_ddummy = ddummy.c
 SOURCES_edummy = edummy.c
 SOURCES_fdkaac = fdkaac.c
+SOURCES_scte27 = scte27.c
 
 libvlc_LTLIBRARIES += \
        liba52_plugin.la \
@@ -77,6 +78,7 @@ libvlc_LTLIBRARIES += \
        libsubsdec_plugin.la \
        libsubsusf_plugin.la \
        libstl_plugin.la \
+       libscte27_plugin.la \
        $(NULL)
 
 if ENABLE_SOUT
diff --git a/modules/codec/scte27.c b/modules/codec/scte27.c
new file mode 100644 (file)
index 0000000..29a3482
--- /dev/null
@@ -0,0 +1,523 @@
+/*****************************************************************************
+ * scte27.c : SCTE-27 subtitles decoder
+ *****************************************************************************
+ * Copyright (C) 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 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.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_codec.h>
+#include <vlc_bits.h>
+
+#include <assert.h>
+
+/*****************************************************************************
+ * Module descriptor.
+ *****************************************************************************/
+static int  Open (vlc_object_t *);
+static void Close(vlc_object_t *);
+
+vlc_module_begin ()
+    set_description(N_("SCTE-27 decoder"))
+    set_shortname(N_("SCTE-27"))
+    set_capability( "decoder", 51)
+    set_category(CAT_INPUT)
+    set_subcategory(SUBCAT_INPUT_SCODEC)
+    set_callbacks(Open, Close)
+vlc_module_end ()
+
+/****************************************************************************
+ * Local prototypes
+ ****************************************************************************/
+struct decoder_sys_t {
+    int     segment_id;
+    int     segment_size;
+    uint8_t *segment_buffer;
+    mtime_t segment_date;
+};
+
+typedef struct {
+    uint8_t y, u, v;
+    uint8_t alpha;
+} scte27_color_t;
+
+static const scte27_color_t scte27_color_transparent = {
+    .y     = 0x00,
+    .u     = 0x80,
+    .v     = 0x80,
+    .alpha = 0x00,
+};
+
+static scte27_color_t bs_read_color(bs_t *bs)
+{
+    scte27_color_t color;
+
+    /* XXX it's unclear if a value of 0 in Y/U/V means a transparent pixel */
+    color.y     = bs_read(bs, 5) << 3;
+    color.alpha = bs_read1(bs) ? 0xff : 0x80;
+    color.u     = bs_read(bs, 5) << 3;
+    color.v     = bs_read(bs, 5) << 3;
+
+    return color;
+}
+
+static inline void SetPixel(picture_t *picture, int x, int y, int value)
+{
+    picture->p->p_pixels[y * picture->p->i_pitch + x] = value;
+}
+
+static subpicture_region_t *DecodeSimpleBitmap(decoder_t *dec,
+                                               const uint8_t *data, int size)
+{
+    VLC_UNUSED(dec);
+    /* Parse the bitmap and its properties */
+    bs_t bs;
+    bs_init(&bs, data, size);
+
+    bs_skip(&bs, 5);
+    int is_framed = bs_read(&bs, 1);
+    int outline_style = bs_read(&bs, 2);
+    scte27_color_t character_color = bs_read_color(&bs);
+    int top_h = bs_read(&bs, 12);
+    int top_v = bs_read(&bs, 12);
+    int bottom_h = bs_read(&bs, 12);
+    int bottom_v = bs_read(&bs, 12);
+    if (top_h >= bottom_h || top_v >= bottom_v)
+        return NULL;
+    int frame_top_h = top_h;
+    int frame_top_v = top_v;
+    int frame_bottom_h = bottom_h;
+    int frame_bottom_v = bottom_v;
+    scte27_color_t frame_color = scte27_color_transparent;
+    if (is_framed) {
+        frame_top_h = bs_read(&bs, 12);
+        frame_top_v = bs_read(&bs, 12);
+        frame_bottom_h = bs_read(&bs, 12);
+        frame_bottom_v = bs_read(&bs, 12);
+        frame_color = bs_read_color(&bs);
+        if (frame_top_h > top_h ||
+            frame_top_v > top_v ||
+            frame_bottom_h < bottom_h ||
+            frame_bottom_v < bottom_v)
+            return NULL;
+    }
+    int outline_thickness = 0;
+    scte27_color_t outline_color = scte27_color_transparent;
+    int shadow_right = 0;
+    int shadow_bottom = 0;
+    scte27_color_t shadow_color = scte27_color_transparent;
+    if (outline_style == 1) {
+        bs_skip(&bs, 4);
+        outline_thickness = bs_read(&bs, 4);
+        outline_color = bs_read_color(&bs);
+    } else if (outline_style == 2) {
+        shadow_right = bs_read(&bs, 4);
+        shadow_bottom = bs_read(&bs, 4);
+        shadow_color = bs_read_color(&bs);
+    } else if (outline_style == 3) {
+        bs_skip(&bs, 24);
+    }
+    bs_skip(&bs, 16); // bitmap_compressed_length
+    int bitmap_h = bottom_h - top_h;
+    int bitmap_v = bottom_v - top_v;
+    int bitmap_size = bitmap_h * bitmap_v;
+    bool *bitmap = malloc(bitmap_size * sizeof(*bitmap));
+    if (!bitmap)
+        return NULL;
+    for (int position = 0; position < bitmap_size;) {
+        if (bs_eof(&bs)) {
+            for (; position < bitmap_size; position++)
+                bitmap[position] = false;
+            break;
+        }
+
+        int run_on_length = 0;
+        int run_off_length = 0;
+        if (!bs_read1(&bs)) {
+            if (!bs_read1(&bs)) {
+                if (!bs_read1(&bs)) {
+                    if (bs_read(&bs, 2) == 1) {
+                        int next = __MIN((position / bitmap_h + 1) * bitmap_h,
+                                         bitmap_size);
+                        for (; position < next; position++)
+                            bitmap[position] = false;
+                    }
+                } else {
+                    run_on_length = 4;
+                }
+            } else {
+                run_off_length = 6;
+            }
+        } else {
+            run_on_length = 3;
+            run_off_length = 5;
+        }
+
+        if (run_on_length > 0) {
+            int run = bs_read(&bs, run_on_length);
+            if (!run)
+                run = 1 << run_on_length;
+            for (; position < bitmap_size && run > 0; position++, run--)
+                bitmap[position] = true;
+        }
+        if (run_off_length > 0) {
+            int run = bs_read(&bs, run_off_length);
+            if (!run)
+                run = 1 << run_off_length;
+            for (; position < bitmap_size && run > 0; position++, run--)
+                bitmap[position] = false;
+        }
+    }
+
+    /* Render the bitmap into a subpicture_region_t */
+
+    /* Reserve the place for the style
+     * FIXME It's unclear if it is needed or if the bitmap should already include
+     * the needed margin (I think the samples I have do both). */
+    int margin_h = 0;
+    int margin_v = 0;
+    if (outline_style == 1) {
+        margin_h =
+        margin_v = outline_thickness;
+    } else if (outline_style == 2) {
+        margin_h = shadow_right;
+        margin_v = shadow_bottom;
+    }
+    frame_top_h -= margin_h;
+    frame_top_v -= margin_v;
+    frame_bottom_h += margin_h;
+    frame_bottom_v += margin_v;
+
+    const int frame_h = frame_bottom_h - frame_top_h;
+    const int frame_v = frame_bottom_v - frame_top_v;
+    const int bitmap_oh = top_h - frame_top_h;
+    const int bitmap_ov = top_v - frame_top_v;
+
+    enum {
+        COLOR_FRAME,
+        COLOR_CHARACTER,
+        COLOR_OUTLINE,
+        COLOR_SHADOW,
+    };
+    video_palette_t palette = {
+        .i_entries = 4,
+        .palette = {
+            [COLOR_FRAME] = {
+                frame_color.y,
+                frame_color.u,
+                frame_color.v,
+                frame_color.alpha
+            },
+            [COLOR_CHARACTER] = {
+                character_color.y,
+                character_color.u,
+                character_color.v,
+                character_color.alpha
+            },
+            [COLOR_OUTLINE] = {
+                outline_color.y,
+                outline_color.u,
+                outline_color.v,
+                outline_color.alpha
+            },
+            [COLOR_SHADOW] = {
+                shadow_color.y,
+                shadow_color.u,
+                shadow_color.v,
+                shadow_color.alpha
+            },
+        },
+    };
+    video_format_t fmt = {
+        .i_chroma = VLC_CODEC_YUVP,
+        .i_width = frame_h,
+        .i_height = frame_v,
+        .i_sar_num = 0, /* Use video AR */
+        .i_sar_den = 1,
+        .p_palette = &palette,
+    };
+    subpicture_region_t *r = subpicture_region_New(&fmt);
+    if (!r) {
+        free(bitmap);
+        return NULL;
+    }
+    r->i_x = frame_top_h;
+    r->i_y = frame_top_v;
+
+    /* Fill up with frame (background) color */
+    for (int y = 0; y < frame_v; y++)
+        memset(&r->p_picture->p->p_pixels[y * r->p_picture->p->i_pitch],
+               COLOR_FRAME,
+               frame_h);
+
+    /* Draw the outline/shadow if requested */
+    if (outline_style == 1) {
+        /* Draw an outline
+         * XXX simple but slow and of low quality (no anti-aliasing) */
+        bool circle[16][16];
+        for (int dy = 0; dy <= 15; dy++) {
+            for (int dx = 0; dx <= 15; dx++)
+                circle[dy][dx] = (dx > 0 || dy > 0) &&
+                                 dx * dx + dy * dy <= outline_thickness * outline_thickness;
+        }
+        for (int by = 0; by < bitmap_v; by++) {
+            for (int bx = 0; bx < bitmap_h; bx++) {
+                if (!bitmap[by * bitmap_h + bx])
+                    continue;
+                for (int dy = 0; dy <= outline_thickness; dy++) {
+                    for (int dx = 0; dx <= outline_thickness; dx++) {
+                        if (circle[dy][dx]) {
+                            SetPixel(r->p_picture,
+                                     bx + bitmap_oh + dx, by + bitmap_ov + dy, COLOR_OUTLINE);
+                            SetPixel(r->p_picture,
+                                     bx + bitmap_oh - dx, by + bitmap_ov + dy, COLOR_OUTLINE);
+                            SetPixel(r->p_picture,
+                                     bx + bitmap_oh + dx, by + bitmap_ov - dy, COLOR_OUTLINE);
+                            SetPixel(r->p_picture,
+                                     bx + bitmap_oh - dx, by + bitmap_ov - dy, COLOR_OUTLINE);
+                        }
+                    }
+                }
+            }
+        }
+    } else if (outline_style == 2) {
+        /* Draw a shadow by drawing the character shifted by shaddow right/bottom */
+        for (int by = 0; by < bitmap_v; by++) {
+            for (int bx = 0; bx < bitmap_h; bx++) {
+                if (bitmap[by * bitmap_h + bx])
+                    SetPixel(r->p_picture,
+                             bx + bitmap_oh + shadow_right,
+                             by + bitmap_ov + shadow_bottom,
+                             COLOR_SHADOW);
+            }
+        }
+    }
+
+    /* Draw the character */
+    for (int by = 0; by < bitmap_v; by++) {
+        for (int bx = 0; bx < bitmap_h; bx++) {
+            if (bitmap[by * bitmap_h + bx])
+                SetPixel(r->p_picture,
+                         bx + bitmap_oh, by + bitmap_ov, COLOR_CHARACTER);
+        }
+    }
+    free(bitmap);
+    return r;
+}
+
+static subpicture_t *DecodeSubtitleMessage(decoder_t *dec,
+                                           const uint8_t *data, int size,
+                                           mtime_t date)
+{
+    if (size < 12)
+        goto error;
+
+    /* Parse the header */
+    bool pre_clear_display = data[3] & 0x80;
+    int  display_standard = data[3] & 0x1f;
+    int subtitle_type = data[8] >> 4;
+    int display_duration = ((data[8] & 0x07) << 8) | data[9];
+    int block_length = GetWBE(&data[10]);
+
+    size -= 12;
+    data += 12;
+
+    if (block_length > size)
+        goto error;
+
+    if (subtitle_type == 1) {
+        subpicture_region_t *region = DecodeSimpleBitmap(dec, data, block_length);
+        if (!region)
+            goto error;
+        subpicture_t *sub = decoder_NewSubpicture(dec, NULL);
+        if (!sub) {
+            subpicture_region_Delete(region);
+            return NULL;
+        }
+        int frame_duration;
+        switch (display_standard) {
+        case 0:
+            sub->i_original_picture_width  = 720;
+            sub->i_original_picture_height = 480;
+            frame_duration = 33367;
+            break;
+        case 1:
+            sub->i_original_picture_width  = 720;
+            sub->i_original_picture_height = 576;
+            frame_duration = 40000;
+            break;
+        case 2:
+            sub->i_original_picture_width  = 1280;
+            sub->i_original_picture_height =  720;
+            frame_duration = 16683;
+            break;
+        case 3:
+            sub->i_original_picture_width  = 1920;
+            sub->i_original_picture_height = 1080;
+            frame_duration = 16683;
+            break;
+        default:
+            msg_Warn(dec, "Unknown display standard");
+            sub->i_original_picture_width  = 0;
+            sub->i_original_picture_height = 0;
+            frame_duration = 40000;
+            break;
+        }
+        sub->b_absolute = true;
+        if (!pre_clear_display)
+            msg_Warn(dec, "SCTE-27 subtitles without pre_clear_display flag are not well supported");
+        sub->b_ephemer = true;
+        sub->i_start = date;
+        sub->i_stop = date + display_duration * frame_duration;
+        sub->p_region = region;
+
+        return sub;
+    } else {
+        /* Reserved */
+        return NULL;
+    }
+
+error:
+    msg_Err(dec, "corrupted subtitle_message");
+    return NULL;
+}
+
+static subpicture_t *Decode(decoder_t *dec, block_t **block)
+{
+    decoder_sys_t *sys = dec->p_sys;
+
+    if (block == NULL || *block == NULL)
+        return NULL;
+    block_t *b = *block; *block = NULL;
+
+    subpicture_t *sub_first = NULL;
+    subpicture_t **sub_last = &sub_first;
+
+    if (b->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED))
+        goto exit;
+
+    while (b->i_buffer > 3) {
+        const int table_id =  b->p_buffer[0];
+        if (table_id != 0xc6) {
+            //if (table_id != 0xff)
+            //    msg_Err(dec, "Invalid SCTE-27 table id (0x%x)", table_id);
+            break;
+        }
+        const int section_length = ((b->p_buffer[1] & 0xf) << 8) | b->p_buffer[2];
+        if (section_length <= 1 + 4 || b->i_buffer < 3 + (unsigned)section_length) {
+            msg_Err(dec, "Invalid SCTE-27 section length");
+            break;
+        }
+        const int protocol_version = b->p_buffer[3] & 0x3f;
+        if (protocol_version != 0) {
+            msg_Err(dec, "Unsupported SCTE-27 protocol version (%d)", protocol_version);
+            break;
+        }
+        const bool segmentation_overlay = b->p_buffer[3] & 0x40;
+
+        subpicture_t *sub = NULL;
+        if (segmentation_overlay) {
+            if (section_length < 1 + 5 + 4)
+                break;
+            int id = GetWBE(&b->p_buffer[4]);
+            int last = (b->p_buffer[6] << 4) | (b->p_buffer[7] >> 4);
+            int index = ((b->p_buffer[7] & 0x0f) << 8) | b->p_buffer[8];
+            if (index > last)
+                break;
+            if (index == 0) {
+                sys->segment_id = id;
+                sys->segment_size = 0;
+                sys->segment_date = b->i_pts > VLC_TS_INVALID ? b->i_pts : b->i_dts;
+            } else {
+                if (sys->segment_id != id || sys->segment_size <= 0) {
+                    sys->segment_id = -1;
+                    break;
+                }
+            }
+
+            int segment_size = section_length - 1 - 5 - 4;
+
+            sys->segment_buffer = xrealloc(sys->segment_buffer,
+                                           sys->segment_size + segment_size);
+            memcpy(&sys->segment_buffer[sys->segment_size],
+                   &b->p_buffer[9], segment_size);
+            sys->segment_size += segment_size;
+
+            if (index == last) {
+                sub = DecodeSubtitleMessage(dec,
+                                            sys->segment_buffer,
+                                            sys->segment_size,
+                                            sys->segment_date);
+                sys->segment_size = 0;
+            }
+        } else {
+            sub = DecodeSubtitleMessage(dec,
+                                        &b->p_buffer[4],
+                                        section_length - 1 - 4,
+                                        b->i_pts > VLC_TS_INVALID ? b->i_pts : b->i_dts);
+        }
+        *sub_last = sub;
+        if (*sub_last)
+            sub_last = &(*sub_last)->p_next;
+
+        b->i_buffer -= 3 + section_length;
+        b->p_buffer += 3 + section_length;
+        break;
+    }
+
+exit:
+    block_Release(b);
+    return sub_first;
+}
+
+static int Open(vlc_object_t *object)
+{
+    decoder_t *dec = (decoder_t *)object;
+
+    if (dec->fmt_in.i_codec != VLC_CODEC_SCTE_27)
+        return VLC_EGENERIC;
+
+    decoder_sys_t *sys = dec->p_sys = malloc(sizeof(*sys));
+    if (!sys)
+        return VLC_ENOMEM;
+    sys->segment_id = -1;
+    sys->segment_size = 0;
+    sys->segment_buffer = NULL;
+
+    dec->pf_decode_sub = Decode;
+    es_format_Init(&dec->fmt_out, SPU_ES, VLC_CODEC_SPU);
+    dec->fmt_out.video.i_chroma = VLC_CODEC_YUVP;
+
+    return VLC_SUCCESS;
+}
+
+static void Close(vlc_object_t *object)
+{
+    decoder_t *dec = (decoder_t *)object;
+    decoder_sys_t *sys = dec->p_sys;
+
+    free(sys->segment_buffer);
+    free(sys);
+}
+