Coverage Report

Created: 2025-08-25 07:17

/src/vlc/modules/codec/scte27.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * scte27.c : SCTE-27 subtitles decoder
3
 *****************************************************************************
4
 * Copyright (C) Laurent Aimar
5
 *
6
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
7
 *
8
 * This program is free software; you can redistribute it and/or modify it
9
 * under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program; if not, write to the Free Software Foundation,
20
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21
 *****************************************************************************/
22
23
#ifdef HAVE_CONFIG_H
24
# include "config.h"
25
#endif
26
27
#include <vlc_common.h>
28
#include <vlc_plugin.h>
29
#include <vlc_codec.h>
30
#include <vlc_bits.h>
31
32
#include <assert.h>
33
34
/*****************************************************************************
35
 * Module descriptor.
36
 *****************************************************************************/
37
static int  Open (vlc_object_t *);
38
static void Close(vlc_object_t *);
39
40
100
vlc_module_begin ()
41
50
    set_description(N_("SCTE-27 decoder"))
42
50
    set_shortname(N_("SCTE-27"))
43
50
    set_capability( "spu decoder", 51)
44
50
    set_subcategory(SUBCAT_INPUT_SCODEC)
45
50
    set_callbacks(Open, Close)
46
50
vlc_module_end ()
47
48
/****************************************************************************
49
 * Local prototypes
50
 ****************************************************************************/
51
typedef struct
52
{
53
    int     segment_id;
54
    int     segment_size;
55
    uint8_t *segment_buffer;
56
    vlc_tick_t segment_date;
57
} decoder_sys_t;
58
59
typedef struct {
60
    uint8_t y, u, v;
61
    uint8_t alpha;
62
} scte27_color_t;
63
64
static const scte27_color_t scte27_color_transparent = {
65
    .y     = 0x00,
66
    .u     = 0x80,
67
    .v     = 0x80,
68
    .alpha = 0x00,
69
};
70
71
static scte27_color_t bs_read_color(bs_t *bs)
72
0
{
73
0
    scte27_color_t color;
74
75
    /* XXX it's unclear if a value of 0 in Y/U/V means a transparent pixel */
76
0
    color.y     = bs_read(bs, 5) << 3;
77
0
    color.alpha = bs_read1(bs) ? 0xff : 0x80;
78
0
    color.v     = bs_read(bs, 5) << 3;
79
0
    color.u     = bs_read(bs, 5) << 3;
80
81
0
    return color;
82
0
}
83
84
static inline void SetYUVPPixel(picture_t *picture, int x, int y, int value)
85
0
{
86
0
    picture->p->p_pixels[y * picture->p->i_pitch + x] = value;
87
0
}
88
89
static subpicture_region_t *DecodeSimpleBitmap(decoder_t *dec,
90
                                               const uint8_t *data, int size)
