Coverage Report

Created: 2026-04-01 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavcodec/bsf/eia608_to_smpte436m.c
Line
Count
Source
1
/*
2
 * EIA-608 to MXF SMPTE-436M ANC bitstream filter
3
 * Copyright (c) 2025 Jacob Lifshay
4
 *
5
 * This file is part of FFmpeg.
6
 *
7
 * FFmpeg is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with FFmpeg; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
 */
21
22
#include "bsf.h"
23
#include "bsf_internal.h"
24
#include "codec_id.h"
25
#include "libavcodec/smpte_436m.h"
26
#include "libavcodec/smpte_436m_internal.h"
27
#include "libavutil/avassert.h"
28
#include "libavutil/avutil.h"
29
#include "libavutil/error.h"
30
#include "libavutil/intreadwrite.h"
31
#include "libavutil/macros.h"
32
#include "libavutil/opt.h"
33
#include "libavutil/rational.h"
34
35
typedef struct EIA608ToSMPTE436MContext {
36
    const AVClass *class;
37
    unsigned                       line_number;
38
    unsigned                       cdp_sequence_cntr;
39
    unsigned                       wrapping_type_opt;
40
    unsigned                       sample_coding_opt;
41
    AVSmpte436mWrappingType        wrapping_type;
42
    AVSmpte436mPayloadSampleCoding sample_coding;
43
    AVRational                     cdp_frame_rate;
44
    uint8_t                        cdp_frame_rate_byte;
45
} EIA608ToSMPTE436MContext;
46
47
// clang-format off
48
static const AVSmpte291mAnc8bit test_anc = {
49
    .did         = 0x61,
50
    .sdid_or_dbn = 0x01,
51
    .data_count  = 0x49,
52
    .payload     = {
53
        // header
54
        0x96, 0x69, 0x49, 0x7F, 0x43, 0xFA, 0x8D, 0x72, 0xF4,
55
56
        // 608 triples
57
        0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80,
58
59
        // 708 padding
60
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
61
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
62
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
63
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
64
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
65
        0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
66
67
        // footer
68
        0x74, 0xFA, 0x8D, 0x81,
69
    },
70
    .checksum = 0xAB,
71
};
72
// clang-format on
73
74
static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx)
75
252
{
76
252
    EIA608ToSMPTE436MContext *priv = ctx->priv_data;
77
78
252
    priv->wrapping_type = priv->wrapping_type_opt;
79
252
    priv->sample_coding = priv->sample_coding_opt;
80
81
    // validate we can handle the selected wrapping type and sample coding
82
83
252
    AVSmpte436mCodedAnc coded_anc;
84
85
252
    int ret = av_smpte_291m_anc_8bit_encode(
86
252
        &coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx);
87
252
    if (ret < 0)
88
0
        return ret;
89
90
252
    ctx->par_out->codec_type = AVMEDIA_TYPE_DATA;
91
252
    ctx->par_out->codec_id   = AV_CODEC_ID_SMPTE_436M_ANC;
92
93
252
    static const struct {
94
252
        AVRational frame_rate;
95
252
        uint8_t    cdp_frame_rate;
96
252
    } known_frame_rates[] = {
97
252
        { .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F },
98
252
        { .frame_rate = { .num = 24, .den = 1 },       .cdp_frame_rate = 0x2F },
99
252
        { .frame_rate = { .num = 25, .den = 1 },       .cdp_frame_rate = 0x3F },
100
252
        { .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F },
101
252
        { .frame_rate = { .num = 30, .den = 1 },       .cdp_frame_rate = 0x5F },
102
252
        { .frame_rate = { .num = 50, .den = 1 },       .cdp_frame_rate = 0x6F },
103
252
        { .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F },
104
252
        { .frame_rate = { .num = 60, .den = 1 },       .cdp_frame_rate = 0x8F },
105
252
    };
106
107
252
    priv->cdp_frame_rate_byte = 0;
108
109
1.00k
    for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) {
110
1.00k
        if (known_frame_rates[i].frame_rate.num == priv->cdp_frame_rate.num && known_frame_rates[i].frame_rate.den == priv->cdp_frame_rate.den) {
111
252
            priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate;
112
252
            break;
113
252
        }
114
1.00k
    }
