FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/trim.c
Date: 2026-09-28 11:37:26
Exec Total Coverage
Lines: 308 345 89.3%
Functions: 28 28 100.0%
Branches: 197 242 81.4%

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/fifo.h"
20 #include "libavutil/intreadwrite.h"
21 #include "libavutil/opt.h"
22 #include "libavutil/timestamp.h"
23
24 #include "libavcodec/bsf.h"
25 #include "libavcodec/bsf_internal.h"
26
27 enum TrimType {
28 TRIM_PTS,
29 TRIM_DTS,
30 TRIM_PKT_INDEX,
31 TRIM_MSEC_PT,
32 TRIM_MSEC_DT,
33 TRIM_DUR_TS,
34 TRIM_DUR_T_MSEC,
35 };
36
37 /* The packet discriminant a bound is compared against; the indexes count consumed
38 * and exported packets. */
39 enum TrimAxis {
40 AXIS_PTS,
41 AXIS_DTS,
42 AXIS_INDEX,
43 AXIS_OUT_INDEX,
44 };
45
46 /* An axis, a unit, and whether the value is relative; the duration types are
47 * the relative forms of pts and msec_pt. */
48 static const struct {
49 enum TrimAxis axis;
50 int msec;
51 int relative;
52 } trim_types[] = {
53 [TRIM_PTS] = { AXIS_PTS, 0, 0 },
54 [TRIM_DTS] = { AXIS_DTS, 0, 0 },
55 [TRIM_PKT_INDEX] = { AXIS_INDEX, 0, 0 },
56 [TRIM_MSEC_PT] = { AXIS_PTS, 1, 0 },
57 [TRIM_MSEC_DT] = { AXIS_DTS, 1, 0 },
58 [TRIM_DUR_TS] = { AXIS_PTS, 0, 1 },
59 [TRIM_DUR_T_MSEC] = { AXIS_PTS, 1, 1 },
60 };
61
62 typedef struct TrimBound {
63 /* Resolved from the options, immutable once init returns. */
64 int active; ///< the option was set at all
65 enum TrimAxis axis;
66 int relative; ///< offset is measured from the reference packet
67 int64_t offset; ///< option value converted to axis units
68
69 /* Rebuilt from the fields above by every reset. */
70 int64_t value; ///< resolved bound, meaningful once pending is clear
71 int pending; ///< the reference packet has not been seen yet
72 } TrimBound;
73
74 typedef struct TrimContext {
75 const AVClass *class;
76
77 int64_t start;
78 int64_t end;
79 int start_type;
80 int end_type;
81 int start_rel;
82 int end_rel;
83 int trim_packets;
84
85 TrimBound start_bound;
86 TrimBound end_bound;
87
88 int audio; ///< trim through skip samples side data
89 int64_t pkt_idx; ///< packets consumed so far
90 int64_t out_idx; ///< packets exported so far
91 int64_t skip_carry; ///< samples of head skip left over by dropped packets
92 uint8_t skip_reason; ///< reason that head skip came with
93
94 int preroll;
95 int preroll_size;
96
97 AVFifo *preroll_fifo; ///< packets held since the last keyframe, NULL when off
98 AVPacket *pending; ///< in-range packet waiting for the preroll to drain
99 int preroll_full; ///< the group overflowed, so hold nothing until the next one
100
101 int64_t nb_exported; ///< packets that left the filter
102 int64_t last_pts; ///< the last of them, kept for the closing summary
103 int64_t last_dts;
104 int last_key;
105 } TrimContext;
106
107 /* Bounds mix option values with stream values; either can overflow. */
108 47 static int add_checked(AVBSFContext *ctx, int64_t *acc, int64_t delta,
109 const char *what)
110 {
111
4/6
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 47 times.
47 if ((delta > 0 && *acc > INT64_MAX - delta) ||
112 ✗ (delta < 0 && *acc < INT64_MIN - delta)) {
113 ✗ av_log(ctx, AV_LOG_ERROR, "%s is not representable\n", what);
114 ✗ return AVERROR(ERANGE);
115 }
116
117 47 *acc += delta;
118
119 47 return 0;
120 }
121
122 1035 static int64_t packet_axis(const TrimContext *s, const AVPacket *pkt,
123 enum TrimAxis axis)
124 {
125
4/4
✓ Branch 0 taken 532 times.
✓ Branch 1 taken 275 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 208 times.
1035 switch (axis) {
126 532 case AXIS_PTS: return pkt->pts;
127 275 case AXIS_DTS: return pkt->dts;
128 20 case AXIS_OUT_INDEX: return s->out_idx;
129 208 default: return s->pkt_idx;
130 }
131 }
132
133 /* The dts stands in for the pts, being monotonic and never above it. */
134 232 static enum TrimAxis monotonic_axis(enum TrimAxis axis)
135 {
136
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 178 times.
232 return axis == AXIS_PTS ? AXIS_DTS : axis;
137 }
138
139 /* An index counts whole packets, so only time places a bound inside one. */
140 289 static int axis_is_time(enum TrimAxis axis)
141 {
142
4/4
✓ Branch 0 taken 106 times.
✓ Branch 1 taken 183 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 73 times.
289 return axis == AXIS_PTS || axis == AXIS_DTS;
143 }
144
145 /* Nothing converted here is ever negative, so a negative result is the
146 * INT64_MIN av_rescale_q reports for one it cannot represent. */
147 602 static int convert_checked(AVBSFContext *ctx, int64_t *value, AVRational from,
148 AVRational to, const char *what)
149 {
150 602 int64_t converted = av_rescale_q(*value, from, to);
151
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 602 times.
602 if (converted < 0) {
153 ✗ av_log(ctx, AV_LOG_ERROR, "%s is not representable\n", what);
154 ✗ return AVERROR(ERANGE);
155 }
156
157 602 *value = converted;
158
159 602 return 0;
160 }
161
162 337 static int time_base_to_samples(AVBSFContext *ctx, int64_t *value,
163 const char *what)
164 {
165 674 return convert_checked(ctx, value, ctx->time_base_in,
166 337 (AVRational){ 1, ctx->par_in->sample_rate }, what);
167 }
168
169 265 static int samples_to_time_base(AVBSFContext *ctx, int64_t *value,
170 const char *what)
171 {
172 265 return convert_checked(ctx, value, (AVRational){ 1, ctx->par_in->sample_rate },
173 ctx->time_base_in, what);
174 }
175
176 /* The samples a packet decodes to, zero where the codec does not give them. */
177 315 static int64_t packet_samples(AVBSFContext *ctx, const AVPacket *pkt)
178 {
179
2/2
✓ Branch 1 taken 296 times.
✓ Branch 2 taken 19 times.
315 return FFMAX(av_get_audio_frame_duration2(ctx->par_in, pkt->size), 0);
180 }
181
182 /* A skip reaching past a dropped packet only needs how much of itself that
183 * packet used up, which a duration still estimates closely enough. */
184 8 static int64_t packet_skip_consumed(AVBSFContext *ctx, const AVPacket *pkt)
185 {
186 8 int64_t nb_samples = packet_samples(ctx, pkt);
187
188
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 if (nb_samples > 0)
189 7 return nb_samples;
190
191 1 nb_samples = av_rescale_q(pkt->duration, ctx->time_base_in,
192 1 (AVRational){ 1, ctx->par_in->sample_rate });
193
194 1 return FFMAX(nb_samples, 0);
195 }
196
197 /* How far a packet reaches past its axis value. Audio reaches as far as the
198 * samples it decodes to, a duration also covering any gap after them; without
199 * them it reaches nowhere, so no bound can fall inside it. */
200 277 static int packet_span(AVBSFContext *ctx, const AVPacket *pkt,
201 enum TrimAxis axis, int64_t *span)
202 {
203 277 TrimContext *s = ctx->priv_data;
204
205 277 *span = 0;
206
207
2/2
✓ Branch 1 taken 71 times.
✓ Branch 2 taken 206 times.
277 if (!axis_is_time(axis))
208 71 return 0;
209
210
2/2
✓ Branch 0 taken 103 times.
✓ Branch 1 taken 103 times.
206 if (s->audio) {
211 103 *span = packet_samples(ctx, pkt);
212 103 return samples_to_time_base(ctx, span, "packet span");
213 }
214
215 103 *span = FFMAX(pkt->duration, 0);
216
217 103 return 0;
218 }
219
220 106 static int bound_init(AVBSFContext *ctx, TrimBound *b, int active,
221 int64_t value, int type, int rel, const char *name)
222 {
223 106 b->active = active;
224
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 81 times.
106 if (!active)
225 25 return 0;
226
227 81 b->axis = trim_types[type].axis;
228
4/4
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 65 times.
81 b->relative = rel || trim_types[type].relative;
229
230
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
81 if (trim_types[type].relative && value < 0) {
231 ✗ av_log(ctx, AV_LOG_ERROR, "%s duration must not be negative\n", name);
232 ✗ return AVERROR(EINVAL);
233 }
234
235
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 79 times.
81 if (trim_types[type].msec) {
236 2 int64_t ts = av_rescale_q(value, (AVRational){ 1, 1000 },
237 ctx->time_base_in);
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ts == INT64_MIN) {
239 ✗ av_log(ctx, AV_LOG_ERROR, "%s of %"PRId64" ms is not representable "
240 "in the stream time base\n", name, value);
241 ✗ return AVERROR(ERANGE);
242 }
243 2 value = ts;
244 }
245
246 81 b->offset = value;
247
248 81 return 0;
249 }
250
251 116 static void bound_reset(TrimBound *b)
252 {
253 116 b->value = b->offset;
254 116 b->pending = b->relative;
255 116 }
256
257 16 static int bound_resolve(AVBSFContext *ctx, TrimBound *b, int64_t ref,
258 const char *name)
259 {
260 16 int ret = add_checked(ctx, &b->value, ref, name);
261
262
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (ret < 0)
263 ✗ return ret;
264
265 16 b->pending = 0;
266
267 16 return 0;
268 }
269
270 /* How much of the packet lies before the start bound, EAGAIN when all of it
271 * does. */
272 647 static int trim_head(AVBSFContext *ctx, const AVPacket *pkt, int64_t *head)
273 {
274 647 TrimContext *s = ctx->priv_data;
275 int64_t v, span;
276 int ret;
277
278
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 557 times.
647 if (!s->start_bound.active)
279 90 return 0;
280
281 /* A packet without the discriminant a bound uses cannot be placed. */
282 557 v = packet_axis(s, pkt, s->start_bound.axis);
283
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 556 times.
557 if (v == AV_NOPTS_VALUE)
284 1 return AVERROR(EAGAIN);
285
286 /* The start is measured from the first packet of the stream. */
287
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 552 times.
556 if (s->start_bound.pending) {
288 4 ret = bound_resolve(ctx, &s->start_bound, v, "start bound");
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
290 ✗ return ret;
291 }
292
293
2/2
✓ Branch 0 taken 428 times.
✓ Branch 1 taken 128 times.
556 if (v >= s->start_bound.value)
294 428 return 0;
295
296 128 ret = packet_span(ctx, pkt, s->start_bound.axis, &span);
297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 if (ret < 0)
298 ✗ return ret;
299
300
4/4
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 114 times.
✓ Branch 4 taken 11 times.
128 if (!s->trim_packets || av_sat_add64(v, span) <= s->start_bound.value)
301 117 return AVERROR(EAGAIN);
302 11 *head = s->start_bound.value - v;
303
304 11 return 0;
305 }
306
307 /* How much of the packet lies past the end bound, measured from head, where it
308 * starts contributing. EAGAIN when none of it is inside, EOF once nothing later
309 * can be. */
310 527 static int trim_tail(AVBSFContext *ctx, const AVPacket *pkt, int64_t head,
311 int64_t *tail)
312 {
313 527 TrimContext *s = ctx->priv_data;
314 int64_t v;
315 int ret;
316
317
2/2
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 267 times.
527 if (!s->end_bound.active)
318 260 return 0;
319
320 /* The end is measured from the first exported packet, at the point it is
321 * trimmed to. */
322
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 220 times.
267 if (s->end_bound.pending) {
323 47 int64_t ref = packet_axis(s, pkt, s->end_bound.axis);
324
325
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 12 times.
47 if (ref == AV_NOPTS_VALUE)
326 35 return AVERROR(EAGAIN);
327
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 2 times.
12 if (axis_is_time(s->end_bound.axis)) {
328 10 ret = add_checked(ctx, &ref, head, "end anchor");
329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (ret < 0)
330 ✗ return ret;
331 }
332 12 ret = bound_resolve(ctx, &s->end_bound, ref, "end bound");
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (ret < 0)
334 ✗ return ret;
335 }
336
337 /* Only a discriminant that cannot come back into range may end the stream. */
338 232 v = packet_axis(s, pkt, monotonic_axis(s->end_bound.axis));
339
4/4
✓ Branch 0 taken 207 times.
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 174 times.
232 if (v != AV_NOPTS_VALUE && v >= s->end_bound.value)
340 33 return AVERROR_EOF;
341
342 199 v = packet_axis(s, pkt, s->end_bound.axis);
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
199 if (v == AV_NOPTS_VALUE)
344 ✗ return AVERROR(EAGAIN);
345
346 /* Reordering can still bring in-range packets after this one. */
347
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 160 times.
199 if (v >= s->end_bound.value)
348 39 return AVERROR(EAGAIN);
349
350
2/2
✓ Branch 0 taken 149 times.
✓ Branch 1 taken 11 times.
160 if (s->trim_packets) {
351 int64_t span, past;
352
353 149 ret = packet_span(ctx, pkt, s->end_bound.axis, &span);
354
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 149 times.
149 if (ret < 0)
355 ✗ return ret;
356
357 149 past = av_sat_add64(v, span);
358
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 133 times.
149 if (past > s->end_bound.value)
359 16 *tail = past - s->end_bound.value;
360 }
361
362 160 return 0;
363 }
364
365 /* Audio is trimmed in sample space through skip samples side data, leaving the
366 * timestamps untouched. */
367 204 static int trim_audio(AVBSFContext *ctx, AVPacket *pkt, int64_t head, int keep)
368 {
369 204 TrimContext *s = ctx->priv_data;
370 204 int64_t nb_samples = packet_samples(ctx, pkt);
371 204 int64_t tail = 0, anchor, input_head;
372 204 int trimming = head != 0;
373 204 uint8_t head_reason = 0, tail_reason = 0, input_reason;
374 size_t size;
375 204 uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES,
376 &size);
377 int ret;
378
379 204 ret = time_base_to_samples(ctx, &head, "head trim");
380
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 204 times.
204 if (ret < 0)
381 ✗ return ret;
382
383 /* A decoder replaces the skip it carries with any nonzero one a packet
384 * brings, whether larger or smaller, so resolve the incoming skip first. */
385 204 input_head = s->skip_carry;
386 204 input_reason = s->skip_reason;
387
388
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 193 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 1 times.
204 if (side && size >= 8 && AV_RL32(side)) {
389 10 input_head = AV_RL32(side);
390
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 input_reason = size >= 10 ? AV_RL8(side + 8) : 0;
391 }
392
393 /* That skip and this filter's trim are offsets from the same boundary, so
394 * the larger wins and brings its own reason; the trim has none. */
395
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 4 times.
204 if (input_head >= head) {
396 200 head = input_head;
397 200 head_reason = input_reason;
398 }
399
400 /* Only a trim of this filter collapses a packet; one already covered by its
401 * own skip is passed through. */
402
7/8
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 158 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 4 times.
204 if (!keep || (trimming && nb_samples > 0 && head >= nb_samples))
403 42 goto drop;
404
405 /* A bound measured from this packet is anchored past its composed skip. */
406 162 anchor = head;
407 162 ret = samples_to_time_base(ctx, &anchor, "end anchor");
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 162 times.
162 if (ret < 0)
409 ✗ return ret;
410
411 162 ret = trim_tail(ctx, pkt, anchor, &tail);
412
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 133 times.
162 if (ret < 0) {
413
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 13 times.
29 if (ret != AVERROR(EAGAIN))
414 16 return ret;
415 13 goto drop;
416 }
417
418 133 trimming |= tail != 0;
419 133 ret = time_base_to_samples(ctx, &tail, "tail trim");
420
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 133 times.
133 if (ret < 0)
421 ✗ return ret;
422
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 129 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
133 if (side && size >= 8 && AV_RL32(side + 4) >= tail) {
423 2 tail = AV_RL32(side + 4);
424
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 tail_reason = size >= 10 ? AV_RL8(side + 9) : 0;
425 }
426
427
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 122 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 10 times.
133 if (trimming && nb_samples > 0 && head >= nb_samples - tail)
428 1 goto drop;
429
430 /* The side data below carries any excess from here on. */
431 132 s->skip_carry = 0;
432 132 s->skip_reason = 0;
433
434
4/4
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 114 times.
✓ Branch 3 taken 6 times.
132 if (!head && !tail)
435 114 return 0;
436
437
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (head > UINT32_MAX || tail > UINT32_MAX) {
438 ✗ av_log(ctx, AV_LOG_ERROR, "skip samples count is not representable\n");
439 ✗ return AVERROR(ERANGE);
440 }
441
442
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
18 if (!side || size < 10) {
443 15 side = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10);
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (!side)
445 ✗ return AVERROR(ENOMEM);
446 }
447
448 18 AV_WL32(side, head);
449 18 AV_WL32(side + 4, tail);
450 18 AV_WL8 (side + 8, head_reason);
451 18 AV_WL8 (side + 9, tail_reason);
452
453 18 return 0;
454
455 56 drop:
456 /* A decoder subtracts the samples of every frame it discards and carries
457 * the rest, so do the same across dropped packets. */
458
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 48 times.
56 if (head > 0) {
459 8 int64_t consumed = packet_skip_consumed(ctx, pkt);
460
461
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (consumed <= 0) {
462 ✗ av_log(ctx, AV_LOG_ERROR, "cannot carry a %"PRId64" sample skip past "
463 "a dropped packet of unknown sample count\n", head);
464 ✗ return AVERROR(EINVAL);
465 }
466 8 s->skip_carry = FFMAX(head - consumed, 0);
467
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 s->skip_reason = s->skip_carry ? head_reason : 0;
468 }
469
470 56 return AVERROR(EAGAIN);
471 }
472
473 /* Everything but audio is trimmed on the timeline itself, which no side data
474 * can express. */
475 365 static int trim_shift(AVBSFContext *ctx, AVPacket *pkt, int64_t head)
476 {
477 365 int64_t tail = 0;
478 int ret;
479
480 365 ret = trim_tail(ctx, pkt, head, &tail);
481
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 287 times.
365 if (ret < 0)
482 78 return ret;
483
484
4/4
✓ Branch 0 taken 282 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 275 times.
✓ Branch 3 taken 7 times.
287 if (!head && !tail)
485 275 return 0;
486
487 /* Bounds on different axes can select disjoint parts of one packet, and
488 * each trim is below the duration, so the subtraction cannot overflow. */
489
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11 times.
12 if (head >= pkt->duration - tail)
490 1 return AVERROR(EAGAIN);
491
492
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (pkt->pts != AV_NOPTS_VALUE) {
493 11 ret = add_checked(ctx, &pkt->pts, head, "packet pts");
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (ret < 0)
495 ✗ return ret;
496 }
497
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
11 if (pkt->dts != AV_NOPTS_VALUE) {
498 10 ret = add_checked(ctx, &pkt->dts, head, "packet dts");
499
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (ret < 0)
500 ✗ return ret;
501 }
502 11 pkt->duration -= head + tail;
503
504 11 return 0;
505 }
506
507 121 static void preroll_clear(AVBSFContext *ctx)
508 {
509 121 TrimContext *s = ctx->priv_data;
510 AVPacket *held;
511
512
4/4
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 100 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 21 times.
146 while (s->preroll_fifo && av_fifo_read(s->preroll_fifo, &held, 1) >= 0)
513 25 av_packet_free(&held);
514 121 }
515
516 /* A held packet is decoded for the frames after it to reference and dropped
517 * before output, so the exported stream still begins at the bound. */
518 25 static void preroll_export(AVBSFContext *ctx, AVPacket *pkt)
519 {
520 25 TrimContext *s = ctx->priv_data;
521 AVPacket *held;
522
523
2/2
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 5 times.
25 if (av_fifo_read(s->preroll_fifo, &held, 1) >= 0) {
524 20 av_packet_move_ref(pkt, held);
525 20 av_packet_free(&held);
526 20 pkt->flags |= AV_PKT_FLAG_DISCARD;
527 20 return;
528 }
529
530 5 av_packet_move_ref(pkt, s->pending);
531 5 av_packet_free(&s->pending);
532 }
533
534 50 static int preroll_hold(AVBSFContext *ctx, AVPacket *pkt)
535 {
536 50 TrimContext *s = ctx->priv_data;
537 AVPacket *held;
538 int ret;
539
540
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 46 times.
50 if (s->preroll_full)
541 4 return AVERROR(EAGAIN);
542
543 46 held = av_packet_alloc();
544
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (!held)
545 ✗ return AVERROR(ENOMEM);
546
547 46 av_packet_move_ref(held, pkt);
548 46 ret = av_fifo_write(s->preroll_fifo, &held, 1);
549
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 45 times.
46 if (ret < 0) {
550 1 av_packet_move_ref(pkt, held);
551 1 av_packet_free(&held);
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret != AVERROR(ENOSPC))
553 ✗ return ret;
554
555 /* A group too long to hold is dropped whole rather than exported as a
556 * fragment no decoder can start from. */
557 1 av_log(ctx, AV_LOG_WARNING, "preroll exceeds %d packets, the first "
558 "packets in range will not be decodable\n", s->preroll_size);
559 1 preroll_clear(ctx);
560 1 s->preroll_full = 1;
561 }
562
563 46 return AVERROR(EAGAIN);
564 }
565
566 102 static void log_exported_packet(AVBSFContext *ctx, const char *which,
567 int64_t idx, int64_t pts, int64_t dts, int key)
568 {
569 102 av_log(ctx, AV_LOG_VERBOSE, "%s exported packet: output index %"PRId64", "
570 102 "pts %s, dts %s, keyframe %d\n", which, idx, av_ts2str(pts),
571 102 av_ts2str(dts), key);
572 102 }
573
574 438 static void packet_exported(AVBSFContext *ctx, const AVPacket *pkt)
575 {
576 438 TrimContext *s = ctx->priv_data;
577
578 438 s->last_pts = pkt->pts;
579 438 s->last_dts = pkt->dts;
580 438 s->last_key = !!(pkt->flags & AV_PKT_FLAG_KEY);
581
582
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 387 times.
438 if (!s->nb_exported++)
583 51 log_exported_packet(ctx, "first", 0, s->last_pts, s->last_dts,
584 s->last_key);
585 438 }
586
587 /* Only the flush or close ending the stream can tell which packet was last. */
588 58 static void trim_report(AVBSFContext *ctx)
589 {
590 58 TrimContext *s = ctx->priv_data;
591
592
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 7 times.
58 if (s->nb_exported)
593 51 log_exported_packet(ctx, "last", s->nb_exported - 1, s->last_pts,
594 s->last_dts, s->last_key);
595
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 else if (s->pkt_idx)
596 4 av_log(ctx, AV_LOG_WARNING, "no packet was in range, the output is "
597 "empty\n");
598 58 }
599
600 1089 static int trim_filter(AVBSFContext *ctx, AVPacket *pkt)
601 {
602 1089 TrimContext *s = ctx->priv_data;
603 1089 int64_t head = 0;
604 int ret;
605
606 /* The preroll an in-range packet needs is exported ahead of it. */
607
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1069 times.
1089 if (s->pending) {
608 20 preroll_export(ctx, pkt);
609 20 packet_exported(ctx, pkt);
610 20 return 0;
611 }
612
613 1069 ret = ff_bsf_get_packet_ref(ctx, pkt);
614
2/2
✓ Branch 0 taken 422 times.
✓ Branch 1 taken 647 times.
1069 if (ret < 0)
615 422 return ret;
616
617 /* Only the group the start bound falls in is worth keeping, so the window
618 * starts over at every keyframe. */
619
4/4
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 522 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 116 times.
647 if (s->preroll_fifo && pkt->flags & AV_PKT_FLAG_KEY) {
620 9 preroll_clear(ctx);
621 9 s->preroll_full = 0;
622 }
623
624 647 ret = trim_head(ctx, pkt, &head);
625
626 /* A packet before the start still composes with the skip it carries, so the
627 * audio path runs on it too and is told the verdict instead. */
628
5/6
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 443 times.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 164 times.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
647 if (s->audio && (!ret || ret == AVERROR(EAGAIN)))
629 204 ret = trim_audio(ctx, pkt, head, !ret);
630
2/2
✓ Branch 0 taken 365 times.
✓ Branch 1 taken 78 times.
443 else if (!ret)
631 365 ret = trim_shift(ctx, pkt, head);
632
3/4
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✓ Branch 3 taken 28 times.
78 else if (ret == AVERROR(EAGAIN) && s->preroll_fifo)
633 50 ret = preroll_hold(ctx, pkt);
634
635 647 s->pkt_idx++;
636
2/2
✓ Branch 0 taken 229 times.
✓ Branch 1 taken 418 times.
647 if (ret < 0) {
637 229 av_packet_unref(pkt);
638 229 return ret;
639 }
640
641 418 s->out_idx++;
642
4/4
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 343 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 70 times.
418 if (s->preroll_fifo && av_fifo_can_read(s->preroll_fifo)) {
643 5 s->pending = av_packet_alloc();
644
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!s->pending) {
645 ✗ av_packet_unref(pkt);
646 ✗ return AVERROR(ENOMEM);
647 }
648 5 av_packet_move_ref(s->pending, pkt);
649 5 preroll_export(ctx, pkt);
650 }
651
652 418 packet_exported(ctx, pkt);
653
654 418 return 0;
655 }
656
657 /* Runtime state is derived from the options, so a flush restores it exactly. */
658 58 static void trim_reset(AVBSFContext *ctx)
659 {
660 58 TrimContext *s = ctx->priv_data;
661
662 58 bound_reset(&s->start_bound);
663 58 bound_reset(&s->end_bound);
664
665 58 s->pkt_idx = 0;
666 58 s->out_idx = 0;
667 58 s->nb_exported = 0;
668 58 s->skip_carry = 0;
669 58 s->skip_reason = 0;
670
671 58 preroll_clear(ctx);
672 58 av_packet_free(&s->pending);
673 58 s->preroll_full = 0;
674 58 }
675
676 5 static void trim_flush(AVBSFContext *ctx)
677 {
678 5 trim_report(ctx);
679 5 trim_reset(ctx);
680 5 }
681
682 53 static void trim_close(AVBSFContext *ctx)
683 {
684 53 TrimContext *s = ctx->priv_data;
685
686 53 trim_report(ctx);
687 53 preroll_clear(ctx);
688 53 av_packet_free(&s->pending);
689 53 av_fifo_freep2(&s->preroll_fifo);
690 53 }
691
692 53 static int trim_init(AVBSFContext *ctx)
693 {
694 53 TrimContext *s = ctx->priv_data;
695 int ret;
696
697
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
53 if (s->start == INT64_MIN && s->end == INT64_MAX) {
698 ✗ av_log(ctx, AV_LOG_ERROR, "At least one of start or end must be set\n");
699 ✗ return AVERROR(EINVAL);
700 }
701
702 53 ret = bound_init(ctx, &s->start_bound, s->start != INT64_MIN, s->start,
703 s->start_type, s->start_rel, "start");
704
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 if (ret < 0)
705 ✗ return ret;
706
707 53 ret = bound_init(ctx, &s->end_bound, s->end != INT64_MAX, s->end,
708 s->end_type, s->end_rel, "end");
709
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 if (ret < 0)
710 ✗ return ret;
711
712 /* A packet count measured from the first exported packet is a promise about
713 * the exported stream, so it is counted there. */
714
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 49 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
53 if (s->end_bound.axis == AXIS_INDEX && s->end_bound.relative)
715 2 s->end_bound.axis = AXIS_OUT_INDEX;
716
717 /* Shifting the timeline leaves the samples where they were, so audio with
718 * no sample space to be trimmed in cannot be trimmed at all. */
719
4/4
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 24 times.
53 if (s->trim_packets && ctx->par_in->codec_type == AVMEDIA_TYPE_AUDIO) {
720
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 if (ctx->par_in->sample_rate <= 0 || ctx->time_base_in.num <= 0 ||
721
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 ctx->time_base_in.den <= 0) {
722 ✗ av_log(ctx, AV_LOG_ERROR, "audio is trimmed through skip samples "
723 "side data, which needs a sample rate and a time base\n");
724 ✗ return AVERROR(EINVAL);
725 }
726 26 s->audio = 1;
727 }
728
729 /* The window is bounded by a keyframe, which only video has. Audio needs a
730 * preroll counted in samples, which is not the one this would give. */
731
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
53 if (s->preroll && s->start_bound.active &&
732
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 ctx->par_in->codec_type == AVMEDIA_TYPE_VIDEO) {
733 5 s->preroll_fifo = av_fifo_alloc2(1, sizeof(AVPacket *),
734 AV_FIFO_FLAG_AUTO_GROW);
735
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!s->preroll_fifo)
736 ✗ return AVERROR(ENOMEM);
737 5 av_fifo_auto_grow_limit(s->preroll_fifo, s->preroll_size);
738 }
739
740 53 trim_reset(ctx);
741
742 53 return 0;
743 }
744
745 #define OFFSET(x) offsetof(TrimContext, x)
746 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)
747 static const AVOption trim_options[] = {
748 { "start", "time or index marking the start of the accepted range", OFFSET(start),
749 AV_OPT_TYPE_INT64, { .i64 = INT64_MIN }, INT64_MIN, INT64_MAX, FLAGS },
750 { "start_type", "how to interpret start", OFFSET(start_type),
751 AV_OPT_TYPE_INT, { .i64 = TRIM_PTS }, 0, TRIM_MSEC_DT, FLAGS, .unit = "start_type" },
752 { "pts", "stream time base pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PTS }, 0, 0, FLAGS, .unit = "start_type" },
753 { "dts", "stream time base dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DTS }, 0, 0, FLAGS, .unit = "start_type" },
754 { "pkt_index", "packet index", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PKT_INDEX }, 0, 0, FLAGS, .unit = "start_type" },
755 { "msec_pt", "milliseconds, matched against pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_PT }, 0, 0, FLAGS, .unit = "start_type" },
756 { "msec_dt", "milliseconds, matched against dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_DT }, 0, 0, FLAGS, .unit = "start_type" },
757 { "start_rel", "interpret start relative to the first packet of the stream", OFFSET(start_rel),
758 AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
759 { "end", "time or index marking the end of the accepted range", OFFSET(end),
760 AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, INT64_MIN, INT64_MAX, FLAGS },
761 { "end_type", "how to interpret end", OFFSET(end_type),
762 AV_OPT_TYPE_INT, { .i64 = TRIM_PTS }, 0, TRIM_DUR_T_MSEC, FLAGS, .unit = "end_type" },
763 { "pts", "stream time base pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PTS }, 0, 0, FLAGS, .unit = "end_type" },
764 { "dts", "stream time base dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DTS }, 0, 0, FLAGS, .unit = "end_type" },
765 { "pkt_index", "packet index", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PKT_INDEX }, 0, 0, FLAGS, .unit = "end_type" },
766 { "msec_pt", "milliseconds, matched against pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_PT }, 0, 0, FLAGS, .unit = "end_type" },
767 { "msec_dt", "milliseconds, matched against dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_DT }, 0, 0, FLAGS, .unit = "end_type" },
768 { "dur_ts", "duration in stream time base", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DUR_TS }, 0, 0, FLAGS, .unit = "end_type" },
769 { "dur_t_msec", "duration in milliseconds", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DUR_T_MSEC }, 0, 0, FLAGS, .unit = "end_type" },
770 { "end_rel", "interpret end relative to the first exported packet", OFFSET(end_rel),
771 AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
772 { "trim_packets", "trim packets straddling a boundary instead of exporting them "
773 "untouched or dropping them", OFFSET(trim_packets),
774 AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS },
775 { "preroll", "export the packets the first packet in range needs to be decodable, "
776 "flagged for the decoder to drop them after decoding", OFFSET(preroll),
777 AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
778 { "preroll_size", "maximum number of packets held for preroll", OFFSET(preroll_size),
779 AV_OPT_TYPE_INT, { .i64 = 300 }, 1, INT_MAX, FLAGS },
780 { NULL },
781 };
782
783 static const AVClass trim_class = {
784 .class_name = "trim",
785 .item_name = av_default_item_name,
786 .option = trim_options,
787 .version = LIBAVUTIL_VERSION_INT,
788 };
789
790 const FFBitStreamFilter ff_trim_bsf = {
791 .p.name = "trim",
792 .p.priv_class = &trim_class,
793 .priv_data_size = sizeof(TrimContext),
794 .init = trim_init,
795 .close = trim_close,
796 .flush = trim_flush,
797 .filter = trim_filter,
798 };
799