91
0
{
92
0
    VLC_UNUSED(dec);
93
    /* Parse the bitmap and its properties */
94
0
    bs_t bs;
95
0
    bs_init(&bs, data, size);
96
97
0
    bs_skip(&bs, 5);
98
0
    int is_framed = bs_read(&bs, 1);
99
0
    int outline_style = bs_read(&bs, 2);
100
0
    scte27_color_t character_color = bs_read_color(&bs);
101
0
    int top_h = bs_read(&bs, 12);
102
0
    int top_v = bs_read(&bs, 12);
103
0
    int bottom_h = bs_read(&bs, 12);
104
0
    int bottom_v = bs_read(&bs, 12);
105
0
    if (top_h >= bottom_h || top_v >= bottom_v)
106
0
        return NULL;
107
0
    int frame_top_h = top_h;
108
0
    int frame_top_v = top_v;
109
0
    int frame_bottom_h = bottom_h;
110
0
    int frame_bottom_v = bottom_v;
111
0
    scte27_color_t frame_color = scte27_color_transparent;
112
0
    if (is_framed) {
113
0
        frame_top_h = bs_read(&bs, 12);
114
0
        frame_top_v = bs_read(&bs, 12);
115
0
        frame_bottom_h = bs_read(&bs, 12);
116
0
        frame_bottom_v = bs_read(&bs, 12);
117
0
        frame_color = bs_read_color(&bs);
118
0
        if (frame_top_h > top_h ||
119
0
            frame_top_v > top_v ||
120
0
            frame_bottom_h < bottom_h ||
121
0
            frame_bottom_v < bottom_v)
122
0
            return NULL;
123
0
    }
124
0
    int outline_thickness = 0;
125
0
    scte27_color_t outline_color = scte27_color_transparent;
126
0
    int shadow_right = 0;
127
0
    int shadow_bottom = 0;
128
0
    scte27_color_t shadow_color = scte27_color_transparent;
129
0
    if (outline_style == 1) {
130
0
        bs_skip(&bs, 4);
131
0
        outline_thickness = bs_read(&bs, 4);
132
0
        outline_color = bs_read_color(&bs);
133
0
    } else if (outline_style == 2) {
134
0
        shadow_right = bs_read(&bs, 4);
135
0
        shadow_bottom = bs_read(&bs, 4);
136
0
        shadow_color = bs_read_color(&bs);
137
0
    } else if (outline_style == 3) {
138
0
        bs_skip(&bs, 24);
139
0
    }
140
0
    bs_skip(&bs, 16); // bitmap_compressed_length
141
0
    int bitmap_h = bottom_h - top_h;
142
0
    int bitmap_v = bottom_v - top_v;
143
0
    int bitmap_size = bitmap_h * bitmap_v;
144
0
    bool *bitmap = vlc_alloc(bitmap_size, sizeof(*bitmap));
145
0
    if (!bitmap)
146
0
        return NULL;
147
0
    for (int position = 0; position < bitmap_size;) {
148
0
        if (bs_eof(&bs)) {
149
0
            for (; position < bitmap_size; position++)
150
0
                bitmap[position] = false;
151
0
            break;
152
0
        }
153
154
0
        int run_on_length = 0;
155
0
        int run_off_length = 0;
156
0
        if (!bs_read1(&bs)) {
157
0
            if (!bs_read1(&bs)) {
158
0
                if (!bs_read1(&bs)) {
159
0
                    if (bs_read(&bs, 2) == 1) {
160
0
                        int next = __MIN((position / bitmap_h + 1) * bitmap_h,
161
0
                                         bitmap_size);
162
0
                        for (; position < next; position++)
163
0
                            bitmap[position] = false;
164
0
                    }
165
0
                } else {
166
0
                    run_on_length = 4;
167
0
                }
168
0
            } else {
169
0
                run_off_length = 6;
170
0
            }
171
0
        } else {
172
0
            run_on_length = 3;
173
0
            run_off_length = 5;
174
0
        }
175
176
0
        if (run_on_length > 0) {
177
0
            int run = bs_read(&bs, run_on_length);
178
0
            if (!run)
179
0
                run = 1 << run_on_length;
180
0
            for (; position < bitmap_size && run > 0; position++, run--)
181
0
                bitmap[position] = true;
182
0
        }
183
0
        if (run_off_length > 0) {
184
0
            int run = bs_read(&bs, run_off_length);
185
0
            if (!run)
186
0
                run = 1 << run_off_length;
187
0
            for (; position < bitmap_size && run > 0; position++, run--)
188
0
                bitmap[position] = false;
189
0
        }
190
0
    }
191
192
    /* Render the bitmap into a subpicture_region_t */
193
194
    /* Reserve the place for the style
195
     * FIXME It's unclear if it is needed or if the bitmap should already include
196
     * the needed margin (I think the samples I have do both). */
197
0
    int margin_h = 0;
198
0
    int margin_v = 0;
199
0
    if (outline_style == 1) {
200
0
        margin_h =
201
0
        margin_v = outline_thickness;
202
0
    } else if (outline_style == 2) {
203
0
        margin_h = shadow_right;
204
0
        margin_v = shadow_bottom;
205
0
    }
206
0
    frame_top_h -= margin_h;
207
0
    frame_top_v -= margin_v;
208
0
    frame_bottom_h += margin_h;
209
0
    frame_bottom_v += margin_v;
210
211
0
    const int frame_h = frame_bottom_h - frame_top_h;
212
0
    const int frame_v = frame_bottom_v - frame_top_v;
213
0
    const int bitmap_oh = top_h - frame_top_h;
214
0
    const int bitmap_ov = top_v - frame_top_v;
215
216
0
    enum {
217
0
        COLOR_FRAME,
218
0
        COLOR_CHARACTER,
219
0
        COLOR_OUTLINE,
220
0
        COLOR_SHADOW,
221
0
    };
222
0
    video_palette_t palette = {
223
0
        .i_entries = 4,
224
0
        .palette = {
225
0
            [COLOR_FRAME] = {
226
0
                frame_color.y,
227
0
                frame_color.u,
228
0
                frame_color.v,
229
0
                frame_color.alpha
230
0
            },
231
0
            [COLOR_CHARACTER] = {
232
0
                character_color.y,
233
0
                character_color.u,
234
0
                character_color.v,
235
0
                character_color.alpha
236
0
            },
237
0
            [COLOR_OUTLINE] = {
238
0
                outline_color.y,
239
0
                outline_color.u,
240
0
                outline_color.v,
241
0
                outline_color.alpha
242
0
            },
243
0
            [COLOR_SHADOW] = {
244
0
                shadow_color.y,
245
0
                shadow_color.u,
246
0
                shadow_color.v,
247
0
                shadow_color.alpha
248
0
            },
249
0
        },
250
0
    };
251
0
    video_format_t fmt = {
252
0
        .i_chroma = VLC_CODEC_YUVP,
253
0
        .i_width = frame_h,
254
0
        .i_visible_width = frame_h,
255
0
        .i_height = frame_v,
256
0
        .i_visible_height = frame_v,
257
0
        .i_sar_num = 0, /* Use video AR */
258
0
        .i_sar_den = 1,
259
0
        .p_palette = &palette,
260
0
    };
261
0
    subpicture_region_t *r = subpicture_region_New(&fmt);
262
0
    if (!r) {
263
0
        free(bitmap);
264
0
        return NULL;
265
0
    }
266
0
    r->i_x = frame_top_h;
267
0
    r->i_y = frame_top_v;
268
269
    /* Fill up with frame (background) color */
270
0
    for (int y = 0; y < frame_v; y++)
271
0
        memset(&r->p_picture->p->p_pixels[y * r->p_picture->p->i_pitch],
272
0
               COLOR_FRAME,
273
0
               frame_h);
274
275
    /* Draw the outline/shadow if requested */
276
0
    if (outline_style == 1) {
277
        /* Draw an outline
278
         * XXX simple but slow and of low quality (no anti-aliasing) */
279
0
        bool circle[16][16];
280
0
        for (int dy = 0; dy <= 15; dy++) {
281
0
            for (int dx = 0; dx <= 15; dx++)
282
0
                circle[dy][dx] = (dx > 0 || dy > 0) &&
283
0
                                 dx * dx + dy * dy <= outline_thickness * outline_thickness;
284
0
        }
285
0
        for (int by = 0; by < bitmap_v; by++) {
286
0
            for (int bx = 0; bx < bitmap_h; bx++) {
287
0
                if (!bitmap[by * bitmap_h + bx])
288
0
                    continue;
289
0
                for (int dy = 0; dy <= outline_thickness; dy++) {
290
0
                    for (int dx = 0; dx <= outline_thickness; dx++) {
291
0
                        if (circle[dy][dx]) {
292
0
                            SetYUVPPixel(r->p_picture,
293
0
                                         bx + bitmap_oh + dx, by + bitmap_ov + dy, COLOR_OUTLINE);
294
0
                            SetYUVPPixel(r->p_picture,
295
0
                                         bx + bitmap_oh - dx, by + bitmap_ov + dy, COLOR_OUTLINE);
296
0
                            SetYUVPPixel(r->p_picture,
297
0
                                         bx + bitmap_oh + dx, by + bitmap_ov - dy, COLOR_OUTLINE);
298
0
                            SetYUVPPixel(r->p_picture,
299
0
                                         bx + bitmap_oh - dx, by + bitmap_ov - dy, COLOR_OUTLINE);
300
0
                        }
301
0
                    }
302
0
                }
303
0
            }
304
0
        }
305
0
    } else if (outline_style == 2) {
306
        /* Draw a shadow by drawing the character shifted by shadow right/bottom */
307
0
        for (int by = 0; by < bitmap_v; by++) {
308
0
            for (int bx = 0; bx < bitmap_h; bx++) {
309
0
                if (bitmap[by * bitmap_h + bx])
310
0
                    SetYUVPPixel(r->p_picture,
311
0
                                 bx + bitmap_oh + shadow_right,
312
0
                                 by + bitmap_ov + shadow_bottom,
313
0
                                 COLOR_SHADOW);
314
0
            }
315
0
        }
316
0
    }
317
318
    /* Draw the character */
319
0
    for (int by = 0; by < bitmap_v; by++) {
320
0
        for (int bx = 0; bx < bitmap_h; bx++) {
321
0
            if (bitmap[by * bitmap_h + bx])
322
0
                SetYUVPPixel(r->p_picture,
323
0
                             bx + bitmap_oh, by + bitmap_ov, COLOR_CHARACTER);
324
0
        }
325
0
    }
326
0
    free(bitmap);
327
0
    return r;
328
0
}
329
330
static subpicture_t *DecodeSubtitleMessage(decoder_t *dec,
331
                                           const uint8_t *data, int size,
332
                                           vlc_tick_t date)