115
116
252
    if (priv->cdp_frame_rate_byte == 0) {
117
0
        av_log(ctx,
118
0
               AV_LOG_FATAL,
119
0
               "cdp_frame_rate not supported: %d/%d\n",
120
0
               priv->cdp_frame_rate.num,
121
0
               priv->cdp_frame_rate.den);
122
0
        return AVERROR(EINVAL);
123
0
    }
124
125
252
    return 0;
126
252
}
127
128
static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out)
129
19.1k
{
130
19.1k
    EIA608ToSMPTE436MContext *priv = ctx->priv_data;
131
19.1k
    AVPacket                 *in;
132
133
19.1k
    int ret = ff_bsf_get_packet(ctx, &in);
134
19.1k
    if (ret < 0)
135
9.68k
        return ret;
136
137
9.43k
    AVSmpte291mAnc8bit anc;
138
9.43k
    anc.did         = 0x61;
139
9.43k
    anc.sdid_or_dbn = 0x1;
140
141
9.43k
    uint8_t *p = anc.payload;
142
143
9.43k
    *p++ = 0x96; // cdp_identifier -- always 0x9669
144
9.43k
    *p++ = 0x69;
145
146
9.43k
    uint8_t *cdp_length_p = p++;
147
148
9.43k
    *p++ = priv->cdp_frame_rate_byte;
149
150
9.43k
    const uint8_t FLAG_CC_DATA_PRESENT        = 0x40;
151
9.43k
    const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2;
152
9.43k
    const uint8_t FLAG_RESERVED               = 0x1; // must always be set
153
154
9.43k
    *p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED;
155
156
9.43k
    AV_WB16(p, priv->cdp_sequence_cntr);
157
9.43k
    p += 2;
158
159
9.43k
    const uint8_t CC_DATA_SECTION_ID = 0x72;
160
161
9.43k
    *p++ = CC_DATA_SECTION_ID;
162
163
9.43k
    uint8_t *cc_count_p = p++;
164
165
9.43k
    const uint8_t CC_COUNT_MASK   = 0x1F;
166
9.43k
    const int     CDP_FOOTER_SIZE = 4;
167
168
9.43k
    int cc_count           = in->size / 3;
169
9.43k
    int space_left         = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload);
170
9.43k
    int cc_data_space_left = space_left - CDP_FOOTER_SIZE;
171
9.43k
    int max_cc_count       = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK);
172
173
9.43k
    if (cc_count > max_cc_count) {
174
922
        av_log(ctx,
175
922
               AV_LOG_ERROR,
176
922
               "cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n",
177
922
               cc_count,
178
922
               max_cc_count);
179
922
        cc_count = max_cc_count;
180
922
    }
181
182
9.43k
    *cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones
183
184
120k
    for (size_t i = 0; i < cc_count; i++) {
185
111k
        size_t start = i * 3;
186
111k
        *p++         = in->data[start] | 0xF8; // fill reserved bits with ones
187
111k
        *p++         = in->data[start + 1];
188
111k
        *p++         = in->data[start + 2];
189
111k
    }
190
191
9.43k
    const uint8_t CDP_FOOTER_ID = 0x74;
192
193
9.43k
    *p++ = CDP_FOOTER_ID;
194
195
9.43k
    AV_WB16(p, priv->cdp_sequence_cntr);
196
9.43k
    p += 2;
197
198
9.43k
    uint8_t *packet_checksum_p = p;
199
9.43k
    *p++                       = 0;
200
201
9.43k
    anc.data_count = p - anc.payload;
202
9.43k
    *cdp_length_p  = anc.data_count;
