Coverage Report

Created: 2026-07-25 07:52

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 "libavcodec/bsf.h"
23
#include "libavcodec/bsf_internal.h"
24
#include "libavcodec/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
247
{
76
247
    EIA608ToSMPTE436MContext *priv = ctx->priv_data;
77
78
247
    priv->wrapping_type = priv->wrapping_type_opt;
79
247
    priv->sample_coding = priv->sample_coding_opt;
80
81
    // validate we can handle the selected wrapping type and sample coding
82
83
247
    AVSmpte436mCodedAnc coded_anc;
84
85
247
    int ret = av_smpte_291m_anc_8bit_encode(
86
247
        &coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx);
87
247
    if (ret < 0)
88
0
        return ret;
89
90
247
    ctx->par_out->codec_type = AVMEDIA_TYPE_DATA;
91
247
    ctx->par_out->codec_id   = AV_CODEC_ID_SMPTE_436M_ANC;
92
93
247
    static const struct {
94
247
        AVRational frame_rate;
95
247
        uint8_t    cdp_frame_rate;
96
247
    } known_frame_rates[] = {
97
247
        { .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F },
98
247
        { .frame_rate = { .num = 24, .den = 1 },       .cdp_frame_rate = 0x2F },
99
247
        { .frame_rate = { .num = 25, .den = 1 },       .cdp_frame_rate = 0x3F },
100
247
        { .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F },
101
247
        { .frame_rate = { .num = 30, .den = 1 },       .cdp_frame_rate = 0x5F },
102
247
        { .frame_rate = { .num = 50, .den = 1 },       .cdp_frame_rate = 0x6F },
103
247
        { .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F },
104
247
        { .frame_rate = { .num = 60, .den = 1 },       .cdp_frame_rate = 0x8F },
105
247
    };
106
107
247
    priv->cdp_frame_rate_byte = 0;
108
109
988
    for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) {
110
988
        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
247
            priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate;
112
247
            break;
113
247
        }
114
988
    }
115
116
247
    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
247
    return 0;
126
247
}
127
128
static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out)
129
13.8k
{
130
13.8k
    EIA608ToSMPTE436MContext *priv = ctx->priv_data;
131
13.8k
    AVPacket                 *in;
132
133
13.8k
    int ret = ff_bsf_get_packet(ctx, &in);
134
13.8k
    if (ret < 0)
135
7.02k
        return ret;
136
137
6.78k
    AVSmpte291mAnc8bit anc;
138
6.78k
    anc.did         = 0x61;
139
6.78k
    anc.sdid_or_dbn = 0x1;
140
141
6.78k
    uint8_t *p = anc.payload;
142
143
6.78k
    *p++ = 0x96; // cdp_identifier -- always 0x9669
144
6.78k
    *p++ = 0x69;
145
146
6.78k
    uint8_t *cdp_length_p = p++;
147
148
6.78k
    *p++ = priv->cdp_frame_rate_byte;
149
150
6.78k
    const uint8_t FLAG_CC_DATA_PRESENT        = 0x40;
151
6.78k
    const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2;
152
6.78k
    const uint8_t FLAG_RESERVED               = 0x1; // must always be set
153
154
6.78k
    *p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED;
155
156
6.78k
    AV_WB16(p, priv->cdp_sequence_cntr);
157
6.78k
    p += 2;
158
159
6.78k
    const uint8_t CC_DATA_SECTION_ID = 0x72;
160
161
6.78k
    *p++ = CC_DATA_SECTION_ID;
162
163
6.78k
    uint8_t *cc_count_p = p++;
164
165
6.78k
    const uint8_t CC_COUNT_MASK   = 0x1F;
166
6.78k
    const int     CDP_FOOTER_SIZE = 4;
167
168
6.78k
    int cc_count           = in->size / 3;
169
6.78k
    int space_left         = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload);
170
6.78k
    int cc_data_space_left = space_left - CDP_FOOTER_SIZE;
171
6.78k
    int max_cc_count       = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK);
172
173
6.78k
    if (cc_count > max_cc_count) {
174
794
        av_log(ctx,
175
794
               AV_LOG_ERROR,
176
794
               "cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n",
177
794
               cc_count,
178
794
               max_cc_count);
179
794
        cc_count = max_cc_count;
180
794
    }
181
182
6.78k
    *cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones
183
184
97.5k
    for (size_t i = 0; i < cc_count; i++) {
185
90.7k
        size_t start = i * 3;
186
90.7k
        *p++         = in->data[start] | 0xF8; // fill reserved bits with ones
187
90.7k
        *p++         = in->data[start + 1];
188
90.7k
        *p++         = in->data[start + 2];
189
90.7k
    }
190
191
6.78k
    const uint8_t CDP_FOOTER_ID = 0x74;
192
193
6.78k
    *p++ = CDP_FOOTER_ID;
194
195
6.78k
    AV_WB16(p, priv->cdp_sequence_cntr);
196
6.78k
    p += 2;
197
198
6.78k
    uint8_t *packet_checksum_p = p;
199
6.78k
    *p++                       = 0;
200
201
6.78k
    anc.data_count = p - anc.payload;
202
6.78k
    *cdp_length_p  = anc.data_count;
203
204
6.78k
    int sum = 0;
205
367k
    for (int i = 0; i < anc.data_count; i++) {
206
360k
        sum += anc.payload[i];
207
360k
    }
208
    // set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0
209
6.78k
    *packet_checksum_p = -sum;
210
211
6.78k
    priv->cdp_sequence_cntr++;
212
    // cdp_sequence_cntr wraps around at 16-bits
213
6.78k
    priv->cdp_sequence_cntr &= 0xFFFFU;
214
215
6.78k
    av_smpte_291m_anc_8bit_fill_checksum(&anc);
216
217
6.78k
    AVSmpte436mCodedAnc coded_anc;
218
6.78k
    ret = av_smpte_291m_anc_8bit_encode(
219
6.78k
        &coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx);
220
6.78k
    if (ret < 0)
221
0
        goto fail;
222
223
6.78k
    ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc);
224
6.78k
    if (ret < 0)
225
0
        goto fail;
226
227
6.78k
    ret = av_new_packet(out, ret);
228
6.78k
    if (ret < 0)
229
0
        goto fail;
230
231
6.78k
    ret = av_packet_copy_props(out, in);
232
6.78k
    if (ret < 0)
233
0
        goto fail;
234
235
6.78k
    ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc);
236
6.78k
    if (ret < 0)
237
0
        goto fail;
238
239
6.78k
    ret = 0;
240
241
6.78k
fail:
242
6.78k
    if (ret < 0)
243
0
        av_packet_unref(out);
244
6.78k
    av_packet_free(&in);
245
6.78k
    return ret;
246
6.78k
}
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
};