333
0
{
334
0
    if (size < 12)
335
0
        goto error;
336
337
    /* Parse the header */
338
0
    bool pre_clear_display = data[3] & 0x80;
339
0
    int  display_standard = data[3] & 0x1f;
340
0
    int subtitle_type = data[8] >> 4;
341
0
    int display_duration = ((data[8] & 0x07) << 8) | data[9];
342
0
    int block_length = GetWBE(&data[10]);
343
344
0
    size -= 12;
345
0
    data += 12;
346
347
0
    if (block_length > size)
348
0
        goto error;
349
350
0
    if (subtitle_type != 1) {
351
        /* Reserved */
352
0
        return NULL;
353
0
    }
354
355
0
    subpicture_region_t *region = DecodeSimpleBitmap(dec, data, block_length);
356
0
    if (!region)
357
0
        goto error;
358
0
    subpicture_t *sub = decoder_NewSubpicture(dec, NULL);
359
0
    if (!sub) {
360
0
        subpicture_region_Delete(region);
361
0
        return NULL;
362
0
    }
363
0
    vlc_tick_t frame_duration;
364
0
    switch (display_standard) {
365
0
    case 0:
366
0
        sub->i_original_picture_width  = 720;
367
0
        sub->i_original_picture_height = 480;
368
0
        frame_duration = VLC_TICK_FROM_US(33367);
369
0
        break;
370
0
    case 1:
371
0
        sub->i_original_picture_width  = 720;
372
0
        sub->i_original_picture_height = 576;
373
0
        frame_duration = VLC_TICK_FROM_MS(40);
374
0
        break;
375
0
    case 2:
376
0
        sub->i_original_picture_width  = 1280;
377
0
        sub->i_original_picture_height =  720;
378
0
        frame_duration = VLC_TICK_FROM_US(16683);
379
0
        break;
380
0
    case 3:
381
0
        sub->i_original_picture_width  = 1920;
382
0
        sub->i_original_picture_height = 1080;
383
0
        frame_duration = VLC_TICK_FROM_US(16683);
384
0
        break;
385
0
    default:
386
0
        msg_Warn(dec, "Unknown display standard");
387
0
        sub->i_original_picture_width  = 0;
388
0
        sub->i_original_picture_height = 0;
389
0
        frame_duration = VLC_TICK_FROM_MS(40);
390
0
        break;
391
0
    }
392
0
    region->b_absolute = true; region->b_in_window = false;
393
0
    if (!pre_clear_display)
394
0
        msg_Warn(dec, "SCTE-27 subtitles without pre_clear_display flag are not well supported");
395
0
    sub->b_ephemer = true;
396
0
    sub->i_start = date;
397
0
    sub->i_stop = date + display_duration * frame_duration;
398
0
    vlc_spu_regions_push(&sub->regions, region);
399
400
0
    return sub;
401
402
0
error:
403
0
    msg_Err(dec, "corrupted subtitle_message");
404
0
    return NULL;
405
0
}
406
407
static int Decode(decoder_t *dec, block_t *b)
408
0
{
409
0
    decoder_sys_t *sys = dec->p_sys;
410
411
0
    if (b == NULL ) /* No Drain */
412
0
        return VLCDEC_SUCCESS;
413
414
0
    if (b->i_flags & (BLOCK_FLAG_CORRUPTED))
415
0
        goto exit;
416
417
0
    while (b->i_buffer > 3) {
418
0
        const int table_id =  b->p_buffer[0];
419
0
        if (table_id != 0xc6) {
420
            //if (table_id != 0xff)
421
            //    msg_Err(dec, "Invalid SCTE-27 table id (0x%x)", table_id);
422
0
            break;
423
0
        }
424
0
        const int section_length = ((b->p_buffer[1] & 0xf) << 8) | b->p_buffer[2];
425
0
        if (section_length <= 1 + 4 || b->i_buffer < 3 + (unsigned)section_length) {
426
0
            msg_Err(dec, "Invalid SCTE-27 section length");
427
0
            break;
428
0
        }
429
0
        const int protocol_version = b->p_buffer[3] & 0x3f;
430
0
        if (protocol_version != 0) {
431
0
            msg_Err(dec, "Unsupported SCTE-27 protocol version (%d)", protocol_version);
432
0
            break;
433
0
        }
434
0
        const bool segmentation_overlay = b->p_buffer[3] & 0x40;
435
436
0
        subpicture_t *sub = NULL;
437
0
        if (segmentation_overlay) {
438
0
            if (section_length < 1 + 5 + 4)
439
0
                break;
440
0
            int id = GetWBE(&b->p_buffer[4]);
441
0
            int last = (b->p_buffer[6] << 4) | (b->p_buffer[7] >> 4);
442
0
            int index = ((b->p_buffer[7] & 0x0f) << 8) | b->p_buffer[8];
443
0
            if (index > last)
444
0
                break;
445
0
            if (index == 0) {
446
0
                sys->segment_id = id;
447
0
                sys->segment_size = 0;
448
0
                sys->segment_date = b->i_pts != VLC_TICK_INVALID ? b->i_pts : b->i_dts;
449
0
            } else {
450
0
                if (sys->segment_id != id || sys->segment_size <= 0) {
451
0
                    sys->segment_id = -1;
452
0
                    break;
453
0
                }
454
0
            }
455
456
0
            int segment_size = section_length - 1 - 5 - 4;
457
458
0
            sys->segment_buffer = xrealloc(sys->segment_buffer,
459
0
                                           sys->segment_size + segment_size);
460
0
            memcpy(&sys->segment_buffer[sys->segment_size],
461
0
                   &b->p_buffer[9], segment_size);
462
0
            sys->segment_size += segment_size;
463
464
0
            if (index == last) {
465
0
                sub = DecodeSubtitleMessage(dec,
466
0
                                            sys->segment_buffer,
467
0
                                            sys->segment_size,
468
0
                                            sys->segment_date);
469
0
                sys->segment_size = 0;
470
0
            }
471
0
        } else {
472
0
            sub = DecodeSubtitleMessage(dec,
473
0
                                        &b->p_buffer[4],
474
0
                                        section_length - 1 - 4,
475
0
                                        b->i_pts != VLC_TICK_INVALID ? b->i_pts : b->i_dts);
476
0
        }
477
0
        if (sub != NULL)
478
0
            decoder_QueueSub(dec, sub);
479
480
0
        b->i_buffer -= 3 + section_length;
481
0
        b->p_buffer += 3 + section_length;
482
0
        break;
483
0
    }
484
485
0
exit:
486
0
    block_Release(b);
487
0
    return VLCDEC_SUCCESS;
488
0
}
489
490
static int Open(vlc_object_t *object)
491
11.2k
{
492
11.2k
    decoder_t *dec = (decoder_t *)object;
493
494
11.2k
    if (dec->fmt_in->i_codec != VLC_CODEC_SCTE_27)
495
11.2k
        return VLC_EGENERIC;
496
497
0
    decoder_sys_t *sys = dec->p_sys = malloc(sizeof(*sys));
498
0
    if (!sys)
499
0
        return VLC_ENOMEM;
500
0
    sys->segment_id = -1;
501
0
    sys->segment_size = 0;
502
0
    sys->segment_buffer = NULL;
503
504
0
    dec->pf_decode = Decode;
505
0
    dec->fmt_out.i_codec = VLC_CODEC_YUVP;
506
507
0
    return VLC_SUCCESS;
508
0
}
509
510
static void Close(vlc_object_t *object)
511
0
{
512
0
    decoder_t *dec = (decoder_t *)object;
513
0
    decoder_sys_t *sys = dec->p_sys;
514
515
0
    free(sys->segment_buffer);
516
0
    free(sys);
517
0
}