FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/trim.c
Date: 2026-10-22 02:10:50
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 1049 static int64_t packet_axis(const TrimContext *s, const AVPacket *pkt,
123 enum TrimAxis axis)
124 {
125
4/4
✓ Branch 0 taken 546 times.
✓ Branch 1 taken 275 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 208 times.
1049 switch (axis) {
126 546 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 291 static int axis_is_time(enum TrimAxis axis)
141 {
142
4/4
✓ Branch 0 taken 106 times.
✓ Branch 1 taken 185 times.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 73 times.
291 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 605 static int convert_checked(AVBSFContext *ctx, int64_t *value, AVRational from,
148 AVRational to, const char *what)
149 {
150 605 int64_t converted = av_rescale_q(*value, from, to);
151
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 605 times.
605 if (converted < 0) {
153 ✗ av_log(ctx, AV_LOG_ERROR, "%s is not representable\n", what);
154 ✗ return AVERROR(ERANGE);
155 }
156
157 605 *value = converted;
158
159 605 return 0;
160 }
161
162 339 static int time_base_to_samples(AVBSFContext *ctx, int64_t *value,
163 const char *what)
164 {
165 678 return convert_checked(ctx, value, ctx->time_base_in,
166 339 (AVRational){ 1, ctx->par_in->sample_rate }, what);
167 }
168
169 266 static int samples_to_time_base(AVBSFContext *ctx, int64_t *value,
170 const char *what)
171 {
172 266 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 316 static int64_t packet_samples(AVBSFContext *ctx, const AVPacket *pkt)
178 {
179
2/2
✓ Branch 1 taken 296 times.
✓ Branch 2 taken 20 times.
316 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 279 static int packet_span(AVBSFContext *ctx, const AVPacket *pkt,
201 enum TrimAxis axis, int64_t *span)
202 {
203 279 TrimContext *s = ctx->priv_data;
204
205 279 *span = 0;
206
207
2/2
✓ Branch 1 taken 71 times.
✓ Branch 2 taken 208 times.
279 if (!axis_is_time(axis))
208 71 return 0;
209
210
2/2
✓ Branch 0 taken 103 times.
✓ Branch 1 taken 105 times.
208 if (s->audio) {
211 103 *span = packet_samples(ctx, pkt);
212 103 return samples_to_time_base(ctx, span, "packet span");
213 }
214
215 105 *span = FFMAX(pkt->duration, 0);
216
217 105 return 0;
218 }
219
220 108 static int bound_init(AVBSFContext *ctx, TrimBound *b, int active,
221 int64_t value, int type, int rel, const char *name)
222 {
223 108 b->active = active;
224
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 82 times.
108 if (!active)
225 26 return 0;
226
227 82 b->axis = trim_types[type].axis;
228
4/4
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 66 times.
82 b->relative = rel || trim_types[type].relative;
229
230
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 71 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
82 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 80 times.
82 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 82 b->offset = value;
247
248 82 return 0;
249 }
250
251 120 static void bound_reset(TrimBound *b)
252 {
253 120 b->value = b->offset;
254 120 b->pending = b->relative;
255 120 }
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 661 static int trim_head(AVBSFContext *ctx, const AVPacket *pkt, int64_t *head)
273 {
274 661 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 571 times.
661 if (!s->start_bound.active)
279 90 return 0;
280
281 /* A packet without the discriminant a bound uses cannot be placed. */
282 571 v = packet_axis(s, pkt, s->start_bound.axis);
283
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 570 times.
571 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 566 times.
570 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 440 times.
✓ Branch 1 taken 130 times.
570 if (v >= s->start_bound.value)
294 440 return 0;
295
296 130 ret = packet_span(ctx, pkt, s->start_bound.axis, &span);
297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 130 times.
130 if (ret < 0)
298 ✗ return ret;
299
300
4/4
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 116 times.
✓ Branch 4 taken 11 times.
130 if (!s->trim_packets || av_sat_add64(v, span) <= s->start_bound.value)
301 119 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 539 static int trim_tail(AVBSFContext *ctx, const AVPacket *pkt, int64_t head,
311 int64_t *tail)
312 {
313 539 TrimContext *s = ctx->priv_data;
314 int64_t v;
315 int ret;
316
317
2/2
✓ Branch 0 taken 272 times.
✓ Branch 1 taken 267 times.
539 if (!s->end_bound.active)
318 272 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 205 static int trim_audio(AVBSFContext *ctx, AVPacket *pkt, int64_t head, int keep)
368 {
369 205 TrimContext *s = ctx->priv_data;
370 205 int64_t nb_samples = packet_samples(ctx, pkt);
371 205 int64_t tail = 0, anchor, input_head;
372 205 int trimming = head != 0;
373 205 uint8_t head_reason = 0, tail_reason = 0, input_reason;
374 size_t size;
375 205 uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES,
376 &size);
377 int ret;
378
379 205 ret = time_base_to_samples(ctx, &head, "head trim");
380
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 205 times.
205 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 205 input_head = s->skip_carry;
386 205 input_reason = s->skip_reason;
387
388
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 194 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 1 times.
205 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 201 times.
✓ Branch 1 taken 4 times.
205 if (input_head >= head) {
396 201 head = input_head;
397 201 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 165 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 159 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 4 times.
205 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 163 anchor = head;
407 163 ret = samples_to_time_base(ctx, &anchor, "end anchor");
408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
163 if (ret < 0)
409 ✗ return ret;
410
411 163 ret = trim_tail(ctx, pkt, anchor, &tail);
412
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 134 times.
163 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 134 trimming |= tail != 0;
419 134 ret = time_base_to_samples(ctx, &tail, "tail trim");
420
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 134 times.
134 if (ret < 0)
421 ✗ return ret;
422
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
134 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 123 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 10 times.
134 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 133 s->skip_carry = 0;
432 133 s->skip_reason = 0;
433
434
4/4
✓ Branch 0 taken 121 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 115 times.
✓ Branch 3 taken 6 times.
133 if (!head && !tail)
435 115 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 376 static int trim_shift(AVBSFContext *ctx, AVPacket *pkt, int64_t head)
476 {
477 376 int64_t tail = 0;
478 int ret;
479
480 376 ret = trim_tail(ctx, pkt, head, &tail);
481
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 298 times.
376 if (ret < 0)
482 78 return ret;
483
484
4/4
✓ Branch 0 taken 293 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 286 times.
✓ Branch 3 taken 7 times.
298 if (!head && !tail)
485 286 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 125 static void preroll_clear(AVBSFContext *ctx)
508 {
509 125 TrimContext *s = ctx->priv_data;
510 AVPacket *held;
511
512
4/4
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 100 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 25 times.
150 while (s->preroll_fifo && av_fifo_read(s->preroll_fifo, &held, 1) >= 0)
513 25 av_packet_free(&held);
514 125 }
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 28 static void preroll_export(AVBSFContext *ctx, AVPacket *pkt)
519 {
520 28 TrimContext *s = ctx->priv_data;
521 AVPacket *held;
522
523
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 6 times.
28 if (av_fifo_read(s->preroll_fifo, &held, 1) >= 0) {
524 22 av_packet_move_ref(pkt, held);
525 22 av_packet_free(&held);
526 22 pkt->flags |= AV_PKT_FLAG_DISCARD;
527 22 return;
528 }
529
530 6 av_packet_move_ref(pkt, s->pending);
531 6 av_packet_free(&s->pending);
532 }
533
534 52 static int preroll_hold(AVBSFContext *ctx, AVPacket *pkt)
535 {
536 52 TrimContext *s = ctx->priv_data;
537 AVPacket *held;
538 int ret;
539
540
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 48 times.
52 if (s->preroll_full)
541 4 return AVERROR(EAGAIN);
542
543 48 held = av_packet_alloc();
544
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (!held)
545 ✗ return AVERROR(ENOMEM);
546
547 48 av_packet_move_ref(held, pkt);
548 48 ret = av_fifo_write(s->preroll_fifo, &held, 1);
549
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 47 times.
48 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 48 return AVERROR(EAGAIN);
564 }
565
566 104 static void log_exported_packet(AVBSFContext *ctx, const char *which,
567 int64_t idx, int64_t pts, int64_t dts, int key)
568 {
569 104 av_log(ctx, AV_LOG_VERBOSE, "%s exported packet: output index %"PRId64", "
570 104 "pts %s, dts %s, keyframe %d\n", which, idx, av_ts2str(pts),
571 104 av_ts2str(dts), key);
572 104 }
573
574 452 static void packet_exported(AVBSFContext *ctx, const AVPacket *pkt)
575 {
576 452 TrimContext *s = ctx->priv_data;
577
578 452 s->last_pts = pkt->pts;
579 452 s->last_dts = pkt->dts;
580 452 s->last_key = !!(pkt->flags & AV_PKT_FLAG_KEY);
581
582
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 400 times.
452 if (!s->nb_exported++)
583 52 log_exported_packet(ctx, "first", 0, s->last_pts, s->last_dts,
584 s->last_key);
585 452 }
586
587 /* Only the flush or close ending the stream can tell which packet was last. */
588 60 static void trim_report(AVBSFContext *ctx)
589 {
590 60 TrimContext *s = ctx->priv_data;
591
592
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 8 times.
60 if (s->nb_exported)
593 52 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 4 times.
8 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 60 }
599
600 1118 static int trim_filter(AVBSFContext *ctx, AVPacket *pkt)
601 {
602 1118 TrimContext *s = ctx->priv_data;
603 1118 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 22 times.
✓ Branch 1 taken 1096 times.
1118 if (s->pending) {
608 22 preroll_export(ctx, pkt);
609 22 packet_exported(ctx, pkt);
610 22 return 0;
611 }
612
613 1096 ret = ff_bsf_get_packet_ref(ctx, pkt);
614
2/2
✓ Branch 0 taken 435 times.
✓ Branch 1 taken 661 times.
1096 if (ret < 0)
615 435 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 138 times.
✓ Branch 1 taken 523 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 128 times.
661 if (s->preroll_fifo && pkt->flags & AV_PKT_FLAG_KEY) {
620 10 preroll_clear(ctx);
621 10 s->preroll_full = 0;
622 }
623
624 661 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 205 times.
✓ Branch 1 taken 456 times.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 165 times.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
661 if (s->audio && (!ret || ret == AVERROR(EAGAIN)))
629 205 ret = trim_audio(ctx, pkt, head, !ret);
630
2/2
✓ Branch 0 taken 376 times.
✓ Branch 1 taken 80 times.
456 else if (!ret)
631 376 ret = trim_shift(ctx, pkt, head);
632
3/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✓ Branch 3 taken 28 times.
80 else if (ret == AVERROR(EAGAIN) && s->preroll_fifo)
633 52 ret = preroll_hold(ctx, pkt);
634
635 661 s->pkt_idx++;
636
2/2
✓ Branch 0 taken 231 times.
✓ Branch 1 taken 430 times.
661 if (ret < 0) {
637 231 av_packet_unref(pkt);
638 231 return ret;
639 }
640
641 430 s->out_idx++;
642
4/4
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 344 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 80 times.
430 if (s->preroll_fifo && av_fifo_can_read(s->preroll_fifo)) {
643 6 s->pending = av_packet_alloc();
644
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!s->pending) {
645 ✗ av_packet_unref(pkt);
646 ✗ return AVERROR(ENOMEM);
647 }
648 6 av_packet_move_ref(s->pending, pkt);
649 6 preroll_export(ctx, pkt);
650 }
651
652 430 packet_exported(ctx, pkt);
653
654 430 return 0;
655 }
656
657 /* Runtime state is derived from the options, so a flush restores it exactly. */
658 60 static void trim_reset(AVBSFContext *ctx)
659 {
660 60 TrimContext *s = ctx->priv_data;
661
662 60 bound_reset(&s->start_bound);
663 60 bound_reset(&s->end_bound);
664
665 60 s->pkt_idx = 0;
666 60 s->out_idx = 0;
667 60 s->nb_exported = 0;
668 60 s->skip_carry = 0;
669 60 s->skip_reason = 0;
670
671 60 preroll_clear(ctx);
672 60 av_packet_free(&s->pending);
673 60 s->preroll_full = 0;
674 60 }
675
676 6 static void trim_flush(AVBSFContext *ctx)
677 {
678 6 trim_report(ctx);
679 6 trim_reset(ctx);
680 6 }
681
682 54 static void trim_close(AVBSFContext *ctx)
683 {
684 54 TrimContext *s = ctx->priv_data;
685
686 54 trim_report(ctx);
687 54 preroll_clear(ctx);
688 54 av_packet_free(&s->pending);
689 54 av_fifo_freep2(&s->preroll_fifo);
690 54 }
691
692 54 static int trim_init(AVBSFContext *ctx)
693 {
694 54 TrimContext *s = ctx->priv_data;
695 int ret;
696
697
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
54 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 54 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 54 times.
54 if (ret < 0)
705 ✗ return ret;
706
707 54 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 54 times.
54 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 50 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
54 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 51 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 25 times.
54 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 6 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
54 if (s->preroll && s->start_bound.active &&
732
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 ctx->par_in->codec_type == AVMEDIA_TYPE_VIDEO) {
733 6 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 6 times.
6 if (!s->preroll_fifo)
736 ✗ return AVERROR(ENOMEM);
737 6 av_fifo_auto_grow_limit(s->preroll_fifo, s->preroll_size);
738 }
739
740 54 trim_reset(ctx);
741
742 54 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