Commit 6ce3ecfc authored by Laurent Aimar's avatar Laurent Aimar
Browse files

Added SCTE-27 decoder.

parent d2b19589
......@@ -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
......
/*****************************************************************************
* 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);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment