FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/dovi_rpu.c
Date: 2026-09-28 17:50:41
Exec Total Coverage
Lines: 0 129 0.0%
Functions: 0 5 0.0%
Branches: 0 75 0.0%

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 "libavutil/common.h"
20 #include "libavutil/mem.h"
21 #include "libavutil/opt.h"
22
23 #include "libavcodec/bsf.h"
24 #include "libavcodec/bsf_internal.h"
25 #include "libavcodec/cbs.h"
26 #include "libavcodec/cbs_bsf.h"
27 #include "libavcodec/cbs_av1.h"
28 #include "libavcodec/cbs_h265.h"
29 #include "libavcodec/dovi_rpu.h"
30 #include "libavcodec/h2645data.h"
31 #include "libavcodec/h265_profile_level.h"
32 #include "libavcodec/itut35.h"
33
34 #include "libavcodec/hevc/hevc.h"
35
36 typedef struct DoviRpuContext {
37 CBSBSFContext common;
38 DOVIContext dec;
39 DOVIContext enc;
40
41 int strip;
42 int compression;
43 } DoviRpuContext;
44
45 ✗ static int update_rpu(AVBSFContext *bsf, const AVPacket *pkt, int flags,
46 const uint8_t *rpu, size_t rpu_size,
47 uint8_t **out_rpu, int *out_size)
48 {
49 ✗ DoviRpuContext *s = bsf->priv_data;
50 ✗ AVDOVIMetadata *metadata = NULL;
51 int ret;
52
53 ✗ ret = ff_dovi_rpu_parse(&s->dec, rpu, rpu_size, 0);
54 ✗ if (ret < 0) {
55 ✗ ff_dovi_ctx_flush(&s->dec);
56 ✗ return ret;
57 }
58
59 ✗ ret = ff_dovi_get_metadata(&s->dec, &metadata);
60 ✗ if (ret == 0 /* no metadata */) {
61 ✗ *out_rpu = NULL;
62 ✗ *out_size = 0;
63 ✗ return 0;
64 ✗ } else if (ret < 0) {
65 ✗ ff_dovi_ctx_flush(&s->dec);
66 ✗ return ret;
67 }
68
69 ✗ if (pkt && !(pkt->flags & AV_PKT_FLAG_KEY))
70 ✗ flags |= FF_DOVI_COMPRESS_RPU;
71 ✗ ret = ff_dovi_rpu_generate(&s->enc, metadata, flags, out_rpu, out_size);
72 ✗ av_free(metadata);
73 ✗ if (ret < 0)
74 ✗ ff_dovi_ctx_flush(&s->enc);
75
76 ✗ return ret;
77 }
78
79 ✗ static int dovi_rpu_update_fragment_hevc(AVBSFContext *bsf, AVPacket *pkt,
80 CodedBitstreamFragment *au)
81 {
82 ✗ DoviRpuContext *s = bsf->priv_data;
83 ✗ CodedBitstreamUnit *nal = au->nb_units ? &au->units[au->nb_units - 1] : NULL;
84 ✗ uint8_t *rpu = NULL;
85 int rpu_size, ret;
86
87 // HEVC_NAL_UNSPEC62 is Dolby Vision RPU and HEVC_NAL_UNSPEC63 is Dolby Vision EL
88 ✗ if (!nal || (nal->type != HEVC_NAL_UNSPEC62 && nal->type != HEVC_NAL_UNSPEC63))
89 ✗ return 0;
90
91 ✗ if (s->strip) {
92 ✗ ff_cbs_delete_unit(au, au->nb_units - 1);
93 ✗ return 0;
94 }
95
96 ✗ if (nal->type == HEVC_NAL_UNSPEC63)
97 ✗ return 0;
98
99 ✗ ret = update_rpu(bsf, pkt, 0, nal->data + 2, nal->data_size - 2, &rpu, &rpu_size);
100 ✗ if (ret < 0)
101 ✗ return ret;
102 ✗ if (!rpu || rpu_size <= 0)
103 ✗ return 0;
104
105 /* NAL unit header + NAL prefix */
106 ✗ if (rpu_size + 3 <= nal->data_size && av_buffer_is_writable(nal->data_ref)) {
107 ✗ memcpy(nal->data + 3, rpu, rpu_size);
108 ✗ av_free(rpu);
109 ✗ nal->data_size = rpu_size + 3;
110 } else {
111 ✗ AVBufferRef *ref = av_buffer_alloc(rpu_size + 3);
112 ✗ if (!ref) {
113 ✗ av_free(rpu);
114 ✗ return AVERROR(ENOMEM);
115 }
116
117 ✗ memcpy(ref->data, nal->data, 3);
118 ✗ memcpy(ref->data + 3, rpu, rpu_size);
119 ✗ av_buffer_unref(&nal->data_ref);
120 ✗ av_free(rpu);
121 ✗ nal->data = ref->data;
122 ✗ nal->data_size = rpu_size + 3;
123 ✗ nal->data_ref = ref;
124 ✗ nal->data_bit_padding = 0;
125 }
126
127 ✗ return 0;
128 }
129
130 ✗ static int dovi_rpu_update_fragment_av1(AVBSFContext *bsf, AVPacket *pkt,
131 CodedBitstreamFragment *frag)
132 {
133 ✗ DoviRpuContext *s = bsf->priv_data;
134 int provider_code, provider_oriented_code, rpu_size, ret;
135 AVBufferRef *ref;
136 uint8_t *rpu;
137
138 ✗ for (int i = 0; i < frag->nb_units; i++) {
139 ✗ AV1RawOBU *obu = frag->units[i].content;
140 ✗ AV1RawMetadataITUTT35 *t35 = &obu->obu.metadata.metadata.itut_t35;
141 ✗ if (frag->units[i].type != AV1_OBU_METADATA ||
142 ✗ obu->obu.metadata.metadata_type != AV1_METADATA_TYPE_ITUT_T35 ||
143 ✗ t35->itu_t_t35_country_code != ITU_T_T35_COUNTRY_CODE_US ||
144 ✗ t35->payload_size < 6)
145 ✗ continue;
146
147 ✗ provider_code = AV_RB16(t35->payload);
148 ✗ provider_oriented_code = AV_RB32(t35->payload + 2);
149 ✗ if (provider_code != ITU_T_T35_PROVIDER_CODE_DOLBY ||
150 provider_oriented_code != 0x800)
151 ✗ continue;
152
153 ✗ if (s->strip) {
154 ✗ ff_cbs_delete_unit(frag, i);
155 ✗ return 0;
156 }
157
158 ✗ ret = update_rpu(bsf, pkt, FF_DOVI_WRAP_T35,
159 ✗ t35->payload + 6, t35->payload_size - 6,
160 &rpu, &rpu_size);
161 ✗ if (ret < 0)
162 ✗ return ret;
163 ✗ if (!rpu || rpu_size <= 1) {
164 ✗ av_free(rpu);
165 ✗ continue;
166 }
167
168 ✗ ref = av_buffer_create(rpu, rpu_size, av_buffer_default_free, NULL, 0);
169 ✗ if (!ref) {
170 ✗ av_free(rpu);
171 ✗ return AVERROR(ENOMEM);
172 }
173
174 ✗ av_buffer_unref(&t35->payload_ref);
175 ✗ t35->payload_ref = ref;
176 ✗ t35->payload = rpu + 1; /* skip country code */
177 ✗ t35->payload_size = rpu_size - 1;
178 ✗ break; /* should be only one RPU per packet */
179 }
180
181 ✗ return 0;
182 }
183
184 static const CBSBSFType dovi_rpu_hevc_type = {
185 .codec_id = AV_CODEC_ID_HEVC,
186 .fragment_name = "access unit",
187 .unit_name = "NAL unit",
188 .update_fragment = &dovi_rpu_update_fragment_hevc,
189 };
190
191 static const CBSBSFType dovi_rpu_av1_type = {
192 .codec_id = AV_CODEC_ID_AV1,
193 .fragment_name = "temporal unit",
194 .unit_name = "OBU",
195 .update_fragment = &dovi_rpu_update_fragment_av1,
196 };
197
198 ✗ static int dovi_rpu_init(AVBSFContext *bsf)
199 {
200 int ret;
201 ✗ DoviRpuContext *s = bsf->priv_data;
202 ✗ s->dec.logctx = s->enc.logctx = bsf;
203 ✗ s->enc.enable = 1;
204
205 ✗ if (s->compression == AV_DOVI_COMPRESSION_RESERVED) {
206 ✗ av_log(bsf, AV_LOG_ERROR, "Invalid compression level: %d\n", s->compression);
207 ✗ return AVERROR(EINVAL);
208 }
209
210 ✗ if (s->strip) {
211 ✗ av_packet_side_data_remove(bsf->par_out->coded_side_data,
212 ✗ &bsf->par_out->nb_coded_side_data,
213 AV_PKT_DATA_DOVI_CONF);
214 } else {
215 const AVPacketSideData *sd;
216 ✗ sd = av_packet_side_data_get(bsf->par_out->coded_side_data,
217 ✗ bsf->par_out->nb_coded_side_data,
218 AV_PKT_DATA_DOVI_CONF);
219
220 ✗ if (sd) {
221 AVDOVIDecoderConfigurationRecord *cfg;
222 ✗ cfg = (AVDOVIDecoderConfigurationRecord *) sd->data;
223 ✗ s->dec.cfg = *cfg;
224
225 /* Update configuration record before setting to enc ctx */
226 ✗ cfg->dv_md_compression = s->compression;
227 ✗ if (s->compression && s->dec.cfg.dv_profile < 8) {
228 ✗ av_log(bsf, AV_LOG_ERROR, "Invalid compression level %d for "
229 ✗ "Dolby Vision profile %d.\n", s->compression, s->dec.cfg.dv_profile);
230 ✗ return AVERROR(EINVAL);
231 }
232
233 ✗ s->enc.cfg = *cfg;
234 } else {
235 ✗ av_log(bsf, AV_LOG_WARNING, "No Dolby Vision configuration record "
236 "found? Generating one, but results may be invalid.\n");
237 ✗ ret = ff_dovi_configure_from_codedpar(&s->enc, bsf->par_out, NULL, s->compression,
238 FF_COMPLIANCE_NORMAL);
239 ✗ if (ret < 0)
240 ✗ return ret;
241 /* Be conservative in accepting all compressed RPUs */
242 ✗ s->dec.cfg = s->enc.cfg;
243 ✗ s->dec.cfg.dv_md_compression = AV_DOVI_COMPRESSION_EXTENDED;
244 }
245 }
246
247 ✗ switch (bsf->par_in->codec_id) {
248 ✗ case AV_CODEC_ID_HEVC:
249 ✗ return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_hevc_type);
250 ✗ case AV_CODEC_ID_AV1:
251 ✗ return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_av1_type);
252 ✗ default:
253 ✗ return AVERROR_BUG;
254 }
255 }
256
257 ✗ static void dovi_rpu_close(AVBSFContext *bsf)
258 {
259 ✗ DoviRpuContext *s = bsf->priv_data;
260 ✗ ff_dovi_ctx_unref(&s->dec);
261 ✗ ff_dovi_ctx_unref(&s->enc);
262 ✗ ff_cbs_bsf_generic_close(bsf);
263 ✗ }
264
265 #define OFFSET(x) offsetof(DoviRpuContext, x)
266 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
267 static const AVOption dovi_rpu_options[] = {
268 { "strip", "Strip Dolby Vision metadata", OFFSET(strip), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
269 { "compression", "DV metadata compression mode", OFFSET(compression), AV_OPT_TYPE_INT, { .i64 = AV_DOVI_COMPRESSION_LIMITED }, 0, AV_DOVI_COMPRESSION_EXTENDED, FLAGS, .unit = "compression" },
270 { "none", "Don't compress metadata", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, .flags = FLAGS, .unit = "compression" },
271 { "limited", "Limited metadata compression", 0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_LIMITED}, .flags = FLAGS, .unit = "compression" },
272 { "extended", "Extended metadata compression",0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_EXTENDED}, .flags = FLAGS, .unit = "compression" },
273 { NULL }
274 };
275
276 static const AVClass dovi_rpu_class = {
277 .class_name = "dovi_rpu_bsf",
278 .item_name = av_default_item_name,
279 .option = dovi_rpu_options,
280 .version = LIBAVUTIL_VERSION_INT,
281 };
282
283 static const enum AVCodecID dovi_rpu_codec_ids[] = {
284 AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
285 };
286
287 const FFBitStreamFilter ff_dovi_rpu_bsf = {
288 .p.name = "dovi_rpu",
289 .p.codec_ids = dovi_rpu_codec_ids,
290 .p.priv_class = &dovi_rpu_class,
291 .priv_data_size = sizeof(DoviRpuContext),
292 .init = &dovi_rpu_init,
293 .close = &dovi_rpu_close,
294 .filter = &ff_cbs_bsf_generic_filter,
295 };
296