| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2012 Nicolas George | ||
| 3 | * | ||
| 4 | * This file is part of FFmpeg. | ||
| 5 | * | ||
| 6 | * FFmpeg is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU Lesser General Public | ||
| 8 | * License as published by the Free Software Foundation; either | ||
| 9 | * version 2.1 of the License, or (at your option) any later version. | ||
| 10 | * | ||
| 11 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
| 14 | * See the GNU Lesser General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU Lesser General Public License | ||
| 17 | * along with FFmpeg; if not, write to the Free Software Foundation, Inc., | ||
| 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | /** | ||
| 22 | * @file | ||
| 23 | * concat audio-video filter | ||
| 24 | */ | ||
| 25 | |||
| 26 | #include "libavutil/avstring.h" | ||
| 27 | #include "libavutil/channel_layout.h" | ||
| 28 | #include "libavutil/mem.h" | ||
| 29 | #include "libavutil/opt.h" | ||
| 30 | #include "avfilter.h" | ||
| 31 | #include "filters.h" | ||
| 32 | #include "formats.h" | ||
| 33 | #include "video.h" | ||
| 34 | #include "audio.h" | ||
| 35 | |||
| 36 | #define TYPE_ALL 2 | ||
| 37 | |||
| 38 | typedef struct ConcatContext { | ||
| 39 | const AVClass *class; | ||
| 40 | unsigned nb_streams[TYPE_ALL]; /**< number of out streams of each type */ | ||
| 41 | unsigned nb_segments; | ||
| 42 | unsigned cur_idx; /**< index of the first input of current segment */ | ||
| 43 | int64_t delta_ts; /**< timestamp to add to produce output timestamps */ | ||
| 44 | unsigned nb_in_active; /**< number of active inputs in current segment */ | ||
| 45 | unsigned unsafe; | ||
| 46 | struct concat_in { | ||
| 47 | int64_t pts; | ||
| 48 | int64_t nb_frames; | ||
| 49 | unsigned eof; | ||
| 50 | } *in; | ||
| 51 | } ConcatContext; | ||
| 52 | |||
| 53 | #define OFFSET(x) offsetof(ConcatContext, x) | ||
| 54 | #define A AV_OPT_FLAG_AUDIO_PARAM | ||
| 55 | #define F AV_OPT_FLAG_FILTERING_PARAM | ||
| 56 | #define V AV_OPT_FLAG_VIDEO_PARAM | ||
| 57 | |||
| 58 | static const AVOption concat_options[] = { | ||
| 59 | { "n", "specify the number of segments", OFFSET(nb_segments), | ||
| 60 | AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, V|A|F}, | ||
| 61 | { "v", "specify the number of video streams", | ||
| 62 | OFFSET(nb_streams[AVMEDIA_TYPE_VIDEO]), | ||
| 63 | AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, V|F }, | ||
| 64 | { "a", "specify the number of audio streams", | ||
| 65 | OFFSET(nb_streams[AVMEDIA_TYPE_AUDIO]), | ||
| 66 | AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A|F}, | ||
| 67 | { "unsafe", "enable unsafe mode", | ||
| 68 | OFFSET(unsafe), | ||
| 69 | AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, V|A|F}, | ||
| 70 | { NULL } | ||
| 71 | }; | ||
| 72 | |||
| 73 | AVFILTER_DEFINE_CLASS(concat); | ||
| 74 | |||
| 75 | 4 | static int query_formats(const AVFilterContext *ctx, | |
| 76 | AVFilterFormatsConfig **cfg_in, | ||
| 77 | AVFilterFormatsConfig **cfg_out) | ||
| 78 | { | ||
| 79 | 4 | const ConcatContext *cat = ctx->priv; | |
| 80 | 4 | unsigned type, nb_str, idx0 = 0, idx, str, seg; | |
| 81 | 4 | AVFilterFormats *formats, *rates = NULL; | |
| 82 | 4 | AVFilterChannelLayouts *layouts = NULL; | |
| 83 | int ret; | ||
| 84 | |||
| 85 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
12 | for (type = 0; type < TYPE_ALL; type++) { |
| 86 | 8 | nb_str = cat->nb_streams[type]; | |
| 87 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
|
14 | for (str = 0; str < nb_str; str++) { |
| 88 | 6 | idx = idx0; | |
| 89 | |||
| 90 | /* Set the output formats */ | ||
| 91 | 6 | formats = ff_all_formats(type); | |
| 92 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if ((ret = ff_formats_ref(formats, &cfg_out[idx]->formats)) < 0) |
| 93 | ✗ | return ret; | |
| 94 | |||
| 95 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
|
6 | if (type == AVMEDIA_TYPE_AUDIO) { |
| 96 | 2 | rates = ff_all_samplerates(); | |
| 97 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if ((ret = ff_formats_ref(rates, &cfg_out[idx]->samplerates)) < 0) |
| 98 | ✗ | return ret; | |
| 99 | 2 | layouts = ff_all_channel_layouts(); | |
| 100 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if ((ret = ff_channel_layouts_ref(layouts, &cfg_out[idx]->channel_layouts)) < 0) |
| 101 | ✗ | return ret; | |
| 102 | } | ||
| 103 | |||
| 104 | /* Set the same formats for each corresponding input */ | ||
| 105 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 6 times.
|
22 | for (seg = 0; seg < cat->nb_segments; seg++) { |
| 106 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
|
16 | if ((ret = ff_formats_ref(formats, &cfg_in[idx]->formats)) < 0) |
| 107 | ✗ | return ret; | |
| 108 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | if (type == AVMEDIA_TYPE_AUDIO) { |
| 109 |
2/4✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
|
12 | if ((ret = ff_formats_ref(rates, &cfg_in[idx]->samplerates)) < 0 || |
| 110 | 6 | (ret = ff_channel_layouts_ref(layouts, &cfg_in[idx]->channel_layouts)) < 0) | |
| 111 | ✗ | return ret; | |
| 112 | } | ||
| 113 | 16 | idx += ctx->nb_outputs; | |
| 114 | } | ||
| 115 | |||
| 116 | 6 | idx0++; | |
| 117 | } | ||
| 118 | } | ||
| 119 | 4 | return 0; | |
| 120 | } | ||
| 121 | |||
| 122 | 6 | static int config_output(AVFilterLink *outlink) | |
| 123 | { | ||
| 124 | 6 | FilterLink *outl = ff_filter_link(outlink); | |
| 125 | 6 | AVFilterContext *ctx = outlink->src; | |
| 126 | 6 | ConcatContext *cat = ctx->priv; | |
| 127 | 6 | unsigned out_no = FF_OUTLINK_IDX(outlink); | |
| 128 | 6 | unsigned in_no = out_no, seg; | |
| 129 | 6 | AVFilterLink *inlink = ctx->inputs[in_no]; | |
| 130 | 6 | FilterLink *inl = ff_filter_link(inlink); | |
| 131 | |||
| 132 | /* enhancement: find a common one */ | ||
| 133 | 6 | outlink->time_base = AV_TIME_BASE_Q; | |
| 134 | 6 | outlink->w = inlink->w; | |
| 135 | 6 | outlink->h = inlink->h; | |
| 136 | 6 | outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; | |
| 137 | 6 | outlink->format = inlink->format; | |
| 138 | 6 | outl->frame_rate = inl->frame_rate; | |
| 139 | |||
| 140 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
|
14 | for (seg = 1; seg < cat->nb_segments; seg++) { |
| 141 | 9 | inlink = ctx->inputs[in_no + seg * ctx->nb_outputs]; | |
| 142 | 9 | inl = ff_filter_link(inlink); | |
| 143 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
|
9 | if (outl->frame_rate.num != inl->frame_rate.num || |
| 144 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | outl->frame_rate.den != inl->frame_rate.den) { |
| 145 | 1 | av_log(ctx, AV_LOG_VERBOSE, | |
| 146 | "Video inputs have different frame rates, output will be VFR\n"); | ||
| 147 | 1 | outl->frame_rate = av_make_q(1, 0); | |
| 148 | 1 | break; | |
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | for (seg = 1; seg < cat->nb_segments; seg++) { |
| 153 | 10 | inlink = ctx->inputs[in_no + seg * ctx->nb_outputs]; | |
| 154 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | if (!outlink->sample_aspect_ratio.num) |
| 155 | 5 | outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; | |
| 156 | /* possible enhancement: unsafe mode, do not check */ | ||
| 157 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
|
10 | if (outlink->w != inlink->w || |
| 158 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | outlink->h != inlink->h || |
| 159 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num && |
| 160 | ✗ | inlink->sample_aspect_ratio.num || | |
| 161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) { |
| 162 | 1 | av_log(ctx, AV_LOG_ERROR, "Input link %s parameters " | |
| 163 | "(size %dx%d, SAR %d:%d) do not match the corresponding " | ||
| 164 | "output link %s parameters (%dx%d, SAR %d:%d)\n", | ||
| 165 | 1 | ctx->input_pads[in_no].name, inlink->w, inlink->h, | |
| 166 | inlink->sample_aspect_ratio.num, | ||
| 167 | inlink->sample_aspect_ratio.den, | ||
| 168 | 1 | ctx->input_pads[out_no].name, outlink->w, outlink->h, | |
| 169 | outlink->sample_aspect_ratio.num, | ||
| 170 | outlink->sample_aspect_ratio.den); | ||
| 171 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!cat->unsafe) |
| 172 | ✗ | return AVERROR(EINVAL); | |
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | 6 | return 0; | |
| 177 | } | ||
| 178 | |||
| 179 | 463 | static int push_frame(AVFilterContext *ctx, unsigned in_no, AVFrame *buf) | |
| 180 | { | ||
| 181 | 463 | ConcatContext *cat = ctx->priv; | |
| 182 | 463 | unsigned out_no = in_no % ctx->nb_outputs; | |
| 183 | 463 | AVFilterLink * inlink = ctx-> inputs[ in_no]; | |
| 184 | 463 | AVFilterLink *outlink = ctx->outputs[out_no]; | |
| 185 | 463 | struct concat_in *in = &cat->in[in_no]; | |
| 186 | |||
| 187 | 463 | buf->pts = av_rescale_q(buf->pts, inlink->time_base, outlink->time_base); | |
| 188 | 463 | buf->duration = av_rescale_q(buf->duration, inlink->time_base, outlink->time_base); | |
| 189 | 463 | in->pts = buf->pts; | |
| 190 | 463 | in->nb_frames++; | |
| 191 | /* add duration to input PTS */ | ||
| 192 |
2/2✓ Branch 0 taken 350 times.
✓ Branch 1 taken 113 times.
|
463 | if (inlink->sample_rate) |
| 193 | /* use number of audio samples */ | ||
| 194 | 350 | in->pts += av_rescale_q(buf->nb_samples, | |
| 195 | av_make_q(1, inlink->sample_rate), | ||
| 196 | outlink->time_base); | ||
| 197 |
2/2✓ Branch 0 taken 103 times.
✓ Branch 1 taken 10 times.
|
113 | else if (in->nb_frames >= 2) |
| 198 | /* use mean duration */ | ||
| 199 | 103 | in->pts = av_rescale(in->pts, in->nb_frames, in->nb_frames - 1); | |
| 200 | |||
| 201 | 463 | buf->pts += cat->delta_ts; | |
| 202 | 463 | return ff_filter_frame(outlink, buf); | |
| 203 | } | ||
| 204 | |||
| 205 | 63 | static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) | |
| 206 | { | ||
| 207 | 63 | AVFilterContext *ctx = inlink->dst; | |
| 208 | 63 | unsigned in_no = FF_INLINK_IDX(inlink); | |
| 209 | 63 | AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; | |
| 210 | |||
| 211 | 63 | return ff_get_video_buffer(outlink, w, h); | |
| 212 | } | ||
| 213 | |||
| 214 | 350 | static AVFrame *get_audio_buffer(AVFilterLink *inlink, int nb_samples) | |
| 215 | { | ||
| 216 | 350 | AVFilterContext *ctx = inlink->dst; | |
| 217 | 350 | unsigned in_no = FF_INLINK_IDX(inlink); | |
| 218 | 350 | AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; | |
| 219 | |||
| 220 | 350 | return ff_get_audio_buffer(outlink, nb_samples); | |
| 221 | } | ||
| 222 | |||
| 223 | 16 | static void close_input(AVFilterContext *ctx, unsigned in_no) | |
| 224 | { | ||
| 225 | 16 | ConcatContext *cat = ctx->priv; | |
| 226 | |||
| 227 | 16 | cat->in[in_no].eof = 1; | |
| 228 | 16 | cat->nb_in_active--; | |
| 229 | 16 | av_log(ctx, AV_LOG_VERBOSE, "EOF on %s, %d streams left in segment.\n", | |
| 230 | 16 | ctx->input_pads[in_no].name, cat->nb_in_active); | |
| 231 | 16 | } | |
| 232 | |||
| 233 | 10 | static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta) | |
| 234 | { | ||
| 235 | 10 | ConcatContext *cat = ctx->priv; | |
| 236 | 10 | unsigned i = cat->cur_idx; | |
| 237 | 10 | unsigned imax = i + ctx->nb_outputs; | |
| 238 | int64_t pts; | ||
| 239 | |||
| 240 | 10 | pts = cat->in[i++].pts; | |
| 241 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | for (; i < imax; i++) |
| 242 | 6 | pts = FFMAX(pts, cat->in[i].pts); | |
| 243 | 10 | cat->delta_ts += pts; | |
| 244 | 10 | *seg_delta = pts; | |
| 245 | 10 | } | |
| 246 | |||
| 247 | 4 | static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, | |
| 248 | int64_t seg_delta) | ||
| 249 | { | ||
| 250 | 4 | ConcatContext *cat = ctx->priv; | |
| 251 | 4 | AVFilterLink *outlink = ctx->outputs[out_no]; | |
| 252 | 4 | int64_t base_pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; | |
| 253 | 4 | int64_t nb_samples, sent = 0; | |
| 254 | int frame_nb_samples, ret; | ||
| 255 | 4 | AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; | |
| 256 | AVFrame *buf; | ||
| 257 | |||
| 258 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!rate_tb.den) |
| 259 | ✗ | return AVERROR_BUG; | |
| 260 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (cat->in[in_no].pts < INT64_MIN + seg_delta) |
| 261 | ✗ | return AVERROR_INVALIDDATA; | |
| 262 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (seg_delta < cat->in[in_no].pts) |
| 263 | ✗ | return AVERROR_INVALIDDATA; | |
| 264 | 4 | nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, | |
| 265 | outlink->time_base, rate_tb); | ||
| 266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | frame_nb_samples = FFMAX(9600, rate_tb.den / 5); /* arbitrary */ |
| 267 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
|
14 | while (nb_samples) { |
| 268 | 10 | frame_nb_samples = FFMIN(frame_nb_samples, nb_samples); | |
| 269 | 10 | buf = ff_get_audio_buffer(outlink, frame_nb_samples); | |
| 270 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (!buf) |
| 271 | ✗ | return AVERROR(ENOMEM); | |
| 272 | 10 | av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, | |
| 273 | 10 | outlink->ch_layout.nb_channels, outlink->format); | |
| 274 | 10 | buf->pts = base_pts + av_rescale_q(sent, rate_tb, outlink->time_base); | |
| 275 | 10 | ret = ff_filter_frame(outlink, buf); | |
| 276 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
| 277 | ✗ | return ret; | |
| 278 | 10 | sent += frame_nb_samples; | |
| 279 | 10 | nb_samples -= frame_nb_samples; | |
| 280 | } | ||
| 281 | 4 | return 0; | |
| 282 | } | ||
| 283 | |||
| 284 | 10 | static int flush_segment(AVFilterContext *ctx) | |
| 285 | { | ||
| 286 | int ret; | ||
| 287 | 10 | ConcatContext *cat = ctx->priv; | |
| 288 | unsigned str, str_max; | ||
| 289 | int64_t seg_delta; | ||
| 290 | |||
| 291 | 10 | find_next_delta_ts(ctx, &seg_delta); | |
| 292 | 10 | cat->cur_idx += ctx->nb_outputs; | |
| 293 | 10 | cat->nb_in_active = ctx->nb_outputs; | |
| 294 | 10 | av_log(ctx, AV_LOG_VERBOSE, "Segment finished at pts=%"PRId64"\n", | |
| 295 | cat->delta_ts); | ||
| 296 | |||
| 297 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
|
10 | if (cat->cur_idx < ctx->nb_inputs) { |
| 298 | /* pad audio streams with silence */ | ||
| 299 | 6 | str = cat->nb_streams[AVMEDIA_TYPE_VIDEO]; | |
| 300 | 6 | str_max = str + cat->nb_streams[AVMEDIA_TYPE_AUDIO]; | |
| 301 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | for (; str < str_max; str++) { |
| 302 | 4 | ret = send_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str, | |
| 303 | seg_delta); | ||
| 304 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
| 305 | ✗ | return ret; | |
| 306 | } | ||
| 307 | } | ||
| 308 | 10 | return 0; | |
| 309 | } | ||
| 310 | |||
| 311 | 7 | static av_cold int init(AVFilterContext *ctx) | |
| 312 | { | ||
| 313 | 7 | ConcatContext *cat = ctx->priv; | |
| 314 | unsigned seg, type, str; | ||
| 315 | int ret; | ||
| 316 | |||
| 317 | /* create input pads */ | ||
| 318 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
|
25 | for (seg = 0; seg < cat->nb_segments; seg++) { |
| 319 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 18 times.
|
54 | for (type = 0; type < TYPE_ALL; type++) { |
| 320 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 36 times.
|
66 | for (str = 0; str < cat->nb_streams[type]; str++) { |
| 321 | 30 | AVFilterPad pad = { | |
| 322 | .type = type, | ||
| 323 | }; | ||
| 324 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 12 times.
|
30 | if (type == AVMEDIA_TYPE_VIDEO) |
| 325 | 18 | pad.get_buffer.video = get_video_buffer; | |
| 326 | else | ||
| 327 | 12 | pad.get_buffer.audio = get_audio_buffer; | |
| 328 | 30 | pad.name = av_asprintf("in%d:%c%d", seg, "va"[type], str); | |
| 329 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
|
30 | if ((ret = ff_append_inpad_free_name(ctx, &pad)) < 0) |
| 330 | ✗ | return ret; | |
| 331 | } | ||
| 332 | } | ||
| 333 | } | ||
| 334 | /* create output pads */ | ||
| 335 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
|
21 | for (type = 0; type < TYPE_ALL; type++) { |
| 336 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 14 times.
|
25 | for (str = 0; str < cat->nb_streams[type]; str++) { |
| 337 | 11 | AVFilterPad pad = { | |
| 338 | .type = type, | ||
| 339 | .config_props = config_output, | ||
| 340 | }; | ||
| 341 | 11 | pad.name = av_asprintf("out:%c%d", "va"[type], str); | |
| 342 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | if ((ret = ff_append_outpad_free_name(ctx, &pad)) < 0) |
| 343 | ✗ | return ret; | |
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | 7 | cat->in = av_calloc(ctx->nb_inputs, sizeof(*cat->in)); | |
| 348 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (!cat->in) |
| 349 | ✗ | return AVERROR(ENOMEM); | |
| 350 | 7 | cat->nb_in_active = ctx->nb_outputs; | |
| 351 | 7 | return 0; | |
| 352 | } | ||
| 353 | |||
| 354 | 7 | static av_cold void uninit(AVFilterContext *ctx) | |
| 355 | { | ||
| 356 | 7 | ConcatContext *cat = ctx->priv; | |
| 357 | |||
| 358 | 7 | av_freep(&cat->in); | |
| 359 | 7 | } | |
| 360 | |||
| 361 | 1005 | static int activate(AVFilterContext *ctx) | |
| 362 | { | ||
| 363 | 1005 | ConcatContext *cat = ctx->priv; | |
| 364 | AVFrame *frame; | ||
| 365 | unsigned i, j; | ||
| 366 | int ret, status; | ||
| 367 | int64_t pts; | ||
| 368 | |||
| 369 | /* Forward status back */ | ||
| 370 |
2/2✓ Branch 0 taken 1835 times.
✓ Branch 1 taken 1005 times.
|
2840 | for (i = 0; i < ctx->nb_outputs; i++) { |
| 371 | 1835 | status = ff_outlink_get_status(ctx->outputs[i]); | |
| 372 |
2/2✓ Branch 0 taken 1804 times.
✓ Branch 1 taken 31 times.
|
1835 | if (!status) |
| 373 | 1804 | continue; | |
| 374 |
2/2✓ Branch 0 taken 92 times.
✓ Branch 1 taken 31 times.
|
123 | for (j = i; j < ctx->nb_inputs; j += ctx->nb_outputs) { |
| 375 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
|
92 | if (!cat->in[j].eof) { |
| 376 | ✗ | cat->in[j].eof = 1; | |
| 377 | ✗ | ff_inlink_set_status(ctx->inputs[j], status); | |
| 378 | ✗ | return 0; | |
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | } | ||
| 383 | |||
| 384 | /* Forward available frames */ | ||
| 385 |
2/2✓ Branch 0 taken 1004 times.
✓ Branch 1 taken 1 times.
|
1005 | if (cat->cur_idx < ctx->nb_inputs) { |
| 386 |
2/2✓ Branch 0 taken 1781 times.
✓ Branch 1 taken 541 times.
|
2322 | for (i = 0; i < ctx->nb_outputs; i++) { |
| 387 | 1781 | ret = ff_inlink_consume_frame(ctx->inputs[cat->cur_idx + i], &frame); | |
| 388 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1781 times.
|
1781 | if (ret < 0) |
| 389 | ✗ | return ret; | |
| 390 |
2/2✓ Branch 0 taken 463 times.
✓ Branch 1 taken 1318 times.
|
1781 | if (ret) { |
| 391 | 463 | ff_filter_set_ready(ctx, 10); | |
| 392 | 463 | return push_frame(ctx, cat->cur_idx + i, frame); | |
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | /* Forward status change */ | ||
| 398 |
2/2✓ Branch 0 taken 541 times.
✓ Branch 1 taken 1 times.
|
542 | if (cat->cur_idx < ctx->nb_inputs) { |
| 399 |
2/2✓ Branch 0 taken 962 times.
✓ Branch 1 taken 525 times.
|
1487 | for (i = 0; i < ctx->nb_outputs; i++) { |
| 400 | 962 | AVFilterLink *inlink = ctx->inputs[cat->cur_idx + i]; | |
| 401 | |||
| 402 | 962 | ret = ff_inlink_acknowledge_status(inlink, &status, &pts); | |
| 403 | /* TODO use pts */ | ||
| 404 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 946 times.
|
962 | if (ret > 0) { |
| 405 | 16 | close_input(ctx, cat->cur_idx + i); | |
| 406 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | if (cat->cur_idx + ctx->nb_outputs >= ctx->nb_inputs) { |
| 407 | 6 | int64_t eof_pts = cat->delta_ts; | |
| 408 | |||
| 409 | 6 | eof_pts += av_rescale_q(pts, inlink->time_base, ctx->outputs[i]->time_base); | |
| 410 | 6 | ff_outlink_set_status(ctx->outputs[i], status, eof_pts); | |
| 411 | } | ||
| 412 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | if (!cat->nb_in_active) { |
| 413 | 10 | ret = flush_segment(ctx); | |
| 414 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
| 415 | ✗ | return ret; | |
| 416 | } | ||
| 417 | 16 | ff_filter_set_ready(ctx, 10); | |
| 418 | 16 | return 0; | |
| 419 | } | ||
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 | 526 | ret = FFERROR_NOT_READY; | |
| 424 |
2/2✓ Branch 0 taken 843 times.
✓ Branch 1 taken 418 times.
|
1261 | for (i = 0; i < ctx->nb_outputs; i++) { |
| 425 |
2/2✓ Branch 1 taken 478 times.
✓ Branch 2 taken 365 times.
|
843 | if (ff_outlink_frame_wanted(ctx->outputs[i])) { |
| 426 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 370 times.
|
478 | if (cat->in[cat->cur_idx + i].eof) { |
| 427 |
2/2✓ Branch 0 taken 216 times.
✓ Branch 1 taken 108 times.
|
324 | for (j = 0; j < ctx->nb_outputs; j++) |
| 428 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 108 times.
|
216 | if (!cat->in[cat->cur_idx + j].eof) |
| 429 | 108 | ff_inlink_request_frame(ctx->inputs[cat->cur_idx + j]); | |
| 430 | 108 | return 0; | |
| 431 | } else { | ||
| 432 | 370 | ff_inlink_request_frame(ctx->inputs[cat->cur_idx + i]); | |
| 433 | 370 | ret = 0; | |
| 434 | } | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | 418 | return ret; | |
| 439 | } | ||
| 440 | |||
| 441 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
| 442 | char *res, int res_len, int flags) | ||
| 443 | { | ||
| 444 | ✗ | int ret = AVERROR(ENOSYS); | |
| 445 | |||
| 446 | ✗ | if (!strcmp(cmd, "next")) { | |
| 447 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "Command received: next\n"); | |
| 448 | ✗ | return flush_segment(ctx); | |
| 449 | } | ||
| 450 | |||
| 451 | ✗ | return ret; | |
| 452 | } | ||
| 453 | |||
| 454 | const FFFilter ff_avf_concat = { | ||
| 455 | .p.name = "concat", | ||
| 456 | .p.description = NULL_IF_CONFIG_SMALL("Concatenate audio and video streams."), | ||
| 457 | .p.inputs = NULL, | ||
| 458 | .p.outputs = NULL, | ||
| 459 | .p.priv_class = &concat_class, | ||
| 460 | .p.flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, | ||
| 461 | .init = init, | ||
| 462 | .uninit = uninit, | ||
| 463 | .activate = activate, | ||
| 464 | .priv_size = sizeof(ConcatContext), | ||
| 465 | FILTER_QUERY_FUNC2(query_formats), | ||
| 466 | .process_command = process_command, | ||
| 467 | }; | ||
| 468 |