| /* |
| * This file is part of FFmpeg. |
| * |
| * FFmpeg is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * FFmpeg is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with FFmpeg; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "libavutil/fifo.h" |
| #include "libavutil/intreadwrite.h" |
| #include "libavutil/opt.h" |
| #include "libavutil/timestamp.h" |
| |
| #include "libavcodec/bsf.h" |
| #include "libavcodec/bsf_internal.h" |
| |
| enum TrimType { |
| TRIM_PTS, |
| TRIM_DTS, |
| TRIM_PKT_INDEX, |
| TRIM_MSEC_PT, |
| TRIM_MSEC_DT, |
| TRIM_DUR_TS, |
| TRIM_DUR_T_MSEC, |
| }; |
| |
| /* The packet discriminant a bound is compared against; the indexes count consumed |
| * and exported packets. */ |
| enum TrimAxis { |
| AXIS_PTS, |
| AXIS_DTS, |
| AXIS_INDEX, |
| AXIS_OUT_INDEX, |
| }; |
| |
| /* An axis, a unit, and whether the value is relative; the duration types are |
| * the relative forms of pts and msec_pt. */ |
| static const struct { |
| enum TrimAxis axis; |
| int msec; |
| int relative; |
| } trim_types[] = { |
| [TRIM_PTS] = { AXIS_PTS, 0, 0 }, |
| [TRIM_DTS] = { AXIS_DTS, 0, 0 }, |
| [TRIM_PKT_INDEX] = { AXIS_INDEX, 0, 0 }, |
| [TRIM_MSEC_PT] = { AXIS_PTS, 1, 0 }, |
| [TRIM_MSEC_DT] = { AXIS_DTS, 1, 0 }, |
| [TRIM_DUR_TS] = { AXIS_PTS, 0, 1 }, |
| [TRIM_DUR_T_MSEC] = { AXIS_PTS, 1, 1 }, |
| }; |
| |
| typedef struct TrimBound { |
| /* Resolved from the options, immutable once init returns. */ |
| int active; ///< the option was set at all |
| enum TrimAxis axis; |
| int relative; ///< offset is measured from the reference packet |
| int64_t offset; ///< option value converted to axis units |
| |
| /* Rebuilt from the fields above by every reset. */ |
| int64_t value; ///< resolved bound, meaningful once pending is clear |
| int pending; ///< the reference packet has not been seen yet |
| } TrimBound; |
| |
| typedef struct TrimContext { |
| const AVClass *class; |
| |
| int64_t start; |
| int64_t end; |
| int start_type; |
| int end_type; |
| int start_rel; |
| int end_rel; |
| int trim_packets; |
| |
| TrimBound start_bound; |
| TrimBound end_bound; |
| |
| int audio; ///< trim through skip samples side data |
| int64_t pkt_idx; ///< packets consumed so far |
| int64_t out_idx; ///< packets exported so far |
| int64_t skip_carry; ///< samples of head skip left over by dropped packets |
| uint8_t skip_reason; ///< reason that head skip came with |
| |
| int preroll; |
| int preroll_size; |
| |
| AVFifo *preroll_fifo; ///< packets held since the last keyframe, NULL when off |
| AVPacket *pending; ///< in-range packet waiting for the preroll to drain |
| int preroll_full; ///< the group overflowed, so hold nothing until the next one |
| |
| int64_t nb_exported; ///< packets that left the filter |
| int64_t last_pts; ///< the last of them, kept for the closing summary |
| int64_t last_dts; |
| int last_key; |
| } TrimContext; |
| |
| /* Bounds mix option values with stream values; either can overflow. */ |
| static int add_checked(AVBSFContext *ctx, int64_t *acc, int64_t delta, |
| const char *what) |
| { |
| if ((delta > 0 && *acc > INT64_MAX - delta) || |
| (delta < 0 && *acc < INT64_MIN - delta)) { |
| av_log(ctx, AV_LOG_ERROR, "%s is not representable\n", what); |
| return AVERROR(ERANGE); |
| } |
| |
| *acc += delta; |
| |
| return 0; |
| } |
| |
| static int64_t packet_axis(const TrimContext *s, const AVPacket *pkt, |
| enum TrimAxis axis) |
| { |
| switch (axis) { |
| case AXIS_PTS: return pkt->pts; |
| case AXIS_DTS: return pkt->dts; |
| case AXIS_OUT_INDEX: return s->out_idx; |
| default: return s->pkt_idx; |
| } |
| } |
| |
| /* The dts stands in for the pts, being monotonic and never above it. */ |
| static enum TrimAxis monotonic_axis(enum TrimAxis axis) |
| { |
| return axis == AXIS_PTS ? AXIS_DTS : axis; |
| } |
| |
| /* An index counts whole packets, so only time places a bound inside one. */ |
| static int axis_is_time(enum TrimAxis axis) |
| { |
| return axis == AXIS_PTS || axis == AXIS_DTS; |
| } |
| |
| /* Nothing converted here is ever negative, so a negative result is the |
| * INT64_MIN av_rescale_q reports for one it cannot represent. */ |
| static int convert_checked(AVBSFContext *ctx, int64_t *value, AVRational from, |
| AVRational to, const char *what) |
| { |
| int64_t converted = av_rescale_q(*value, from, to); |
| |
| if (converted < 0) { |
| av_log(ctx, AV_LOG_ERROR, "%s is not representable\n", what); |
| return AVERROR(ERANGE); |
| } |
| |
| *value = converted; |
| |
| return 0; |
| } |
| |
| static int time_base_to_samples(AVBSFContext *ctx, int64_t *value, |
| const char *what) |
| { |
| return convert_checked(ctx, value, ctx->time_base_in, |
| (AVRational){ 1, ctx->par_in->sample_rate }, what); |
| } |
| |
| static int samples_to_time_base(AVBSFContext *ctx, int64_t *value, |
| const char *what) |
| { |
| return convert_checked(ctx, value, (AVRational){ 1, ctx->par_in->sample_rate }, |
| ctx->time_base_in, what); |
| } |
| |
| /* The samples a packet decodes to, zero where the codec does not give them. */ |
| static int64_t packet_samples(AVBSFContext *ctx, const AVPacket *pkt) |
| { |
| return FFMAX(av_get_audio_frame_duration2(ctx->par_in, pkt->size), 0); |
| } |
| |
| /* A skip reaching past a dropped packet only needs how much of itself that |
| * packet used up, which a duration still estimates closely enough. */ |
| static int64_t packet_skip_consumed(AVBSFContext *ctx, const AVPacket *pkt) |
| { |
| int64_t nb_samples = packet_samples(ctx, pkt); |
| |
| if (nb_samples > 0) |
| return nb_samples; |
| |
| nb_samples = av_rescale_q(pkt->duration, ctx->time_base_in, |
| (AVRational){ 1, ctx->par_in->sample_rate }); |
| |
| return FFMAX(nb_samples, 0); |
| } |
| |
| /* How far a packet reaches past its axis value. Audio reaches as far as the |
| * samples it decodes to, a duration also covering any gap after them; without |
| * them it reaches nowhere, so no bound can fall inside it. */ |
| static int packet_span(AVBSFContext *ctx, const AVPacket *pkt, |
| enum TrimAxis axis, int64_t *span) |
| { |
| TrimContext *s = ctx->priv_data; |
| |
| *span = 0; |
| |
| if (!axis_is_time(axis)) |
| return 0; |
| |
| if (s->audio) { |
| *span = packet_samples(ctx, pkt); |
| return samples_to_time_base(ctx, span, "packet span"); |
| } |
| |
| *span = FFMAX(pkt->duration, 0); |
| |
| return 0; |
| } |
| |
| static int bound_init(AVBSFContext *ctx, TrimBound *b, int active, |
| int64_t value, int type, int rel, const char *name) |
| { |
| b->active = active; |
| if (!active) |
| return 0; |
| |
| b->axis = trim_types[type].axis; |
| b->relative = rel || trim_types[type].relative; |
| |
| if (trim_types[type].relative && value < 0) { |
| av_log(ctx, AV_LOG_ERROR, "%s duration must not be negative\n", name); |
| return AVERROR(EINVAL); |
| } |
| |
| if (trim_types[type].msec) { |
| int64_t ts = av_rescale_q(value, (AVRational){ 1, 1000 }, |
| ctx->time_base_in); |
| if (ts == INT64_MIN) { |
| av_log(ctx, AV_LOG_ERROR, "%s of %"PRId64" ms is not representable " |
| "in the stream time base\n", name, value); |
| return AVERROR(ERANGE); |
| } |
| value = ts; |
| } |
| |
| b->offset = value; |
| |
| return 0; |
| } |
| |
| static void bound_reset(TrimBound *b) |
| { |
| b->value = b->offset; |
| b->pending = b->relative; |
| } |
| |
| static int bound_resolve(AVBSFContext *ctx, TrimBound *b, int64_t ref, |
| const char *name) |
| { |
| int ret = add_checked(ctx, &b->value, ref, name); |
| |
| if (ret < 0) |
| return ret; |
| |
| b->pending = 0; |
| |
| return 0; |
| } |
| |
| /* How much of the packet lies before the start bound, EAGAIN when all of it |
| * does. */ |
| static int trim_head(AVBSFContext *ctx, const AVPacket *pkt, int64_t *head) |
| { |
| TrimContext *s = ctx->priv_data; |
| int64_t v, span; |
| int ret; |
| |
| if (!s->start_bound.active) |
| return 0; |
| |
| /* A packet without the discriminant a bound uses cannot be placed. */ |
| v = packet_axis(s, pkt, s->start_bound.axis); |
| if (v == AV_NOPTS_VALUE) |
| return AVERROR(EAGAIN); |
| |
| /* The start is measured from the first packet of the stream. */ |
| if (s->start_bound.pending) { |
| ret = bound_resolve(ctx, &s->start_bound, v, "start bound"); |
| if (ret < 0) |
| return ret; |
| } |
| |
| if (v >= s->start_bound.value) |
| return 0; |
| |
| ret = packet_span(ctx, pkt, s->start_bound.axis, &span); |
| if (ret < 0) |
| return ret; |
| |
| if (!s->trim_packets || av_sat_add64(v, span) <= s->start_bound.value) |
| return AVERROR(EAGAIN); |
| *head = s->start_bound.value - v; |
| |
| return 0; |
| } |
| |
| /* How much of the packet lies past the end bound, measured from head, where it |
| * starts contributing. EAGAIN when none of it is inside, EOF once nothing later |
| * can be. */ |
| static int trim_tail(AVBSFContext *ctx, const AVPacket *pkt, int64_t head, |
| int64_t *tail) |
| { |
| TrimContext *s = ctx->priv_data; |
| int64_t v; |
| int ret; |
| |
| if (!s->end_bound.active) |
| return 0; |
| |
| /* The end is measured from the first exported packet, at the point it is |
| * trimmed to. */ |
| if (s->end_bound.pending) { |
| int64_t ref = packet_axis(s, pkt, s->end_bound.axis); |
| |
| if (ref == AV_NOPTS_VALUE) |
| return AVERROR(EAGAIN); |
| if (axis_is_time(s->end_bound.axis)) { |
| ret = add_checked(ctx, &ref, head, "end anchor"); |
| if (ret < 0) |
| return ret; |
| } |
| ret = bound_resolve(ctx, &s->end_bound, ref, "end bound"); |
| if (ret < 0) |
| return ret; |
| } |
| |
| /* Only a discriminant that cannot come back into range may end the stream. */ |
| v = packet_axis(s, pkt, monotonic_axis(s->end_bound.axis)); |
| if (v != AV_NOPTS_VALUE && v >= s->end_bound.value) |
| return AVERROR_EOF; |
| |
| v = packet_axis(s, pkt, s->end_bound.axis); |
| if (v == AV_NOPTS_VALUE) |
| return AVERROR(EAGAIN); |
| |
| /* Reordering can still bring in-range packets after this one. */ |
| if (v >= s->end_bound.value) |
| return AVERROR(EAGAIN); |
| |
| if (s->trim_packets) { |
| int64_t span, past; |
| |
| ret = packet_span(ctx, pkt, s->end_bound.axis, &span); |
| if (ret < 0) |
| return ret; |
| |
| past = av_sat_add64(v, span); |
| if (past > s->end_bound.value) |
| *tail = past - s->end_bound.value; |
| } |
| |
| return 0; |
| } |
| |
| /* Audio is trimmed in sample space through skip samples side data, leaving the |
| * timestamps untouched. */ |
| static int trim_audio(AVBSFContext *ctx, AVPacket *pkt, int64_t head, int keep) |
| { |
| TrimContext *s = ctx->priv_data; |
| int64_t nb_samples = packet_samples(ctx, pkt); |
| int64_t tail = 0, anchor, input_head; |
| int trimming = head != 0; |
| uint8_t head_reason = 0, tail_reason = 0, input_reason; |
| size_t size; |
| uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, |
| &size); |
| int ret; |
| |
| ret = time_base_to_samples(ctx, &head, "head trim"); |
| if (ret < 0) |
| return ret; |
| |
| /* A decoder replaces the skip it carries with any nonzero one a packet |
| * brings, whether larger or smaller, so resolve the incoming skip first. */ |
| input_head = s->skip_carry; |
| input_reason = s->skip_reason; |
| |
| if (side && size >= 8 && AV_RL32(side)) { |
| input_head = AV_RL32(side); |
| input_reason = size >= 10 ? AV_RL8(side + 8) : 0; |
| } |
| |
| /* That skip and this filter's trim are offsets from the same boundary, so |
| * the larger wins and brings its own reason; the trim has none. */ |
| if (input_head >= head) { |
| head = input_head; |
| head_reason = input_reason; |
| } |
| |
| /* Only a trim of this filter collapses a packet; one already covered by its |
| * own skip is passed through. */ |
| if (!keep || (trimming && nb_samples > 0 && head >= nb_samples)) |
| goto drop; |
| |
| /* A bound measured from this packet is anchored past its composed skip. */ |
| anchor = head; |
| ret = samples_to_time_base(ctx, &anchor, "end anchor"); |
| if (ret < 0) |
| return ret; |
| |
| ret = trim_tail(ctx, pkt, anchor, &tail); |
| if (ret < 0) { |
| if (ret != AVERROR(EAGAIN)) |
| return ret; |
| goto drop; |
| } |
| |
| trimming |= tail != 0; |
| ret = time_base_to_samples(ctx, &tail, "tail trim"); |
| if (ret < 0) |
| return ret; |
| if (side && size >= 8 && AV_RL32(side + 4) >= tail) { |
| tail = AV_RL32(side + 4); |
| tail_reason = size >= 10 ? AV_RL8(side + 9) : 0; |
| } |
| |
| if (trimming && nb_samples > 0 && head >= nb_samples - tail) |
| goto drop; |
| |
| /* The side data below carries any excess from here on. */ |
| s->skip_carry = 0; |
| s->skip_reason = 0; |
| |
| if (!head && !tail) |
| return 0; |
| |
| if (head > UINT32_MAX || tail > UINT32_MAX) { |
| av_log(ctx, AV_LOG_ERROR, "skip samples count is not representable\n"); |
| return AVERROR(ERANGE); |
| } |
| |
| if (!side || size < 10) { |
| side = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10); |
| if (!side) |
| return AVERROR(ENOMEM); |
| } |
| |
| AV_WL32(side, head); |
| AV_WL32(side + 4, tail); |
| AV_WL8 (side + 8, head_reason); |
| AV_WL8 (side + 9, tail_reason); |
| |
| return 0; |
| |
| drop: |
| /* A decoder subtracts the samples of every frame it discards and carries |
| * the rest, so do the same across dropped packets. */ |
| if (head > 0) { |
| int64_t consumed = packet_skip_consumed(ctx, pkt); |
| |
| if (consumed <= 0) { |
| av_log(ctx, AV_LOG_ERROR, "cannot carry a %"PRId64" sample skip past " |
| "a dropped packet of unknown sample count\n", head); |
| return AVERROR(EINVAL); |
| } |
| s->skip_carry = FFMAX(head - consumed, 0); |
| s->skip_reason = s->skip_carry ? head_reason : 0; |
| } |
| |
| return AVERROR(EAGAIN); |
| } |
| |
| /* Everything but audio is trimmed on the timeline itself, which no side data |
| * can express. */ |
| static int trim_shift(AVBSFContext *ctx, AVPacket *pkt, int64_t head) |
| { |
| int64_t tail = 0; |
| int ret; |
| |
| ret = trim_tail(ctx, pkt, head, &tail); |
| if (ret < 0) |
| return ret; |
| |
| if (!head && !tail) |
| return 0; |
| |
| /* Bounds on different axes can select disjoint parts of one packet, and |
| * each trim is below the duration, so the subtraction cannot overflow. */ |
| if (head >= pkt->duration - tail) |
| return AVERROR(EAGAIN); |
| |
| if (pkt->pts != AV_NOPTS_VALUE) { |
| ret = add_checked(ctx, &pkt->pts, head, "packet pts"); |
| if (ret < 0) |
| return ret; |
| } |
| if (pkt->dts != AV_NOPTS_VALUE) { |
| ret = add_checked(ctx, &pkt->dts, head, "packet dts"); |
| if (ret < 0) |
| return ret; |
| } |
| pkt->duration -= head + tail; |
| |
| return 0; |
| } |
| |
| static void preroll_clear(AVBSFContext *ctx) |
| { |
| TrimContext *s = ctx->priv_data; |
| AVPacket *held; |
| |
| while (s->preroll_fifo && av_fifo_read(s->preroll_fifo, &held, 1) >= 0) |
| av_packet_free(&held); |
| } |
| |
| /* A held packet is decoded for the frames after it to reference and dropped |
| * before output, so the exported stream still begins at the bound. */ |
| static void preroll_export(AVBSFContext *ctx, AVPacket *pkt) |
| { |
| TrimContext *s = ctx->priv_data; |
| AVPacket *held; |
| |
| if (av_fifo_read(s->preroll_fifo, &held, 1) >= 0) { |
| av_packet_move_ref(pkt, held); |
| av_packet_free(&held); |
| pkt->flags |= AV_PKT_FLAG_DISCARD; |
| return; |
| } |
| |
| av_packet_move_ref(pkt, s->pending); |
| av_packet_free(&s->pending); |
| } |
| |
| static int preroll_hold(AVBSFContext *ctx, AVPacket *pkt) |
| { |
| TrimContext *s = ctx->priv_data; |
| AVPacket *held; |
| int ret; |
| |
| if (s->preroll_full) |
| return AVERROR(EAGAIN); |
| |
| held = av_packet_alloc(); |
| if (!held) |
| return AVERROR(ENOMEM); |
| |
| av_packet_move_ref(held, pkt); |
| ret = av_fifo_write(s->preroll_fifo, &held, 1); |
| if (ret < 0) { |
| av_packet_move_ref(pkt, held); |
| av_packet_free(&held); |
| if (ret != AVERROR(ENOSPC)) |
| return ret; |
| |
| /* A group too long to hold is dropped whole rather than exported as a |
| * fragment no decoder can start from. */ |
| av_log(ctx, AV_LOG_WARNING, "preroll exceeds %d packets, the first " |
| "packets in range will not be decodable\n", s->preroll_size); |
| preroll_clear(ctx); |
| s->preroll_full = 1; |
| } |
| |
| return AVERROR(EAGAIN); |
| } |
| |
| static void log_exported_packet(AVBSFContext *ctx, const char *which, |
| int64_t idx, int64_t pts, int64_t dts, int key) |
| { |
| av_log(ctx, AV_LOG_VERBOSE, "%s exported packet: output index %"PRId64", " |
| "pts %s, dts %s, keyframe %d\n", which, idx, av_ts2str(pts), |
| av_ts2str(dts), key); |
| } |
| |
| static void packet_exported(AVBSFContext *ctx, const AVPacket *pkt) |
| { |
| TrimContext *s = ctx->priv_data; |
| |
| s->last_pts = pkt->pts; |
| s->last_dts = pkt->dts; |
| s->last_key = !!(pkt->flags & AV_PKT_FLAG_KEY); |
| |
| if (!s->nb_exported++) |
| log_exported_packet(ctx, "first", 0, s->last_pts, s->last_dts, |
| s->last_key); |
| } |
| |
| /* Only the flush or close ending the stream can tell which packet was last. */ |
| static void trim_report(AVBSFContext *ctx) |
| { |
| TrimContext *s = ctx->priv_data; |
| |
| if (s->nb_exported) |
| log_exported_packet(ctx, "last", s->nb_exported - 1, s->last_pts, |
| s->last_dts, s->last_key); |
| else if (s->pkt_idx) |
| av_log(ctx, AV_LOG_WARNING, "no packet was in range, the output is " |
| "empty\n"); |
| } |
| |
| static int trim_filter(AVBSFContext *ctx, AVPacket *pkt) |
| { |
| TrimContext *s = ctx->priv_data; |
| int64_t head = 0; |
| int ret; |
| |
| /* The preroll an in-range packet needs is exported ahead of it. */ |
| if (s->pending) { |
| preroll_export(ctx, pkt); |
| packet_exported(ctx, pkt); |
| return 0; |
| } |
| |
| ret = ff_bsf_get_packet_ref(ctx, pkt); |
| if (ret < 0) |
| return ret; |
| |
| /* Only the group the start bound falls in is worth keeping, so the window |
| * starts over at every keyframe. */ |
| if (s->preroll_fifo && pkt->flags & AV_PKT_FLAG_KEY) { |
| preroll_clear(ctx); |
| s->preroll_full = 0; |
| } |
| |
| ret = trim_head(ctx, pkt, &head); |
| |
| /* A packet before the start still composes with the skip it carries, so the |
| * audio path runs on it too and is told the verdict instead. */ |
| if (s->audio && (!ret || ret == AVERROR(EAGAIN))) |
| ret = trim_audio(ctx, pkt, head, !ret); |
| else if (!ret) |
| ret = trim_shift(ctx, pkt, head); |
| else if (ret == AVERROR(EAGAIN) && s->preroll_fifo) |
| ret = preroll_hold(ctx, pkt); |
| |
| s->pkt_idx++; |
| if (ret < 0) { |
| av_packet_unref(pkt); |
| return ret; |
| } |
| |
| s->out_idx++; |
| if (s->preroll_fifo && av_fifo_can_read(s->preroll_fifo)) { |
| s->pending = av_packet_alloc(); |
| if (!s->pending) { |
| av_packet_unref(pkt); |
| return AVERROR(ENOMEM); |
| } |
| av_packet_move_ref(s->pending, pkt); |
| preroll_export(ctx, pkt); |
| } |
| |
| packet_exported(ctx, pkt); |
| |
| return 0; |
| } |
| |
| /* Runtime state is derived from the options, so a flush restores it exactly. */ |
| static void trim_reset(AVBSFContext *ctx) |
| { |
| TrimContext *s = ctx->priv_data; |
| |
| bound_reset(&s->start_bound); |
| bound_reset(&s->end_bound); |
| |
| s->pkt_idx = 0; |
| s->out_idx = 0; |
| s->nb_exported = 0; |
| s->skip_carry = 0; |
| s->skip_reason = 0; |
| |
| preroll_clear(ctx); |
| av_packet_free(&s->pending); |
| s->preroll_full = 0; |
| } |
| |
| static void trim_flush(AVBSFContext *ctx) |
| { |
| trim_report(ctx); |
| trim_reset(ctx); |
| } |
| |
| static void trim_close(AVBSFContext *ctx) |
| { |
| TrimContext *s = ctx->priv_data; |
| |
| trim_report(ctx); |
| preroll_clear(ctx); |
| av_packet_free(&s->pending); |
| av_fifo_freep2(&s->preroll_fifo); |
| } |
| |
| static int trim_init(AVBSFContext *ctx) |
| { |
| TrimContext *s = ctx->priv_data; |
| int ret; |
| |
| if (s->start == INT64_MIN && s->end == INT64_MAX) { |
| av_log(ctx, AV_LOG_ERROR, "At least one of start or end must be set\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| ret = bound_init(ctx, &s->start_bound, s->start != INT64_MIN, s->start, |
| s->start_type, s->start_rel, "start"); |
| if (ret < 0) |
| return ret; |
| |
| ret = bound_init(ctx, &s->end_bound, s->end != INT64_MAX, s->end, |
| s->end_type, s->end_rel, "end"); |
| if (ret < 0) |
| return ret; |
| |
| /* A packet count measured from the first exported packet is a promise about |
| * the exported stream, so it is counted there. */ |
| if (s->end_bound.axis == AXIS_INDEX && s->end_bound.relative) |
| s->end_bound.axis = AXIS_OUT_INDEX; |
| |
| /* Shifting the timeline leaves the samples where they were, so audio with |
| * no sample space to be trimmed in cannot be trimmed at all. */ |
| if (s->trim_packets && ctx->par_in->codec_type == AVMEDIA_TYPE_AUDIO) { |
| if (ctx->par_in->sample_rate <= 0 || ctx->time_base_in.num <= 0 || |
| ctx->time_base_in.den <= 0) { |
| av_log(ctx, AV_LOG_ERROR, "audio is trimmed through skip samples " |
| "side data, which needs a sample rate and a time base\n"); |
| return AVERROR(EINVAL); |
| } |
| s->audio = 1; |
| } |
| |
| /* The window is bounded by a keyframe, which only video has. Audio needs a |
| * preroll counted in samples, which is not the one this would give. */ |
| if (s->preroll && s->start_bound.active && |
| ctx->par_in->codec_type == AVMEDIA_TYPE_VIDEO) { |
| s->preroll_fifo = av_fifo_alloc2(1, sizeof(AVPacket *), |
| AV_FIFO_FLAG_AUTO_GROW); |
| if (!s->preroll_fifo) |
| return AVERROR(ENOMEM); |
| av_fifo_auto_grow_limit(s->preroll_fifo, s->preroll_size); |
| } |
| |
| trim_reset(ctx); |
| |
| return 0; |
| } |
| |
| #define OFFSET(x) offsetof(TrimContext, x) |
| #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM) |
| static const AVOption trim_options[] = { |
| { "start", "time or index marking the start of the accepted range", OFFSET(start), |
| AV_OPT_TYPE_INT64, { .i64 = INT64_MIN }, INT64_MIN, INT64_MAX, FLAGS }, |
| { "start_type", "how to interpret start", OFFSET(start_type), |
| AV_OPT_TYPE_INT, { .i64 = TRIM_PTS }, 0, TRIM_MSEC_DT, FLAGS, .unit = "start_type" }, |
| { "pts", "stream time base pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PTS }, 0, 0, FLAGS, .unit = "start_type" }, |
| { "dts", "stream time base dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DTS }, 0, 0, FLAGS, .unit = "start_type" }, |
| { "pkt_index", "packet index", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PKT_INDEX }, 0, 0, FLAGS, .unit = "start_type" }, |
| { "msec_pt", "milliseconds, matched against pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_PT }, 0, 0, FLAGS, .unit = "start_type" }, |
| { "msec_dt", "milliseconds, matched against dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_DT }, 0, 0, FLAGS, .unit = "start_type" }, |
| { "start_rel", "interpret start relative to the first packet of the stream", OFFSET(start_rel), |
| AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, |
| { "end", "time or index marking the end of the accepted range", OFFSET(end), |
| AV_OPT_TYPE_INT64, { .i64 = INT64_MAX }, INT64_MIN, INT64_MAX, FLAGS }, |
| { "end_type", "how to interpret end", OFFSET(end_type), |
| AV_OPT_TYPE_INT, { .i64 = TRIM_PTS }, 0, TRIM_DUR_T_MSEC, FLAGS, .unit = "end_type" }, |
| { "pts", "stream time base pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PTS }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "dts", "stream time base dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DTS }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "pkt_index", "packet index", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_PKT_INDEX }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "msec_pt", "milliseconds, matched against pts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_PT }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "msec_dt", "milliseconds, matched against dts", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_MSEC_DT }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "dur_ts", "duration in stream time base", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DUR_TS }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "dur_t_msec", "duration in milliseconds", 0, AV_OPT_TYPE_CONST, { .i64 = TRIM_DUR_T_MSEC }, 0, 0, FLAGS, .unit = "end_type" }, |
| { "end_rel", "interpret end relative to the first exported packet", OFFSET(end_rel), |
| AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, |
| { "trim_packets", "trim packets straddling a boundary instead of exporting them " |
| "untouched or dropping them", OFFSET(trim_packets), |
| AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS }, |
| { "preroll", "export the packets the first packet in range needs to be decodable, " |
| "flagged for the decoder to drop them after decoding", OFFSET(preroll), |
| AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, |
| { "preroll_size", "maximum number of packets held for preroll", OFFSET(preroll_size), |
| AV_OPT_TYPE_INT, { .i64 = 300 }, 1, INT_MAX, FLAGS }, |
| { NULL }, |
| }; |
| |
| static const AVClass trim_class = { |
| .class_name = "trim", |
| .item_name = av_default_item_name, |
| .option = trim_options, |
| .version = LIBAVUTIL_VERSION_INT, |
| }; |
| |
| const FFBitStreamFilter ff_trim_bsf = { |
| .p.name = "trim", |
| .p.priv_class = &trim_class, |
| .priv_data_size = sizeof(TrimContext), |
| .init = trim_init, |
| .close = trim_close, |
| .flush = trim_flush, |
| .filter = trim_filter, |
| }; |