FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/dovi_split.c
Date: 2026-06-16 12:54:33
Exec Total Coverage
Lines: 111 127 87.4%
Functions: 5 5 100.0%
Branches: 69 99 69.7%

Line Branch Exec Source
1 /*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <stdbool.h>
20
21 #include "libavutil/avassert.h"
22 #include "libavutil/dovi_meta.h"
23 #include "libavutil/intreadwrite.h"
24 #include "libavutil/mem.h"
25 #include "libavutil/opt.h"
26
27 #include "libavcodec/bsf.h"
28 #include "libavcodec/bsf_internal.h"
29 #include "libavcodec/h2645_parse.h"
30 #include "libavcodec/packet.h"
31
32 #include "libavcodec/hevc/hevc.h"
33
34 enum DOVISplitMode {
35 DOVI_SPLIT_BL = 0,
36 DOVI_SPLIT_BL_RPU = 1,
37 DOVI_SPLIT_EL = 2,
38 DOVI_SPLIT_EL_RPU = 3,
39 };
40
41 typedef struct DOVISplitContext {
42 const AVClass *class;
43 int mode;
44
45 int nal_length_size; /* 0 means Annex-B input */
46 int out_nal_length_size; /* 0 means Annex-B output */
47 H2645Packet pkt;
48 } DOVISplitContext;
49
50 8 static int hvcc_nal_length_size(const uint8_t *data, int size)
51 {
52
4/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
8 if (size >= 23 && data[0] == 1 && AV_RB24(data) != 1 && AV_RB32(data) != 1)
53 8 return (data[21] & 3) + 1;
54 return 0;
55 }
56
57 4 static int dovi_split_init(AVBSFContext *ctx)
58 {
59 4 DOVISplitContext *s = ctx->priv_data;
60
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
4 bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU;
61
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
4 bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU;
62
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
4 bool keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU;
63
64 /* Profile 7 is currently the only supported variant with EL by Dolby.
65 * Default to that in case there is no DOVI config. */
66 4 uint8_t dv_profile = 7;
67
68
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 for (int i = 0; i < ctx->par_out->nb_coded_side_data; i++) {
69 4 AVPacketSideData *sd = &ctx->par_out->coded_side_data[i];
70 AVDOVIDecoderConfigurationRecord *cfg;
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (sd->type != AV_PKT_DATA_DOVI_CONF)
72 continue;
73 4 cfg = (AVDOVIDecoderConfigurationRecord *)sd->data;
74 4 cfg->bl_present_flag &= keep_bl;
75 4 cfg->el_present_flag &= keep_el;
76 4 cfg->rpu_present_flag &= keep_rpu;
77 4 dv_profile = cfg->dv_profile;
78 4 break;
79 }
80
81
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (keep_el) {
82 const AVPacketSideData *sd;
83 2 sd = av_packet_side_data_get(ctx->par_out->coded_side_data,
84 2 ctx->par_out->nb_coded_side_data,
85 AV_PKT_DATA_HEVC_CONF);
86
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (sd && sd->size >= 23) {
87 2 uint8_t *new_ed = av_mallocz(sd->size + AV_INPUT_BUFFER_PADDING_SIZE);
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!new_ed)
89 return AVERROR(ENOMEM);
90 2 memcpy(new_ed, sd->data, sd->size);
91 2 av_freep(&ctx->par_out->extradata);
92 2 ctx->par_out->extradata = new_ed;
93 2 ctx->par_out->extradata_size = sd->size;
94 }
95
96 /* DV profile to EL size ratio */
97 static const uint8_t el_div[] = {
98 [2] = 2,
99 [3] = 1,
100 [4] = 2,
101 [6] = 2,
102 [7] = 2,
103 };
104
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int div = dv_profile < FF_ARRAY_ELEMS(el_div) ? el_div[dv_profile] : 0;
105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!div)
106 av_log(ctx, AV_LOG_WARNING, "Unexpected DV Profile %d.\n", dv_profile);
107
108 /* P7: EL is 1:1 for FHD BL */
109
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (dv_profile == 7 && ctx->par_in->width <= 1920)
110 div = 1;
111
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (div > 1) {
112 2 ctx->par_out->width = ctx->par_in->width / div;
113 2 ctx->par_out->height = ctx->par_in->height / div;
114 }
115 }
116
117 /* Drop AV_PKT_DATA_HEVC_CONF as it's no longer valid on output. It's
118 * set as extradata for EL. */
119 4 av_packet_side_data_remove(ctx->par_out->coded_side_data,
120 4 &ctx->par_out->nb_coded_side_data,
121 AV_PKT_DATA_HEVC_CONF);
122
123 8 s->nal_length_size = hvcc_nal_length_size(ctx->par_in->extradata,
124 4 ctx->par_in->extradata_size);
125 8 s->out_nal_length_size = hvcc_nal_length_size(ctx->par_out->extradata,
126 4 ctx->par_out->extradata_size);
127
128 4 return 0;
129 }
130
131 4 static void dovi_split_close(AVBSFContext *ctx)
132 {
133 4 DOVISplitContext *s = ctx->priv_data;
134 4 ff_h2645_packet_uninit(&s->pkt);
135 4 }
136
137 176 static int nal_is_kept(const DOVISplitContext *s, const H2645NAL *nal,
138 const uint8_t **payload, int *payload_size)
139 {
140
4/4
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
176 bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU;
141
4/4
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
176 bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU;
142
4/4
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 88 times.
176 bool keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU;
143
144
3/3
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 88 times.
176 switch (nal->type) {
145 80 case HEVC_NAL_UNSPEC63:
146 /* EL: keep only when extracting EL, strip two-bytes of outer NAL header */
147
3/4
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
80 if (!keep_el || nal->raw_size <= 2)
148 40 return 0;
149 40 *payload = nal->raw_data + 2;
150 40 *payload_size = nal->raw_size - 2;
151 40 return 1;
152 8 case HEVC_NAL_UNSPEC62:
153 /* RPU: kept verbatim only when the selected mode opted in. */
154
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (!keep_rpu)
155 4 return 0;
156 4 *payload = nal->raw_data;
157 4 *payload_size = nal->raw_size;
158 4 return 1;
159 88 default:
160 /* Anything else is a base-layer NAL. */
161
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 44 times.
88 if (!keep_bl)
162 44 return 0;
163 44 *payload = nal->raw_data;
164 44 *payload_size = nal->raw_size;
165 44 return 1;
166 }
167 }
168
169 12 static int dovi_split_filter(AVBSFContext *ctx, AVPacket *out)
170 {
171 12 DOVISplitContext *s = ctx->priv_data;
172 12 AVPacket *in = NULL;
173 12 AVBufferRef *out_buf = NULL;
174 uint8_t *dst;
175 12 size_t out_size = 0;
176 12 int kept_count = 0;
177
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 int flags = (s->nal_length_size ? H2645_FLAG_IS_NALFF : 0) |
178 H2645_FLAG_SMALL_PADDING;
179
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 int prefix_size = s->out_nal_length_size ? s->out_nal_length_size : 4;
180 int ret;
181
182 12 ret = ff_bsf_get_packet(ctx, &in);
183
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 if (ret < 0)
184 8 return ret;
185
186 4 ret = ff_h2645_packet_split(&s->pkt, in->data, in->size, ctx,
187 s->nal_length_size, AV_CODEC_ID_HEVC, flags);
188
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
189 goto fail;
190
191
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 4 times.
92 for (int i = 0; i < s->pkt.nb_nals; i++) {
192 const uint8_t *payload;
193 int payload_size;
194
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
88 if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size))
195 44 continue;
196 44 out_size += prefix_size + payload_size;
197 44 kept_count++;
198 }
199
200
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!kept_count) {
201 ret = AVERROR(EAGAIN);
202 goto fail;
203 }
204
205 4 out_buf = av_buffer_alloc(out_size + AV_INPUT_BUFFER_PADDING_SIZE);
206
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!out_buf) {
207 ret = AVERROR(ENOMEM);
208 goto fail;
209 }
210
211 4 dst = out_buf->data;
212
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 4 times.
92 for (int i = 0; i < s->pkt.nb_nals; i++) {
213 const uint8_t *payload;
214 int payload_size;
215
2/2
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 44 times.
88 if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size))
216 44 continue;
217
1/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
44 switch (s->out_nal_length_size) {
218 case 0: AV_WB32(dst, 1); break;
219 case 1: AV_WB8 (dst, payload_size); break;
220 case 2: AV_WB16(dst, payload_size); break;
221 case 3: AV_WB24(dst, payload_size); break;
222 44 case 4: AV_WB32(dst, payload_size); break;
223 }
224 44 dst += prefix_size;
225 44 memcpy(dst, payload, payload_size);
226 44 dst += payload_size;
227 }
228 4 memset(dst, 0, AV_INPUT_BUFFER_PADDING_SIZE);
229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 av_assert0(dst == out_buf->data + out_size);
230
231 4 ret = av_packet_copy_props(out, in);
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
233 goto fail;
234
235 4 out->buf = out_buf;
236 4 out->data = out_buf->data;
237 4 out->size = out_size;
238 4 out_buf = NULL;
239
240 4 fail:
241 4 av_buffer_unref(&out_buf);
242 4 av_packet_free(&in);
243
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (ret < 0 && ret != AVERROR(EAGAIN))
244 av_packet_unref(out);
245 4 return ret;
246 }
247
248 #define OFFSET(x) offsetof(DOVISplitContext, x)
249 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM)
250 static const AVOption dovi_split_options[] = {
251 { "mode", "Which Dolby Vision components to keep in the output bitstream", OFFSET(mode), AV_OPT_TYPE_INT, { .i64 = DOVI_SPLIT_BL }, DOVI_SPLIT_BL, DOVI_SPLIT_EL_RPU, FLAGS, .unit = "mode" },
252 { "bl", "Base layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL }, .flags = FLAGS, .unit = "mode" },
253 { "bl_rpu", "Base layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL_RPU }, .flags = FLAGS, .unit = "mode" },
254 { "el", "Enhancement layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL }, .flags = FLAGS, .unit = "mode" },
255 { "el_rpu", "Enhancement layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL_RPU }, .flags = FLAGS, .unit = "mode" },
256 { NULL },
257 };
258
259 static const AVClass dovi_split_class = {
260 .class_name = "dovi_split_bsf",
261 .item_name = av_default_item_name,
262 .option = dovi_split_options,
263 .version = LIBAVUTIL_VERSION_INT,
264 };
265
266 static const enum AVCodecID dovi_split_codec_ids[] = {
267 AV_CODEC_ID_HEVC, AV_CODEC_ID_NONE,
268 };
269
270 const FFBitStreamFilter ff_dovi_split_bsf = {
271 .p.name = "dovi_split",
272 .p.codec_ids = dovi_split_codec_ids,
273 .p.priv_class = &dovi_split_class,
274 .priv_data_size = sizeof(DOVISplitContext),
275 .init = dovi_split_init,
276 .close = dovi_split_close,
277 .filter = dovi_split_filter,
278 };
279