Coverage Report

Created: 2026-02-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavformat/mccdec.c
Line
Count
Source
1
/*
2
 * MCC subtitle demuxer
3
 * Copyright (c) 2020 Paul B Mahol
4
 * Copyright (c) 2025 Jacob Lifshay
5
 *
6
 * This file is part of FFmpeg.
7
 *
8
 * FFmpeg is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * FFmpeg 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 GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with FFmpeg; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 */
22
23
#include "avformat.h"
24
#include "demux.h"
25
#include "internal.h"
26
#include "libavcodec/bytestream.h"
27
#include "libavcodec/codec_id.h"
28
#include "libavcodec/smpte_436m.h"
29
#include "libavutil/avstring.h"
30
#include "libavutil/avutil.h"
31
#include "libavutil/error.h"
32
#include "libavutil/internal.h"
33
#include "libavutil/log.h"
34
#include "libavutil/macros.h"
35
#include "libavutil/opt.h"
36
#include "libavutil/rational.h"
37
#include "libavutil/timecode.h"
38
#include "subtitles.h"
39
#include <inttypes.h>
40
#include <stdbool.h>
41
#include <string.h>
42
43
typedef struct MCCContext {
44
    const AVClass *class;
45
    int                   eia608_extract;
46
    FFDemuxSubtitlesQueue q;
47
} MCCContext;
48
49
static int mcc_probe(const AVProbeData *p)
50
936k
{
51
936k
    char         buf[28];
52
936k
    FFTextReader tr;
53
54
936k
    ff_text_init_buf(&tr, p->buf, p->buf_size);
55
56
2.89M
    while (ff_text_peek_r8(&tr) == '\r' || ff_text_peek_r8(&tr) == '\n')
57
1.96M
        ff_text_r8(&tr);
58
59
936k
    ff_text_read(&tr, buf, sizeof(buf));
60
61
936k
    if (!memcmp(buf, "File Format=MacCaption_MCC V", 28))
62
335
        return AVPROBE_SCORE_MAX;
63
64
936k
    return 0;
65
936k
}
66
67
static int convert(uint8_t x)
68
13.9M
{
69
13.9M
    if (x >= 'a')
70
7.55M
        x -= 87;
71
6.44M
    else if (x >= 'A')
72
2.49M
        x -= 55;
73
3.94M
    else
74
3.94M
        x -= '0';
75
13.9M
    return x;
76
13.9M
}
77
78
typedef struct alias {
79
    uint8_t     key;
80
    int         len;
81
    const char *value;
82
} alias;
83
84
#define CCPAD "\xFA\x0\x0"
85
#define CCPAD3 CCPAD CCPAD CCPAD
86
87
static const char cc_pad[27] = CCPAD3 CCPAD3 CCPAD3;
88
89
static const alias aliases[20] = {
90
    // clang-format off
91
    { .key = 16, .len =  3, .value = cc_pad, },
92
    { .key = 17, .len =  6, .value = cc_pad, },
93
    { .key = 18, .len =  9, .value = cc_pad, },
94
    { .key = 19, .len = 12, .value = cc_pad, },
95
    { .key = 20, .len = 15, .value = cc_pad, },
96
    { .key = 21, .len = 18, .value = cc_pad, },
97
    { .key = 22, .len = 21, .value = cc_pad, },
98
    { .key = 23, .len = 24, .value = cc_pad, },
99
    { .key = 24, .len = 27, .value = cc_pad, },
100
    { .key = 25, .len =  3, .value = "\xFB\x80\x80", },
101
    { .key = 26, .len =  3, .value = "\xFC\x80\x80", },
102
    { .key = 27, .len =  3, .value = "\xFD\x80\x80", },
103
    { .key = 28, .len =  2, .value = "\x96\x69", },
104
    { .key = 29, .len =  2, .value = "\x61\x01", },
105
    { .key = 30, .len =  3, .value = "\xFC\x80\x80", },
106
    { .key = 31, .len =  3, .value = "\xFC\x80\x80", },
107
    { .key = 32, .len =  4, .value = "\xE1\x00\x00\x00", },
108
    { .key = 33, .len =  0, .value = NULL, },
109
    { .key = 34, .len =  0, .value = NULL, },
110
    { .key = 35, .len =  1, .value = "\x0", },
111
    // clang-format on
112
};
113
114
typedef struct TimeTracker {
115
    int64_t    last_ts;
116
    int64_t    twenty_four_hr;
117
    AVTimecode timecode;
118
} TimeTracker;
119
120
static int time_tracker_init(TimeTracker *tt, AVStream *st, AVRational rate, void *log_ctx)
121
7.52k
{
122
7.52k
    *tt     = (TimeTracker){ .last_ts = 0 };
123
7.52k
    int ret = av_timecode_init(&tt->timecode, rate, rate.den == 1001 ? AV_TIMECODE_FLAG_DROPFRAME : 0, 0, log_ctx);
124
7.52k
    if (ret < 0)
125
0
        return ret;
126
    // wrap pts values at 24hr ourselves since they can be bigger than fits in an int
127
7.52k
    AVTimecode twenty_four_hr;
128
7.52k
    ret = av_timecode_init_from_components(&twenty_four_hr, rate, tt->timecode.flags, 24, 0, 0, 0, log_ctx);
129
7.52k
    if (ret < 0)
130
0
        return ret;
131
7.52k
    tt->twenty_four_hr = twenty_four_hr.start;
132
    // timecode uses reciprocal of timebase
133
7.52k
    avpriv_set_pts_info(st, 64, rate.den, rate.num);
134
7.52k
    return 0;
135
7.52k
}
136
137
typedef struct MCCTimecode {
138
    unsigned hh, mm, ss, ff, field, line_number;
139
} MCCTimecode;
140
141
static int time_tracker_set_time(TimeTracker *tt, const MCCTimecode *tc, void *log_ctx)
142
1.18M
{
143
1.18M
    AVTimecode last = tt->timecode;
144
1.18M
    int        ret  = av_timecode_init_from_components(&tt->timecode, last.rate, last.flags, tc->hh, tc->mm, tc->ss, tc->ff, log_ctx);
145
1.18M
    if (ret < 0) {
146
0
        tt->timecode = last;
147
0
        return ret;
148
0
    }
149
1.18M
    tt->last_ts -= last.start;
150
1.18M
    tt->last_ts += tt->timecode.start;
151
1.18M
    if (tt->timecode.start < last.start)
152
558k
        tt->last_ts += tt->twenty_four_hr;
153
1.18M
    return 0;
154
1.18M
}
155
156
struct ValidTimeCodeRate {
157
    AVRational  rate;
158
    char        str[5];
159
};
160
161
static const struct ValidTimeCodeRate valid_time_code_rates[] = {
162
    { .rate = { .num = 24, .den = 1 },       .str = "24"   },
163
    { .rate = { .num = 25, .den = 1 },       .str = "25"   },
164
    { .rate = { .num = 30000, .den = 1001 }, .str = "30DF" },
165
    { .rate = { .num = 30, .den = 1 },       .str = "30"   },
166
    { .rate = { .num = 50, .den = 1 },       .str = "50"   },
167
    { .rate = { .num = 60000, .den = 1001 }, .str = "60DF" },
168
    { .rate = { .num = 60, .den = 1 },       .str = "60"   },
169
};
170
171
static int parse_time_code_rate(AVFormatContext *s, AVStream *st, TimeTracker *tt, const char *time_code_rate)
172
5.14k
{
173
28.1k
    for (size_t i = 0; i < FF_ARRAY_ELEMS(valid_time_code_rates); i++) {
174
28.0k
        const char *after;
175
28.0k
        if (av_stristart(time_code_rate, valid_time_code_rates[i].str, &after) != 0) {
176
5.10k
            bool bad_after = false;
177
7.73k
            for (; *after; after++) {
178
2.66k
                if (!av_isspace(*after)) {
179
36
                    bad_after = true;
180
36
                    break;
181
36
                }
182
2.66k
            }
183
5.10k
            if (bad_after)
184
36
                continue;
185
5.07k
            return time_tracker_init(tt, st, valid_time_code_rates[i].rate, s);
186
5.10k
        }
187
28.0k
    }
188
73
    av_log(s, AV_LOG_FATAL, "invalid mcc time code rate: %s", time_code_rate);
189
73
    return AVERROR_INVALIDDATA;
190
5.14k
}
191
192
static int mcc_parse_time_code_part(char **line_left, unsigned *value, unsigned max, const char *after_set)
193
6.32M
{
194
6.32M
    *value = 0;
195
6.32M
    if (!av_isdigit(**line_left))
196
915k
        return AVERROR_INVALIDDATA;
197
11.3M
    while (av_isdigit(**line_left)) {
198
6.01M
        unsigned digit = **line_left - '0';
199
6.01M
        *value         = *value * 10 + digit;
200
6.01M
        ++*line_left;
201
6.01M
        if (*value > max)
202
101k
            return AVERROR_INVALIDDATA;
203
6.01M
    }
204
5.30M
    unsigned char after = **line_left;
205
5.30M
    if (!after || !strchr(after_set, after))
206
141k
        return AVERROR_INVALIDDATA;
207
5.16M
    ++*line_left;
208
5.16M
    return after;
209
5.30M
}
210
211
static int mcc_parse_time_code(char **line_left, MCCTimecode *tc)
212
2.34M
{
213
2.34M
    *tc     = (MCCTimecode){ .field = 0, .line_number = 9 };
214
2.34M
    int ret = mcc_parse_time_code_part(line_left, &tc->hh, 23, ":");
215
2.34M
    if (ret < 0)
216
932k
        return ret;
217
1.40M
    ret = mcc_parse_time_code_part(line_left, &tc->mm, 59, ":");
218
1.40M
    if (ret < 0)
219
114k
        return ret;
220
1.29M
    ret = mcc_parse_time_code_part(line_left, &tc->ss, 59, ":;");
221
1.29M
    if (ret < 0)
222
41.5k
        return ret;
223
1.25M
    ret = mcc_parse_time_code_part(line_left, &tc->ff, 59, ".\t");
224
1.25M
    if (ret < 0)
225
61.0k
        return ret;
226
1.19M
    if (ret == '.') {
227
18.0k
        ret = mcc_parse_time_code_part(line_left, &tc->field, 1, ",\t");
228
18.0k
        if (ret < 0)
229
4.10k
            return ret;
230
13.9k
        if (ret == ',') {
231
5.08k
            ret = mcc_parse_time_code_part(line_left, &tc->line_number, 0xFFFF, "\t");
232
5.08k
            if (ret < 0)
233
4.85k
                return ret;
234
5.08k
        }
235
13.9k
    }
236
1.18M
    if (ret != '\t')
237
0
        return AVERROR_INVALIDDATA;
238
1.18M
    return 0;
239
1.18M
}
240
241
static int mcc_read_header(AVFormatContext *s)
242
2.45k
{
243
2.45k
    MCCContext         *mcc = s->priv_data;
244
2.45k
    AVStream           *st  = avformat_new_stream(s, NULL);
245
2.45k
    int64_t             pos;
246
2.45k
    AVSmpte436mCodedAnc coded_anc = {
247
2.45k
        .payload_sample_coding = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA,
248
2.45k
    };
249
2.45k
    char         line[4096];
250
2.45k
    FFTextReader tr;
251
2.45k
    int          ret = 0;
252
253
2.45k
    ff_text_init_avio(s, &tr, s->pb);
254
255
2.45k
    if (!st)
256
0
        return AVERROR(ENOMEM);
257
2.45k
    if (mcc->eia608_extract) {
258
2.45k
        st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
259
2.45k
        st->codecpar->codec_id   = AV_CODEC_ID_EIA_608;
260
2.45k
    } else {
261
0
        st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
262
0
        st->codecpar->codec_id   = AV_CODEC_ID_SMPTE_436M_ANC;
263
0
        av_dict_set(&st->metadata, "data_type", "vbi_vanc_smpte_436M", 0);
264
0
    }
265
266
2.45k
    TimeTracker tt;
267
2.45k
    ret = time_tracker_init(&tt, st, (AVRational){ .num = 30, .den = 1 }, s);
268
2.45k
    if (ret < 0)
269
0
        return ret;
270
271
6.58M
    while (!ff_text_eof(&tr)) {
272
6.58M
        pos = ff_text_pos(&tr);
273
6.58M
        ff_subtitles_read_line(&tr, line, sizeof(line));
274
6.58M
        if (!strncmp(line, "File Format=MacCaption_MCC V", 28))
275
1.33k
            continue;
276
6.58M
        if (!strncmp(line, "//", 2))
277
346
            continue;
278
6.58M
        if (!strncmp(line, "Time Code Rate=", 15)) {
279
5.14k
            ret = parse_time_code_rate(s, st, &tt, line + 15);
280
5.14k
            if (ret < 0)
281
73
                return ret;
282
5.07k
            continue;
283
5.14k
        }
284
6.57M
        if (strchr(line, '='))
285
146k
            continue; // skip attributes
286
6.43M
        char *line_left = line;
287
6.50M
        while (av_isspace(*line_left))
288
75.0k
            line_left++;
289
6.43M
        if (!*line_left) // skip empty lines
290
4.08M
            continue;
291
2.34M
        MCCTimecode tc;
292
2.34M
        ret = mcc_parse_time_code(&line_left, &tc);
293
2.34M
        if (ret < 0) {
294
1.15M
            av_log(s, AV_LOG_ERROR, "can't parse mcc time code");
295
1.15M
            continue;
296
1.15M
        }
297
298
1.18M
        int64_t last_pts = tt.last_ts;
299
1.18M
        ret              = time_tracker_set_time(&tt, &tc, s);
300
1.18M
        if (ret < 0)
301
0
            continue;
302
1.18M
        bool merge = last_pts == tt.last_ts;
303
304
1.18M
        coded_anc.line_number   = tc.line_number;
305
1.18M
        coded_anc.wrapping_type = tc.field ? AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2 : AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME;
306
307
1.18M
        PutByteContext pb;
308
1.18M
        bytestream2_init_writer(&pb, coded_anc.payload, AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY);
309
310
10.5M
        while (*line_left) {
311
9.48M
            uint8_t v = convert(*line_left++);
312
313
9.48M
            if (v >= 16 && v <= 35) {
314
4.81M
                int idx = v - 16;
315
4.81M
                bytestream2_put_buffer(&pb, aliases[idx].value, aliases[idx].len);
316
4.81M
            } else {
317
4.66M
                uint8_t vv;
318
319
4.66M
                if (!*line_left)
320
147k
                    break;
321
4.51M
                vv = convert(*line_left++);
322
4.51M
                bytestream2_put_byte(&pb, vv | (v << 4));
323
4.51M
            }
324
9.48M
        }
325
1.18M
        if (pb.eof)
326
996
            continue;
327
        // remove trailing ANC checksum byte (not to be confused with the CDP checksum byte),
328
        // it's not included in 8-bit sample encodings. see section 6.2 (page 14) of:
329
        // https://pub.smpte.org/latest/st436/s436m-2006.pdf
330
1.18M
        bytestream2_seek_p(&pb, -1, SEEK_CUR);
331
1.18M
        coded_anc.payload_sample_count = bytestream2_tell_p(&pb);
332
1.18M
        if (coded_anc.payload_sample_count == 0)
333
50.6k
            continue; // ignore if too small
334
        // add padding to align to 4 bytes
335
3.11M
        while (!pb.eof && bytestream2_tell_p(&pb) % 4)
336
1.98M
            bytestream2_put_byte(&pb, 0);
337
1.13M
        if (pb.eof)
338
0
            continue;
339
1.13M
        coded_anc.payload_array_length = bytestream2_tell_p(&pb);
340
341
1.13M
        AVPacket *sub;
342
1.13M
        if (mcc->eia608_extract) {
343
1.13M
            AVSmpte291mAnc8bit anc;
344
1.13M
            if (av_smpte_291m_anc_8bit_decode(
345
1.13M
                    &anc, coded_anc.payload_sample_coding, coded_anc.payload_sample_count, coded_anc.payload, s)
346
1.13M
                < 0)
347
514k
                continue;
348
            // reuse line
349
617k
            int cc_count = av_smpte_291m_anc_8bit_extract_cta_708(&anc, line, s);
350
617k
            if (cc_count < 0) // continue if error or if it's not a closed captions packet
351
15.2k
                continue;
352
602k
            int len = cc_count * 3;
353
354
602k
            sub = ff_subtitles_queue_insert(&mcc->q, line, len, merge);
355
602k
            if (!sub)
356
0
                return AVERROR(ENOMEM);
357
602k
        } else {
358
0
            sub = ff_subtitles_queue_insert(&mcc->q, NULL, 0, merge);
359
0
            if (!sub)
360
0
                return AVERROR(ENOMEM);
361
362
0
            ret = av_smpte_436m_anc_append(sub, 1, &coded_anc);
363
0
            if (ret < 0)
364
0
                return ret;
365
0
        }
366
367
602k
        sub->pos      = pos;
368
602k
        sub->pts      = tt.last_ts;
369
602k
        sub->duration = 1;
370
602k
        continue;
371
1.13M
    }
372
373
2.38k
    ff_subtitles_queue_finalize(s, &mcc->q);
374
375
2.38k
    return ret;
376
2.45k
}
377
378
static int mcc_read_packet(AVFormatContext *s, AVPacket *pkt)
379
208k
{
380
208k
    MCCContext *mcc = s->priv_data;
381
208k
    return ff_subtitles_queue_read_packet(&mcc->q, pkt);
382
208k
}
383
384
static int mcc_read_seek(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
385
0
{
386
0
    MCCContext *mcc = s->priv_data;
387
0
    return ff_subtitles_queue_seek(&mcc->q, s, stream_index, min_ts, ts, max_ts, flags);
388
0
}
389
390
static int mcc_read_close(AVFormatContext *s)
391
2.45k
{
392
2.45k
    MCCContext *mcc = s->priv_data;
393
2.45k
    ff_subtitles_queue_clean(&mcc->q);
394
2.45k
    return 0;
395
2.45k
}
396
397
#define OFFSET(x) offsetof(MCCContext, x)
398
#define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
399
// clang-format off
400
static const AVOption mcc_options[] = {
401
    { "eia608_extract", "extract EIA-608/708 captions from VANC packets", OFFSET(eia608_extract), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, SD },
402
    { NULL },
403
};
404
// clang-format on
405
406
static const AVClass mcc_class = {
407
    .class_name = "mcc demuxer",
408
    .item_name  = av_default_item_name,
409
    .option     = mcc_options,
410
    .version    = LIBAVUTIL_VERSION_INT,
411
    .category   = AV_CLASS_CATEGORY_DEMUXER,
412
};
413
414
const FFInputFormat ff_mcc_demuxer = {
415
    .p.name         = "mcc",
416
    .p.long_name    = NULL_IF_CONFIG_SMALL("MacCaption"),
417
    .p.extensions   = "mcc",
418
    .p.priv_class   = &mcc_class,
419
    .priv_data_size = sizeof(MCCContext),
420
    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
421
    .read_probe     = mcc_probe,
422
    .read_header    = mcc_read_header,
423
    .read_packet    = mcc_read_packet,
424
    .read_seek2     = mcc_read_seek,
425
    .read_close     = mcc_read_close,
426
};