FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/filter_units.c
Date: 2026-08-15 14:54:27
Exec Total Coverage
Lines: 44 109 40.4%
Functions: 3 4 75.0%
Branches: 19 70 27.1%

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 #include <stdlib.h>
21
22 #include "libavutil/mem.h"
23 #include "libavutil/opt.h"
24
25 #include "libavcodec/bsf.h"
26 #include "libavcodec/bsf_internal.h"
27 #include "libavcodec/cbs.h"
28 #include "libavcodec/cbs_bsf.h"
29
30
31 typedef struct FilterUnitsContext {
32 const AVClass *class;
33
34 CodedBitstreamContext *cbc;
35 CodedBitstreamFragment fragment;
36
37 const char *pass_types;
38 const char *remove_types;
39 /* enum AVDiscard, use int for AVOption */
40 int discard;
41 int discard_flags;
42
43 enum {
44 NOOP,
45 PASS,
46 REMOVE,
47 } mode;
48 CodedBitstreamUnitType *type_list;
49 int nb_types;
50 bool passthrough;
51 } FilterUnitsContext;
52
53
54 static int filter_units_make_type_list(const char *list_string,
55 CodedBitstreamUnitType **type_list,
56 int *nb_types)
57 {
58 CodedBitstreamUnitType *list = NULL;
59 int pass, count;
60
61 for (pass = 1; pass <= 2; pass++) {
62 long value, range_start, range_end;
63 const char *str;
64 char *value_end;
65
66 count = 0;
67 for (str = list_string; *str;) {
68 value = strtol(str, &value_end, 0);
69 if (str == value_end)
70 goto invalid;
71 str = (const char *)value_end;
72 if (*str == '-') {
73 ++str;
74 range_start = value;
75 range_end = strtol(str, &value_end, 0);
76 if (str == value_end)
77 goto invalid;
78
79 for (value = range_start; value < range_end; value++) {
80 if (pass == 2)
81 list[count] = value;
82 ++count;
83 }
84 } else {
85 if (pass == 2)
86 list[count] = value;
87 ++count;
88 }
89 if (*str == '|')
90 ++str;
91 }
92 if (pass == 1) {
93 list = av_malloc_array(count, sizeof(*list));
94 if (!list)
95 return AVERROR(ENOMEM);
96 }
97 }
98
99 *type_list = list;
100 *nb_types = count;
101 return 0;
102
103 invalid:
104 av_freep(&list);
105 return AVERROR(EINVAL);
106 }
107
108 838 static int filter_units_filter(AVBSFContext *bsf, AVPacket *pkt)
109 {
110 838 FilterUnitsContext *ctx = bsf->priv_data;
111 838 CodedBitstreamFragment *frag = &ctx->fragment;
112 int err, i, j;
113
114 838 err = ff_bsf_get_packet_ref(bsf, pkt);
115
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 696 times.
838 if (err < 0)
116 142 return err;
117
118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 696 times.
696 if (ctx->passthrough)
119 return 0;
120
121 696 err = ff_cbs_read_packet(ctx->cbc, frag, pkt);
122
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 696 times.
696 if (err < 0) {
123 av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
124 goto fail;
125 }
126
127 696 ff_cbs_discard_units(ctx->cbc, frag, ctx->discard, ctx->discard_flags);
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 696 times.
696 if (ctx->mode != NOOP) {
129 for (i = frag->nb_units - 1; i >= 0; i--) {
130 for (j = 0; j < ctx->nb_types; j++) {
131 if (frag->units[i].type == ctx->type_list[j])
132 break;
133 }
134 if (ctx->mode == REMOVE ? j < ctx->nb_types
135 : j >= ctx->nb_types)
136 ff_cbs_delete_unit(frag, i);
137 }
138 }
139
140
2/2
✓ Branch 0 taken 562 times.
✓ Branch 1 taken 134 times.
696 if (frag->nb_units == 0) {
141 // Don't return packets with nothing in them.
142 562 err = AVERROR(EAGAIN);
143 562 goto fail;
144 }
145
146 134 err = ff_cbs_write_packet(ctx->cbc, pkt, frag);
147
1/2
✓ Branch 0 taken 134 times.
✗ Branch 1 not taken.
134 if (err < 0) {
148 av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
149 goto fail;
150 }
151
152 134 fail:
153
2/2
✓ Branch 0 taken 562 times.
✓ Branch 1 taken 134 times.
696 if (err < 0)
154 562 av_packet_unref(pkt);
155 696 ff_cbs_fragment_reset(frag);
156
157 696 return err;
158 }
159
160 8 static int filter_units_init(AVBSFContext *bsf)
161 {
162 8 FilterUnitsContext *ctx = bsf->priv_data;
163 int err;
164
165
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
8 if (ctx->pass_types && ctx->remove_types) {
166 av_log(bsf, AV_LOG_ERROR, "Exactly one of pass_types or "
167 "remove_types is required.\n");
168 return AVERROR(EINVAL);
169 }
170
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (ctx->pass_types) {
172 ctx->mode = PASS;
173 err = filter_units_make_type_list(ctx->pass_types,
174 &ctx->type_list, &ctx->nb_types);
175 if (err < 0) {
176 av_log(bsf, AV_LOG_ERROR, "Failed to parse pass_types.\n");
177 return err;
178 }
179
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 } else if (ctx->remove_types) {
180 ctx->mode = REMOVE;
181 err = filter_units_make_type_list(ctx->remove_types,
182 &ctx->type_list, &ctx->nb_types);
183 if (err < 0) {
184 av_log(bsf, AV_LOG_ERROR, "Failed to parse remove_types.\n");
185 return err;
186 }
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 } else if (ctx->discard == AVDISCARD_NONE) {
188 ctx->passthrough = true;
189 return 0;
190 }
191
192 8 err = ff_cbs_init(&ctx->cbc, bsf->par_in->codec_id, bsf);
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (err < 0)
194 return err;
195
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (ctx->discard == AVDISCARD_NONE) {
197 // Don't actually decompose anything, we only want the unit data.
198 ctx->cbc->decompose_unit_types = ctx->type_list;
199 ctx->cbc->nb_decompose_unit_types = 0;
200 }
201
202
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (bsf->par_in->extradata) {
203 8 CodedBitstreamFragment *frag = &ctx->fragment;
204
205 8 err = ff_cbs_read_extradata(ctx->cbc, frag, bsf->par_in);
206
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (err < 0) {
207 av_log(bsf, AV_LOG_ERROR, "Failed to read extradata.\n");
208 } else {
209 8 err = ff_cbs_write_extradata(ctx->cbc, bsf->par_out, frag);
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (err < 0)
211 av_log(bsf, AV_LOG_ERROR, "Failed to write extradata.\n");
212 }
213
214 8 ff_cbs_fragment_reset(frag);
215 }
216
217 8 return err;
218 }
219
220 8 static void filter_units_close(AVBSFContext *bsf)
221 {
222 8 FilterUnitsContext *ctx = bsf->priv_data;
223
224 8 av_freep(&ctx->type_list);
225
226 8 ff_cbs_fragment_free(&ctx->fragment);
227 8 ff_cbs_close(&ctx->cbc);
228 8 }
229
230 #define OFFSET(x) offsetof(FilterUnitsContext, x)
231 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
232 static const AVOption filter_units_options[] = {
233 { "pass_types", "List of unit types to pass through the filter.",
234 OFFSET(pass_types), AV_OPT_TYPE_STRING,
235 { .str = NULL }, .flags = FLAGS },
236 { "remove_types", "List of unit types to remove in the filter.",
237 OFFSET(remove_types), AV_OPT_TYPE_STRING,
238 { .str = NULL }, .flags = FLAGS },
239
240 { "discard", "Remove the selected frames",
241 OFFSET(discard), AV_OPT_TYPE_INT,
242 { .i64 = AVDISCARD_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
243 { "none" , "discard none",
244 0, AV_OPT_TYPE_CONST,
245 { .i64 = AVDISCARD_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
246 { "default" , "discard none, but can be changed after dynamically",
247 0, AV_OPT_TYPE_CONST,
248 { .i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
249 { "nonref", "discard all non-reference frames",
250 0, AV_OPT_TYPE_CONST,
251 { .i64 = AVDISCARD_NONREF }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
252 { "bidir", "discard all bidirectional frames",
253 0, AV_OPT_TYPE_CONST,
254 { .i64 = AVDISCARD_BIDIR }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
255 { "nonintra", "discard all frames except I frames",
256 0, AV_OPT_TYPE_CONST,
257 { .i64 = AVDISCARD_NONINTRA }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
258 { "nonkey", "discard all frames except keyframes",
259 0, AV_OPT_TYPE_CONST,
260 { .i64 = AVDISCARD_NONKEY }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
261 { "all", "discard all frames",
262 0, AV_OPT_TYPE_CONST,
263 { .i64 = AVDISCARD_ALL }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
264
265 { "discard_flags", "flags to control the discard frame behavior",
266 OFFSET(discard_flags), AV_OPT_TYPE_FLAGS,
267 { .i64 = DISCARD_FLAG_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard_flags"},
268 { "keep_non_vcl", "non-vcl units even if the picture has been dropped",
269 0, AV_OPT_TYPE_CONST,
270 { .i64 = DISCARD_FLAG_KEEP_NON_VCL }, INT_MIN, INT_MAX, FLAGS, .unit = "discard_flags"},
271 { NULL }
272 };
273
274 static const AVClass filter_units_class = {
275 .class_name = "filter_units",
276 .item_name = av_default_item_name,
277 .option = filter_units_options,
278 .version = LIBAVUTIL_VERSION_INT,
279 };
280
281 const FFBitStreamFilter ff_filter_units_bsf = {
282 .p.name = "filter_units",
283 .p.codec_ids = ff_cbs_all_codec_ids,
284 .p.priv_class = &filter_units_class,
285 .priv_data_size = sizeof(FilterUnitsContext),
286 .init = &filter_units_init,
287 .close = &filter_units_close,
288 .filter = &filter_units_filter,
289 };
290