203
204
9.43k
    int sum = 0;
205
465k
    for (int i = 0; i < anc.data_count; i++) {
206
455k
        sum += anc.payload[i];
207
455k
    }
208
    // set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0
209
9.43k
    *packet_checksum_p = -sum;
210
211
9.43k
    priv->cdp_sequence_cntr++;
212
    // cdp_sequence_cntr wraps around at 16-bits
213
9.43k
    priv->cdp_sequence_cntr &= 0xFFFFU;
214
215
9.43k
    av_smpte_291m_anc_8bit_fill_checksum(&anc);
216
217
9.43k
    AVSmpte436mCodedAnc coded_anc;
218
9.43k
    ret = av_smpte_291m_anc_8bit_encode(
219
9.43k
        &coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx);
220
9.43k
    if (ret < 0)
221
0
        goto fail;
222
223
9.43k
    ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc);
224
9.43k
    if (ret < 0)
225
0
        goto fail;
226
227
9.43k
    ret = av_new_packet(out, ret);
228
9.43k
    if (ret < 0)
229
0
        goto fail;
230
231
9.43k
    ret = av_packet_copy_props(out, in);
232
9.43k
    if (ret < 0)
233
0
        goto fail;
234
235
9.43k
    ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc);
236
9.43k
    if (ret < 0)
237
0
        goto fail;
238
239
9.43k
    ret = 0;
240
241
9.43k
fail:
242
9.43k
    if (ret < 0)
243
0
        av_packet_unref(out);
244
9.43k
    av_packet_free(&in);
245
9.43k
    return ret;
246
9.43k
}
247
248
#define OFFSET(x) offsetof(EIA608ToSMPTE436MContext, x)
249
#define FLAGS AV_OPT_FLAG_BSF_PARAM
250
// clang-format off
251
static const AVOption options[] = {
252
    { "line_number", "line number -- you probably want 9 or 11", OFFSET(line_number), AV_OPT_TYPE_UINT, { .i64 = 9 }, 0, 0xFFFF, FLAGS },
253
    { "wrapping_type", "wrapping type", OFFSET(wrapping_type_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME }, 0, 0xFF, FLAGS, .unit = "wrapping_type" },
254
    FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(FLAGS, "wrapping_type"),
255
    { "sample_coding", "payload sample coding", OFFSET(sample_coding_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA }, 0, 0xFF, FLAGS, .unit = "sample_coding" },
256
    FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ANC_AVOPTIONS(FLAGS, "sample_coding"),
257
    { "initial_cdp_sequence_cntr", "initial cdp_*_sequence_cntr value", OFFSET(cdp_sequence_cntr), AV_OPT_TYPE_UINT, { .i64 = 0 }, 0, 0xFFFF, FLAGS },
258
    { "cdp_frame_rate", "set the `cdp_frame_rate` fields", OFFSET(cdp_frame_rate), AV_OPT_TYPE_VIDEO_RATE, { .str = "30000/1001" }, 0, INT_MAX, FLAGS },
259
    { NULL },
260
};
261
// clang-format on
262
263
static const AVClass eia608_to_smpte436m_class = {
264
    .class_name = "eia608_to_smpte436m bitstream filter",
265
    .item_name  = av_default_item_name,
266
    .option     = options,
267
    .version    = LIBAVUTIL_VERSION_INT,
268
};
269
270
const FFBitStreamFilter ff_eia608_to_smpte436m_bsf = {
271
    .p.name         = "eia608_to_smpte436m",
272
    .p.codec_ids    = (const enum AVCodecID[]){ AV_CODEC_ID_EIA_608, AV_CODEC_ID_NONE },
273
    .p.priv_class   = &eia608_to_smpte436m_class,
274
    .priv_data_size = sizeof(EIA608ToSMPTE436MContext),
275
    .init           = ff_eia608_to_smpte436m_init,
276
    .filter         = ff_eia608_to_smpte436m_filter,
277
};