1 /*****************************************************************************
2 * scte27.c : SCTE-27 subtitles decoder
3 *****************************************************************************
4 * Copyright (C) Laurent Aimar
7 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_codec.h>
35 /*****************************************************************************
37 *****************************************************************************/
38 static int Open (vlc_object_t *);
39 static void Close(vlc_object_t *);
42 set_description(N_("SCTE-27 decoder"))
43 set_shortname(N_("SCTE-27"))
44 set_capability( "decoder", 51)
45 set_category(CAT_INPUT)
46 set_subcategory(SUBCAT_INPUT_SCODEC)
47 set_callbacks(Open, Close)
50 /****************************************************************************
52 ****************************************************************************/
53 struct decoder_sys_t {
56 uint8_t *segment_buffer;
65 static const scte27_color_t scte27_color_transparent = {
72 static scte27_color_t bs_read_color(bs_t *bs)
76 /* XXX it's unclear if a value of 0 in Y/U/V means a transparent pixel */
77 color.y = bs_read(bs, 5) << 3;
78 color.alpha = bs_read1(bs) ? 0xff : 0x80;
79 color.u = bs_read(bs, 5) << 3;
80 color.v = bs_read(bs, 5) << 3;
85 static inline void SetYUVPPixel(picture_t *picture, int x, int y, int value)
87 picture->p->p_pixels[y * picture->p->i_pitch + x] = value;
90 static subpicture_region_t *DecodeSimpleBitmap(decoder_t *dec,
91 const uint8_t *data, int size)
94 /* Parse the bitmap and its properties */
96 bs_init(&bs, data, size);
99 int is_framed = bs_read(&bs, 1);
100 int outline_style = bs_read(&bs, 2);
101 scte27_color_t character_color = bs_read_color(&bs);
102 int top_h = bs_read(&bs, 12);
103 int top_v = bs_read(&bs, 12);
104 int bottom_h = bs_read(&bs, 12);
105 int bottom_v = bs_read(&bs, 12);
106 if (top_h >= bottom_h || top_v >= bottom_v)
108 int frame_top_h = top_h;
109 int frame_top_v = top_v;
110 int frame_bottom_h = bottom_h;
111 int frame_bottom_v = bottom_v;
112 scte27_color_t frame_color = scte27_color_transparent;
114 frame_top_h = bs_read(&bs, 12);
115 frame_top_v = bs_read(&bs, 12);
116 frame_bottom_h = bs_read(&bs, 12);
117 frame_bottom_v = bs_read(&bs, 12);
118 frame_color = bs_read_color(&bs);
119 if (frame_top_h > top_h ||
120 frame_top_v > top_v ||
121 frame_bottom_h < bottom_h ||
122 frame_bottom_v < bottom_v)
125 int outline_thickness = 0;
126 scte27_color_t outline_color = scte27_color_transparent;
127 int shadow_right = 0;
128 int shadow_bottom = 0;
129 scte27_color_t shadow_color = scte27_color_transparent;
130 if (outline_style == 1) {
132 outline_thickness = bs_read(&bs, 4);
133 outline_color = bs_read_color(&bs);
134 } else if (outline_style == 2) {
135 shadow_right = bs_read(&bs, 4);
136 shadow_bottom = bs_read(&bs, 4);
137 shadow_color = bs_read_color(&bs);
138 } else if (outline_style == 3) {
141 bs_skip(&bs, 16); // bitmap_compressed_length
142 int bitmap_h = bottom_h - top_h;
143 int bitmap_v = bottom_v - top_v;
144 int bitmap_size = bitmap_h * bitmap_v;
145 bool *bitmap = malloc(bitmap_size * sizeof(*bitmap));
148 for (int position = 0; position < bitmap_size;) {
150 for (; position < bitmap_size; position++)
151 bitmap[position] = false;
155 int run_on_length = 0;
156 int run_off_length = 0;
157 if (!bs_read1(&bs)) {
158 if (!bs_read1(&bs)) {
159 if (!bs_read1(&bs)) {
160 if (bs_read(&bs, 2) == 1) {
161 int next = __MIN((position / bitmap_h + 1) * bitmap_h,
163 for (; position < next; position++)
164 bitmap[position] = false;
177 if (run_on_length > 0) {
178 int run = bs_read(&bs, run_on_length);
180 run = 1 << run_on_length;
181 for (; position < bitmap_size && run > 0; position++, run--)
182 bitmap[position] = true;
184 if (run_off_length > 0) {
185 int run = bs_read(&bs, run_off_length);
187 run = 1 << run_off_length;
188 for (; position < bitmap_size && run > 0; position++, run--)
189 bitmap[position] = false;
193 /* Render the bitmap into a subpicture_region_t */
195 /* Reserve the place for the style
196 * FIXME It's unclear if it is needed or if the bitmap should already include
197 * the needed margin (I think the samples I have do both). */
200 if (outline_style == 1) {
202 margin_v = outline_thickness;
203 } else if (outline_style == 2) {
204 margin_h = shadow_right;
205 margin_v = shadow_bottom;
207 frame_top_h -= margin_h;
208 frame_top_v -= margin_v;
209 frame_bottom_h += margin_h;
210 frame_bottom_v += margin_v;
212 const int frame_h = frame_bottom_h - frame_top_h;
213 const int frame_v = frame_bottom_v - frame_top_v;
214 const int bitmap_oh = top_h - frame_top_h;
215 const int bitmap_ov = top_v - frame_top_v;
223 video_palette_t palette = {
232 [COLOR_CHARACTER] = {
236 character_color.alpha
252 video_format_t fmt = {
253 .i_chroma = VLC_CODEC_YUVP,
256 .i_sar_num = 0, /* Use video AR */
258 .p_palette = &palette,
260 subpicture_region_t *r = subpicture_region_New(&fmt);
265 r->i_x = frame_top_h;
266 r->i_y = frame_top_v;
268 /* Fill up with frame (background) color */
269 for (int y = 0; y < frame_v; y++)
270 memset(&r->p_picture->p->p_pixels[y * r->p_picture->p->i_pitch],
274 /* Draw the outline/shadow if requested */
275 if (outline_style == 1) {
277 * XXX simple but slow and of low quality (no anti-aliasing) */
279 for (int dy = 0; dy <= 15; dy++) {
280 for (int dx = 0; dx <= 15; dx++)
281 circle[dy][dx] = (dx > 0 || dy > 0) &&
282 dx * dx + dy * dy <= outline_thickness * outline_thickness;
284 for (int by = 0; by < bitmap_v; by++) {
285 for (int bx = 0; bx < bitmap_h; bx++) {
286 if (!bitmap[by * bitmap_h + bx])
288 for (int dy = 0; dy <= outline_thickness; dy++) {
289 for (int dx = 0; dx <= outline_thickness; dx++) {
290 if (circle[dy][dx]) {
291 SetYUVPPixel(r->p_picture,
292 bx + bitmap_oh + dx, by + bitmap_ov + dy, COLOR_OUTLINE);
293 SetYUVPPixel(r->p_picture,
294 bx + bitmap_oh - dx, by + bitmap_ov + dy, COLOR_OUTLINE);
295 SetYUVPPixel(r->p_picture,
296 bx + bitmap_oh + dx, by + bitmap_ov - dy, COLOR_OUTLINE);
297 SetYUVPPixel(r->p_picture,
298 bx + bitmap_oh - dx, by + bitmap_ov - dy, COLOR_OUTLINE);
304 } else if (outline_style == 2) {
305 /* Draw a shadow by drawing the character shifted by shaddow right/bottom */
306 for (int by = 0; by < bitmap_v; by++) {
307 for (int bx = 0; bx < bitmap_h; bx++) {
308 if (bitmap[by * bitmap_h + bx])
309 SetYUVPPixel(r->p_picture,
310 bx + bitmap_oh + shadow_right,
311 by + bitmap_ov + shadow_bottom,
317 /* Draw the character */
318 for (int by = 0; by < bitmap_v; by++) {
319 for (int bx = 0; bx < bitmap_h; bx++) {
320 if (bitmap[by * bitmap_h + bx])
321 SetYUVPPixel(r->p_picture,
322 bx + bitmap_oh, by + bitmap_ov, COLOR_CHARACTER);
329 static subpicture_t *DecodeSubtitleMessage(decoder_t *dec,
330 const uint8_t *data, int size,
336 /* Parse the header */
337 bool pre_clear_display = data[3] & 0x80;
338 int display_standard = data[3] & 0x1f;
339 int subtitle_type = data[8] >> 4;
340 int display_duration = ((data[8] & 0x07) << 8) | data[9];
341 int block_length = GetWBE(&data[10]);
346 if (block_length > size)
349 if (subtitle_type == 1) {
350 subpicture_region_t *region = DecodeSimpleBitmap(dec, data, block_length);
353 subpicture_t *sub = decoder_NewSubpicture(dec, NULL);
355 subpicture_region_Delete(region);
359 switch (display_standard) {
361 sub->i_original_picture_width = 720;
362 sub->i_original_picture_height = 480;
363 frame_duration = 33367;
366 sub->i_original_picture_width = 720;
367 sub->i_original_picture_height = 576;
368 frame_duration = 40000;
371 sub->i_original_picture_width = 1280;
372 sub->i_original_picture_height = 720;
373 frame_duration = 16683;
376 sub->i_original_picture_width = 1920;
377 sub->i_original_picture_height = 1080;
378 frame_duration = 16683;
381 msg_Warn(dec, "Unknown display standard");
382 sub->i_original_picture_width = 0;
383 sub->i_original_picture_height = 0;
384 frame_duration = 40000;
387 sub->b_absolute = true;
388 if (!pre_clear_display)
389 msg_Warn(dec, "SCTE-27 subtitles without pre_clear_display flag are not well supported");
390 sub->b_ephemer = true;
392 sub->i_stop = date + display_duration * frame_duration;
393 sub->p_region = region;
402 msg_Err(dec, "corrupted subtitle_message");
406 static subpicture_t *Decode(decoder_t *dec, block_t **block)
408 decoder_sys_t *sys = dec->p_sys;
410 if (block == NULL || *block == NULL)
412 block_t *b = *block; *block = NULL;
414 subpicture_t *sub_first = NULL;
415 subpicture_t **sub_last = &sub_first;
417 if (b->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED))
420 while (b->i_buffer > 3) {
421 const int table_id = b->p_buffer[0];
422 if (table_id != 0xc6) {
423 //if (table_id != 0xff)
424 // msg_Err(dec, "Invalid SCTE-27 table id (0x%x)", table_id);
427 const int section_length = ((b->p_buffer[1] & 0xf) << 8) | b->p_buffer[2];
428 if (section_length <= 1 + 4 || b->i_buffer < 3 + (unsigned)section_length) {
429 msg_Err(dec, "Invalid SCTE-27 section length");
432 const int protocol_version = b->p_buffer[3] & 0x3f;
433 if (protocol_version != 0) {
434 msg_Err(dec, "Unsupported SCTE-27 protocol version (%d)", protocol_version);
437 const bool segmentation_overlay = b->p_buffer[3] & 0x40;
439 subpicture_t *sub = NULL;
440 if (segmentation_overlay) {
441 if (section_length < 1 + 5 + 4)
443 int id = GetWBE(&b->p_buffer[4]);
444 int last = (b->p_buffer[6] << 4) | (b->p_buffer[7] >> 4);
445 int index = ((b->p_buffer[7] & 0x0f) << 8) | b->p_buffer[8];
449 sys->segment_id = id;
450 sys->segment_size = 0;
451 sys->segment_date = b->i_pts > VLC_TS_INVALID ? b->i_pts : b->i_dts;
453 if (sys->segment_id != id || sys->segment_size <= 0) {
454 sys->segment_id = -1;
459 int segment_size = section_length - 1 - 5 - 4;
461 sys->segment_buffer = xrealloc(sys->segment_buffer,
462 sys->segment_size + segment_size);
463 memcpy(&sys->segment_buffer[sys->segment_size],
464 &b->p_buffer[9], segment_size);
465 sys->segment_size += segment_size;
468 sub = DecodeSubtitleMessage(dec,
472 sys->segment_size = 0;
475 sub = DecodeSubtitleMessage(dec,
477 section_length - 1 - 4,
478 b->i_pts > VLC_TS_INVALID ? b->i_pts : b->i_dts);
482 sub_last = &(*sub_last)->p_next;
484 b->i_buffer -= 3 + section_length;
485 b->p_buffer += 3 + section_length;
494 static int Open(vlc_object_t *object)
496 decoder_t *dec = (decoder_t *)object;
498 if (dec->fmt_in.i_codec != VLC_CODEC_SCTE_27)
501 decoder_sys_t *sys = dec->p_sys = malloc(sizeof(*sys));
504 sys->segment_id = -1;
505 sys->segment_size = 0;
506 sys->segment_buffer = NULL;
508 dec->pf_decode_sub = Decode;
509 es_format_Init(&dec->fmt_out, SPU_ES, VLC_CODEC_SPU);
510 dec->fmt_out.video.i_chroma = VLC_CODEC_YUVP;
515 static void Close(vlc_object_t *object)
517 decoder_t *dec = (decoder_t *)object;
518 decoder_sys_t *sys = dec->p_sys;
520 free(sys->segment_buffer);