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/opt.h" | ||
29 | #include "avfilter.h" | ||
30 | #include "filters.h" | ||
31 | #include "formats.h" | ||
32 | #include "internal.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(AVFilterContext *ctx) | |
76 | { | ||
77 | 4 | ConcatContext *cat = ctx->priv; | |
78 | 4 | unsigned type, nb_str, idx0 = 0, idx, str, seg; | |
79 | 4 | AVFilterFormats *formats, *rates = NULL; | |
80 | 4 | AVFilterChannelLayouts *layouts = NULL; | |
81 | int ret; | ||
82 | |||
83 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
|
12 | for (type = 0; type < TYPE_ALL; type++) { |
84 | 8 | nb_str = cat->nb_streams[type]; | |
85 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
|
14 | for (str = 0; str < nb_str; str++) { |
86 | 6 | idx = idx0; | |
87 | |||
88 | /* Set the output formats */ | ||
89 | 6 | formats = ff_all_formats(type); | |
90 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if ((ret = ff_formats_ref(formats, &ctx->outputs[idx]->incfg.formats)) < 0) |
91 | ✗ | return ret; | |
92 | |||
93 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
|
6 | if (type == AVMEDIA_TYPE_AUDIO) { |
94 | 2 | rates = ff_all_samplerates(); | |
95 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if ((ret = ff_formats_ref(rates, &ctx->outputs[idx]->incfg.samplerates)) < 0) |
96 | ✗ | return ret; | |
97 | 2 | layouts = ff_all_channel_layouts(); | |
98 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if ((ret = ff_channel_layouts_ref(layouts, &ctx->outputs[idx]->incfg.channel_layouts)) < 0) |
99 | ✗ | return ret; | |
100 | } | ||
101 | |||
102 | /* Set the same formats for each corresponding input */ | ||
103 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 6 times.
|
22 | for (seg = 0; seg < cat->nb_segments; seg++) { |
104 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
|
16 | if ((ret = ff_formats_ref(formats, &ctx->inputs[idx]->outcfg.formats)) < 0) |
105 | ✗ | return ret; | |
106 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | if (type == AVMEDIA_TYPE_AUDIO) { |
107 |
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, &ctx->inputs[idx]->outcfg.samplerates)) < 0 || |
108 | 6 | (ret = ff_channel_layouts_ref(layouts, &ctx->inputs[idx]->outcfg.channel_layouts)) < 0) | |
109 | ✗ | return ret; | |
110 | } | ||
111 | 16 | idx += ctx->nb_outputs; | |
112 | } | ||
113 | |||
114 | 6 | idx0++; | |
115 | } | ||
116 | } | ||
117 | 4 | return 0; | |
118 | } | ||
119 | |||
120 | 6 | static int config_output(AVFilterLink *outlink) | |
121 | { | ||
122 | 6 | AVFilterContext *ctx = outlink->src; | |
123 | 6 | ConcatContext *cat = ctx->priv; | |
124 | 6 | unsigned out_no = FF_OUTLINK_IDX(outlink); | |
125 | 6 | unsigned in_no = out_no, seg; | |
126 | 6 | AVFilterLink *inlink = ctx->inputs[in_no]; | |
127 | |||
128 | /* enhancement: find a common one */ | ||
129 | 6 | outlink->time_base = AV_TIME_BASE_Q; | |
130 | 6 | outlink->w = inlink->w; | |
131 | 6 | outlink->h = inlink->h; | |
132 | 6 | outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; | |
133 | 6 | outlink->format = inlink->format; | |
134 | 6 | outlink->frame_rate = inlink->frame_rate; | |
135 | |||
136 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
|
14 | for (seg = 1; seg < cat->nb_segments; seg++) { |
137 | 9 | inlink = ctx->inputs[in_no + seg * ctx->nb_outputs]; | |
138 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
|
9 | if (outlink->frame_rate.num != inlink->frame_rate.num || |
139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | outlink->frame_rate.den != inlink->frame_rate.den) { |
140 | 1 | av_log(ctx, AV_LOG_VERBOSE, | |
141 | "Video inputs have different frame rates, output will be VFR\n"); | ||
142 | 1 | outlink->frame_rate = av_make_q(1, 0); | |
143 | 1 | break; | |
144 | } | ||
145 | } | ||
146 | |||
147 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | for (seg = 1; seg < cat->nb_segments; seg++) { |
148 | 10 | inlink = ctx->inputs[in_no + seg * ctx->nb_outputs]; | |
149 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | if (!outlink->sample_aspect_ratio.num) |
150 | 5 | outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; | |
151 | /* possible enhancement: unsafe mode, do not check */ | ||
152 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
|
10 | if (outlink->w != inlink->w || |
153 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | outlink->h != inlink->h || |
154 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num && |
155 | ✗ | inlink->sample_aspect_ratio.num || | |
156 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) { |
157 | 1 | av_log(ctx, AV_LOG_ERROR, "Input link %s parameters " | |
158 | "(size %dx%d, SAR %d:%d) do not match the corresponding " | ||
159 | "output link %s parameters (%dx%d, SAR %d:%d)\n", | ||
160 | 1 | ctx->input_pads[in_no].name, inlink->w, inlink->h, | |
161 | inlink->sample_aspect_ratio.num, | ||
162 | inlink->sample_aspect_ratio.den, | ||
163 | 1 | ctx->input_pads[out_no].name, outlink->w, outlink->h, | |
164 | outlink->sample_aspect_ratio.num, | ||
165 | outlink->sample_aspect_ratio.den); | ||
166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!cat->unsafe) |
167 | ✗ | return AVERROR(EINVAL); | |
168 | } | ||
169 | } | ||
170 | |||
171 | 6 | return 0; | |
172 | } | ||
173 | |||
174 | 463 | static int push_frame(AVFilterContext *ctx, unsigned in_no, AVFrame *buf) | |
175 | { | ||
176 | 463 | ConcatContext *cat = ctx->priv; | |
177 | 463 | unsigned out_no = in_no % ctx->nb_outputs; | |
178 | 463 | AVFilterLink * inlink = ctx-> inputs[ in_no]; | |
179 | 463 | AVFilterLink *outlink = ctx->outputs[out_no]; | |
180 | 463 | struct concat_in *in = &cat->in[in_no]; | |
181 | |||
182 | 463 | buf->pts = av_rescale_q(buf->pts, inlink->time_base, outlink->time_base); | |
183 | 463 | buf->duration = av_rescale_q(buf->duration, inlink->time_base, outlink->time_base); | |
184 | 463 | in->pts = buf->pts; | |
185 | 463 | in->nb_frames++; | |
186 | /* add duration to input PTS */ | ||
187 |
2/2✓ Branch 0 taken 350 times.
✓ Branch 1 taken 113 times.
|
463 | if (inlink->sample_rate) |
188 | /* use number of audio samples */ | ||
189 | 350 | in->pts += av_rescale_q(buf->nb_samples, | |
190 | av_make_q(1, inlink->sample_rate), | ||
191 | outlink->time_base); | ||
192 |
2/2✓ Branch 0 taken 103 times.
✓ Branch 1 taken 10 times.
|
113 | else if (in->nb_frames >= 2) |
193 | /* use mean duration */ | ||
194 | 103 | in->pts = av_rescale(in->pts, in->nb_frames, in->nb_frames - 1); | |
195 | |||
196 | 463 | buf->pts += cat->delta_ts; | |
197 | 463 | return ff_filter_frame(outlink, buf); | |
198 | } | ||
199 | |||
200 | 63 | static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) | |
201 | { | ||
202 | 63 | AVFilterContext *ctx = inlink->dst; | |
203 | 63 | unsigned in_no = FF_INLINK_IDX(inlink); | |
204 | 63 | AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; | |
205 | |||
206 | 63 | return ff_get_video_buffer(outlink, w, h); | |
207 | } | ||
208 | |||
209 | 350 | static AVFrame *get_audio_buffer(AVFilterLink *inlink, int nb_samples) | |
210 | { | ||
211 | 350 | AVFilterContext *ctx = inlink->dst; | |
212 | 350 | unsigned in_no = FF_INLINK_IDX(inlink); | |
213 | 350 | AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs]; | |
214 | |||
215 | 350 | return ff_get_audio_buffer(outlink, nb_samples); | |
216 | } | ||
217 | |||
218 | 16 | static void close_input(AVFilterContext *ctx, unsigned in_no) | |
219 | { | ||
220 | 16 | ConcatContext *cat = ctx->priv; | |
221 | |||
222 | 16 | cat->in[in_no].eof = 1; | |
223 | 16 | cat->nb_in_active--; | |
224 | 16 | av_log(ctx, AV_LOG_VERBOSE, "EOF on %s, %d streams left in segment.\n", | |
225 | 16 | ctx->input_pads[in_no].name, cat->nb_in_active); | |
226 | 16 | } | |
227 | |||
228 | 10 | static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta) | |
229 | { | ||
230 | 10 | ConcatContext *cat = ctx->priv; | |
231 | 10 | unsigned i = cat->cur_idx; | |
232 | 10 | unsigned imax = i + ctx->nb_outputs; | |
233 | int64_t pts; | ||
234 | |||
235 | 10 | pts = cat->in[i++].pts; | |
236 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | for (; i < imax; i++) |
237 | 6 | pts = FFMAX(pts, cat->in[i].pts); | |
238 | 10 | cat->delta_ts += pts; | |
239 | 10 | *seg_delta = pts; | |
240 | 10 | } | |
241 | |||
242 | 4 | static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no, | |
243 | int64_t seg_delta) | ||
244 | { | ||
245 | 4 | ConcatContext *cat = ctx->priv; | |
246 | 4 | AVFilterLink *outlink = ctx->outputs[out_no]; | |
247 | 4 | int64_t base_pts = cat->in[in_no].pts + cat->delta_ts - seg_delta; | |
248 | 4 | int64_t nb_samples, sent = 0; | |
249 | int frame_nb_samples, ret; | ||
250 | 4 | AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate }; | |
251 | AVFrame *buf; | ||
252 | |||
253 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!rate_tb.den) |
254 | ✗ | return AVERROR_BUG; | |
255 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (cat->in[in_no].pts < INT64_MIN + seg_delta) |
256 | ✗ | return AVERROR_INVALIDDATA; | |
257 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (seg_delta < cat->in[in_no].pts) |
258 | ✗ | return AVERROR_INVALIDDATA; | |
259 | 4 | nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts, | |
260 | outlink->time_base, rate_tb); | ||
261 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | frame_nb_samples = FFMAX(9600, rate_tb.den / 5); /* arbitrary */ |
262 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
|
14 | while (nb_samples) { |
263 | 10 | frame_nb_samples = FFMIN(frame_nb_samples, nb_samples); | |
264 | 10 | buf = ff_get_audio_buffer(outlink, frame_nb_samples); | |
265 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (!buf) |
266 | ✗ | return AVERROR(ENOMEM); | |
267 | 10 | av_samples_set_silence(buf->extended_data, 0, frame_nb_samples, | |
268 | 10 | outlink->ch_layout.nb_channels, outlink->format); | |
269 | 10 | buf->pts = base_pts + av_rescale_q(sent, rate_tb, outlink->time_base); | |
270 | 10 | ret = ff_filter_frame(outlink, buf); | |
271 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
272 | ✗ | return ret; | |
273 | 10 | sent += frame_nb_samples; | |
274 | 10 | nb_samples -= frame_nb_samples; | |
275 | } | ||
276 | 4 | return 0; | |
277 | } | ||
278 | |||
279 | 10 | static int flush_segment(AVFilterContext *ctx) | |
280 | { | ||
281 | int ret; | ||
282 | 10 | ConcatContext *cat = ctx->priv; | |
283 | unsigned str, str_max; | ||
284 | int64_t seg_delta; | ||
285 | |||
286 | 10 | find_next_delta_ts(ctx, &seg_delta); | |
287 | 10 | cat->cur_idx += ctx->nb_outputs; | |
288 | 10 | cat->nb_in_active = ctx->nb_outputs; | |
289 | 10 | av_log(ctx, AV_LOG_VERBOSE, "Segment finished at pts=%"PRId64"\n", | |
290 | cat->delta_ts); | ||
291 | |||
292 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
|
10 | if (cat->cur_idx < ctx->nb_inputs) { |
293 | /* pad audio streams with silence */ | ||
294 | 6 | str = cat->nb_streams[AVMEDIA_TYPE_VIDEO]; | |
295 | 6 | str_max = str + cat->nb_streams[AVMEDIA_TYPE_AUDIO]; | |
296 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | for (; str < str_max; str++) { |
297 | 4 | ret = send_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str, | |
298 | seg_delta); | ||
299 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
300 | ✗ | return ret; | |
301 | } | ||
302 | } | ||
303 | 10 | return 0; | |
304 | } | ||
305 | |||
306 | 7 | static av_cold int init(AVFilterContext *ctx) | |
307 | { | ||
308 | 7 | ConcatContext *cat = ctx->priv; | |
309 | unsigned seg, type, str; | ||
310 | int ret; | ||
311 | |||
312 | /* create input pads */ | ||
313 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
|
25 | for (seg = 0; seg < cat->nb_segments; seg++) { |
314 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 18 times.
|
54 | for (type = 0; type < TYPE_ALL; type++) { |
315 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 36 times.
|
66 | for (str = 0; str < cat->nb_streams[type]; str++) { |
316 | 30 | AVFilterPad pad = { | |
317 | .type = type, | ||
318 | }; | ||
319 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 12 times.
|
30 | if (type == AVMEDIA_TYPE_VIDEO) |
320 | 18 | pad.get_buffer.video = get_video_buffer; | |
321 | else | ||
322 | 12 | pad.get_buffer.audio = get_audio_buffer; | |
323 | 30 | pad.name = av_asprintf("in%d:%c%d", seg, "va"[type], str); | |
324 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
|
30 | if ((ret = ff_append_inpad_free_name(ctx, &pad)) < 0) |
325 | ✗ | return ret; | |
326 | } | ||
327 | } | ||
328 | } | ||
329 | /* create output pads */ | ||
330 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
|
21 | for (type = 0; type < TYPE_ALL; type++) { |
331 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 14 times.
|
25 | for (str = 0; str < cat->nb_streams[type]; str++) { |
332 | 11 | AVFilterPad pad = { | |
333 | .type = type, | ||
334 | .config_props = config_output, | ||
335 | }; | ||
336 | 11 | pad.name = av_asprintf("out:%c%d", "va"[type], str); | |
337 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | if ((ret = ff_append_outpad_free_name(ctx, &pad)) < 0) |
338 | ✗ | return ret; | |
339 | } | ||
340 | } | ||
341 | |||
342 | 7 | cat->in = av_calloc(ctx->nb_inputs, sizeof(*cat->in)); | |
343 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (!cat->in) |
344 | ✗ | return AVERROR(ENOMEM); | |
345 | 7 | cat->nb_in_active = ctx->nb_outputs; | |
346 | 7 | return 0; | |
347 | } | ||
348 | |||
349 | 7 | static av_cold void uninit(AVFilterContext *ctx) | |
350 | { | ||
351 | 7 | ConcatContext *cat = ctx->priv; | |
352 | |||
353 | 7 | av_freep(&cat->in); | |
354 | 7 | } | |
355 | |||
356 | 994 | static int activate(AVFilterContext *ctx) | |
357 | { | ||
358 | 994 | ConcatContext *cat = ctx->priv; | |
359 | AVFrame *frame; | ||
360 | unsigned i, j; | ||
361 | int ret, status; | ||
362 | int64_t pts; | ||
363 | |||
364 | /* Forward status back */ | ||
365 |
2/2✓ Branch 0 taken 1824 times.
✓ Branch 1 taken 994 times.
|
2818 | for (i = 0; i < ctx->nb_outputs; i++) { |
366 | 1824 | status = ff_outlink_get_status(ctx->outputs[i]); | |
367 |
2/2✓ Branch 0 taken 1793 times.
✓ Branch 1 taken 31 times.
|
1824 | if (!status) |
368 | 1793 | continue; | |
369 |
2/2✓ Branch 0 taken 92 times.
✓ Branch 1 taken 31 times.
|
123 | for (j = i; j < ctx->nb_inputs; j += ctx->nb_outputs) { |
370 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
|
92 | if (!cat->in[j].eof) { |
371 | ✗ | cat->in[j].eof = 1; | |
372 | ✗ | ff_inlink_set_status(ctx->inputs[j], status); | |
373 | ✗ | return 0; | |
374 | } | ||
375 | } | ||
376 | |||
377 | } | ||
378 | |||
379 | /* Forward available frames */ | ||
380 |
2/2✓ Branch 0 taken 993 times.
✓ Branch 1 taken 1 times.
|
994 | if (cat->cur_idx < ctx->nb_inputs) { |
381 |
2/2✓ Branch 0 taken 1770 times.
✓ Branch 1 taken 530 times.
|
2300 | for (i = 0; i < ctx->nb_outputs; i++) { |
382 | 1770 | ret = ff_inlink_consume_frame(ctx->inputs[cat->cur_idx + i], &frame); | |
383 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1770 times.
|
1770 | if (ret < 0) |
384 | ✗ | return ret; | |
385 |
2/2✓ Branch 0 taken 463 times.
✓ Branch 1 taken 1307 times.
|
1770 | if (ret) { |
386 | 463 | ff_filter_set_ready(ctx, 10); | |
387 | 463 | return push_frame(ctx, cat->cur_idx + i, frame); | |
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | /* Forward status change */ | ||
393 |
2/2✓ Branch 0 taken 530 times.
✓ Branch 1 taken 1 times.
|
531 | if (cat->cur_idx < ctx->nb_inputs) { |
394 |
2/2✓ Branch 0 taken 951 times.
✓ Branch 1 taken 514 times.
|
1465 | for (i = 0; i < ctx->nb_outputs; i++) { |
395 | 951 | AVFilterLink *inlink = ctx->inputs[cat->cur_idx + i]; | |
396 | |||
397 | 951 | ret = ff_inlink_acknowledge_status(inlink, &status, &pts); | |
398 | /* TODO use pts */ | ||
399 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 935 times.
|
951 | if (ret > 0) { |
400 | 16 | close_input(ctx, cat->cur_idx + i); | |
401 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | if (cat->cur_idx + ctx->nb_outputs >= ctx->nb_inputs) { |
402 | 6 | int64_t eof_pts = cat->delta_ts; | |
403 | |||
404 | 6 | eof_pts += av_rescale_q(pts, inlink->time_base, ctx->outputs[i]->time_base); | |
405 | 6 | ff_outlink_set_status(ctx->outputs[i], status, eof_pts); | |
406 | } | ||
407 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | if (!cat->nb_in_active) { |
408 | 10 | ret = flush_segment(ctx); | |
409 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
410 | ✗ | return ret; | |
411 | } | ||
412 | 16 | ff_filter_set_ready(ctx, 10); | |
413 | 16 | return 0; | |
414 | } | ||
415 | } | ||
416 | } | ||
417 | |||
418 | 515 | ret = FFERROR_NOT_READY; | |
419 |
2/2✓ Branch 0 taken 832 times.
✓ Branch 1 taken 407 times.
|
1239 | for (i = 0; i < ctx->nb_outputs; i++) { |
420 |
2/2✓ Branch 1 taken 477 times.
✓ Branch 2 taken 355 times.
|
832 | if (ff_outlink_frame_wanted(ctx->outputs[i])) { |
421 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 369 times.
|
477 | if (cat->in[cat->cur_idx + i].eof) { |
422 |
2/2✓ Branch 0 taken 216 times.
✓ Branch 1 taken 108 times.
|
324 | for (j = 0; j < ctx->nb_outputs; j++) |
423 |
2/2✓ Branch 0 taken 108 times.
✓ Branch 1 taken 108 times.
|
216 | if (!cat->in[cat->cur_idx + j].eof) |
424 | 108 | ff_inlink_request_frame(ctx->inputs[cat->cur_idx + j]); | |
425 | 108 | return 0; | |
426 | } else { | ||
427 | 369 | ff_inlink_request_frame(ctx->inputs[cat->cur_idx + i]); | |
428 | 369 | ret = 0; | |
429 | } | ||
430 | } | ||
431 | } | ||
432 | |||
433 | 407 | return ret; | |
434 | } | ||
435 | |||
436 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
437 | char *res, int res_len, int flags) | ||
438 | { | ||
439 | ✗ | int ret = AVERROR(ENOSYS); | |
440 | |||
441 | ✗ | if (!strcmp(cmd, "next")) { | |
442 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "Command received: next\n"); | |
443 | ✗ | return flush_segment(ctx); | |
444 | } | ||
445 | |||
446 | ✗ | return ret; | |
447 | } | ||
448 | |||
449 | const AVFilter ff_avf_concat = { | ||
450 | .name = "concat", | ||
451 | .description = NULL_IF_CONFIG_SMALL("Concatenate audio and video streams."), | ||
452 | .init = init, | ||
453 | .uninit = uninit, | ||
454 | .activate = activate, | ||
455 | .priv_size = sizeof(ConcatContext), | ||
456 | .inputs = NULL, | ||
457 | .outputs = NULL, | ||
458 | .priv_class = &concat_class, | ||
459 | .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, | ||
460 | FILTER_QUERY_FUNC(query_formats), | ||
461 | .process_command = process_command, | ||
462 | }; | ||
463 |