FFmpeg coverage


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