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 |
|
|
/** |
20 |
|
|
* @file |
21 |
|
|
* Audio join filter |
22 |
|
|
* |
23 |
|
|
* Join multiple audio inputs as different channels in |
24 |
|
|
* a single output |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include "libavutil/avassert.h" |
28 |
|
|
#include "libavutil/avstring.h" |
29 |
|
|
#include "libavutil/channel_layout.h" |
30 |
|
|
#include "libavutil/common.h" |
31 |
|
|
#include "libavutil/opt.h" |
32 |
|
|
|
33 |
|
|
#include "audio.h" |
34 |
|
|
#include "avfilter.h" |
35 |
|
|
#include "formats.h" |
36 |
|
|
#include "filters.h" |
37 |
|
|
#include "internal.h" |
38 |
|
|
|
39 |
|
|
typedef struct ChannelMap { |
40 |
|
|
int input; ///< input stream index |
41 |
|
|
int in_channel_idx; ///< index of in_channel in the input stream data |
42 |
|
|
uint64_t in_channel; ///< layout describing the input channel |
43 |
|
|
uint64_t out_channel; ///< layout describing the output channel |
44 |
|
|
} ChannelMap; |
45 |
|
|
|
46 |
|
|
typedef struct JoinContext { |
47 |
|
|
const AVClass *class; |
48 |
|
|
|
49 |
|
|
int inputs; |
50 |
|
|
char *map; |
51 |
|
|
char *channel_layout_str; |
52 |
|
|
uint64_t channel_layout; |
53 |
|
|
|
54 |
|
|
int nb_channels; |
55 |
|
|
ChannelMap *channels; |
56 |
|
|
|
57 |
|
|
/** |
58 |
|
|
* Temporary storage for input frames, until we get one on each input. |
59 |
|
|
*/ |
60 |
|
|
AVFrame **input_frames; |
61 |
|
|
|
62 |
|
|
/** |
63 |
|
|
* Temporary storage for buffer references, for assembling the output frame. |
64 |
|
|
*/ |
65 |
|
|
AVBufferRef **buffers; |
66 |
|
|
} JoinContext; |
67 |
|
|
|
68 |
|
|
#define OFFSET(x) offsetof(JoinContext, x) |
69 |
|
|
#define A AV_OPT_FLAG_AUDIO_PARAM |
70 |
|
|
#define F AV_OPT_FLAG_FILTERING_PARAM |
71 |
|
|
static const AVOption join_options[] = { |
72 |
|
|
{ "inputs", "Number of input streams.", OFFSET(inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, A|F }, |
73 |
|
|
{ "channel_layout", "Channel layout of the " |
74 |
|
|
"output stream.", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, A|F }, |
75 |
|
|
{ "map", "A comma-separated list of channels maps in the format " |
76 |
|
|
"'input_stream.input_channel-output_channel.", |
77 |
|
|
OFFSET(map), AV_OPT_TYPE_STRING, .flags = A|F }, |
78 |
|
|
{ NULL } |
79 |
|
|
}; |
80 |
|
|
|
81 |
|
|
AVFILTER_DEFINE_CLASS(join); |
82 |
|
|
|
83 |
|
2 |
static int parse_maps(AVFilterContext *ctx) |
84 |
|
|
{ |
85 |
|
2 |
JoinContext *s = ctx->priv; |
86 |
|
2 |
char separator = '|'; |
87 |
|
2 |
char *cur = s->map; |
88 |
|
|
|
89 |
✗✓✗✗
|
2 |
while (cur && *cur) { |
90 |
|
|
char *sep, *next, *p; |
91 |
|
|
uint64_t in_channel = 0, out_channel = 0; |
92 |
|
|
int input_idx, out_ch_idx, in_ch_idx; |
93 |
|
|
|
94 |
|
|
next = strchr(cur, separator); |
95 |
|
|
if (next) |
96 |
|
|
*next++ = 0; |
97 |
|
|
|
98 |
|
|
/* split the map into input and output parts */ |
99 |
|
|
if (!(sep = strchr(cur, '-'))) { |
100 |
|
|
av_log(ctx, AV_LOG_ERROR, "Missing separator '-' in channel " |
101 |
|
|
"map '%s'\n", cur); |
102 |
|
|
return AVERROR(EINVAL); |
103 |
|
|
} |
104 |
|
|
*sep++ = 0; |
105 |
|
|
|
106 |
|
|
#define PARSE_CHANNEL(str, var, inout) \ |
107 |
|
|
if (!(var = av_get_channel_layout(str))) { \ |
108 |
|
|
av_log(ctx, AV_LOG_ERROR, "Invalid " inout " channel: %s.\n", str);\ |
109 |
|
|
return AVERROR(EINVAL); \ |
110 |
|
|
} \ |
111 |
|
|
if (av_get_channel_layout_nb_channels(var) != 1) { \ |
112 |
|
|
av_log(ctx, AV_LOG_ERROR, "Channel map describes more than one " \ |
113 |
|
|
inout " channel.\n"); \ |
114 |
|
|
return AVERROR(EINVAL); \ |
115 |
|
|
} |
116 |
|
|
|
117 |
|
|
/* parse output channel */ |
118 |
|
|
PARSE_CHANNEL(sep, out_channel, "output"); |
119 |
|
|
if (!(out_channel & s->channel_layout)) { |
120 |
|
|
av_log(ctx, AV_LOG_ERROR, "Output channel '%s' is not present in " |
121 |
|
|
"requested channel layout.\n", sep); |
122 |
|
|
return AVERROR(EINVAL); |
123 |
|
|
} |
124 |
|
|
|
125 |
|
|
out_ch_idx = av_get_channel_layout_channel_index(s->channel_layout, |
126 |
|
|
out_channel); |
127 |
|
|
if (s->channels[out_ch_idx].input >= 0) { |
128 |
|
|
av_log(ctx, AV_LOG_ERROR, "Multiple maps for output channel " |
129 |
|
|
"'%s'.\n", sep); |
130 |
|
|
return AVERROR(EINVAL); |
131 |
|
|
} |
132 |
|
|
|
133 |
|
|
/* parse input channel */ |
134 |
|
|
input_idx = strtol(cur, &cur, 0); |
135 |
|
|
if (input_idx < 0 || input_idx >= s->inputs) { |
136 |
|
|
av_log(ctx, AV_LOG_ERROR, "Invalid input stream index: %d.\n", |
137 |
|
|
input_idx); |
138 |
|
|
return AVERROR(EINVAL); |
139 |
|
|
} |
140 |
|
|
|
141 |
|
|
if (*cur) |
142 |
|
|
cur++; |
143 |
|
|
|
144 |
|
|
in_ch_idx = strtol(cur, &p, 0); |
145 |
|
|
if (p == cur) { |
146 |
|
|
/* channel specifier is not a number, |
147 |
|
|
* try to parse as channel name */ |
148 |
|
|
PARSE_CHANNEL(cur, in_channel, "input"); |
149 |
|
|
} |
150 |
|
|
|
151 |
|
|
s->channels[out_ch_idx].input = input_idx; |
152 |
|
|
if (in_channel) |
153 |
|
|
s->channels[out_ch_idx].in_channel = in_channel; |
154 |
|
|
else |
155 |
|
|
s->channels[out_ch_idx].in_channel_idx = in_ch_idx; |
156 |
|
|
|
157 |
|
|
cur = next; |
158 |
|
|
} |
159 |
|
2 |
return 0; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
2 |
static av_cold int join_init(AVFilterContext *ctx) |
163 |
|
|
{ |
164 |
|
2 |
JoinContext *s = ctx->priv; |
165 |
|
|
int ret, i; |
166 |
|
|
|
167 |
✗✓ |
2 |
if (!(s->channel_layout = av_get_channel_layout(s->channel_layout_str))) { |
168 |
|
|
av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout '%s'.\n", |
169 |
|
|
s->channel_layout_str); |
170 |
|
|
return AVERROR(EINVAL); |
171 |
|
|
} |
172 |
|
|
|
173 |
|
2 |
s->nb_channels = av_get_channel_layout_nb_channels(s->channel_layout); |
174 |
|
2 |
s->channels = av_mallocz_array(s->nb_channels, sizeof(*s->channels)); |
175 |
|
2 |
s->buffers = av_mallocz_array(s->nb_channels, sizeof(*s->buffers)); |
176 |
|
2 |
s->input_frames = av_mallocz_array(s->inputs, sizeof(*s->input_frames)); |
177 |
✓✗✓✗ ✗✓ |
2 |
if (!s->channels || !s->buffers|| !s->input_frames) |
178 |
|
|
return AVERROR(ENOMEM); |
179 |
|
|
|
180 |
✓✓ |
12 |
for (i = 0; i < s->nb_channels; i++) { |
181 |
|
10 |
s->channels[i].out_channel = av_channel_layout_extract_channel(s->channel_layout, i); |
182 |
|
10 |
s->channels[i].input = -1; |
183 |
|
|
} |
184 |
|
|
|
185 |
✗✓ |
2 |
if ((ret = parse_maps(ctx)) < 0) |
186 |
|
|
return ret; |
187 |
|
|
|
188 |
✓✓ |
6 |
for (i = 0; i < s->inputs; i++) { |
189 |
|
4 |
AVFilterPad pad = { 0 }; |
190 |
|
|
|
191 |
|
4 |
pad.type = AVMEDIA_TYPE_AUDIO; |
192 |
|
4 |
pad.name = av_asprintf("input%d", i); |
193 |
✗✓ |
4 |
if (!pad.name) |
194 |
|
|
return AVERROR(ENOMEM); |
195 |
|
|
|
196 |
✗✓ |
4 |
if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) { |
197 |
|
|
av_freep(&pad.name); |
198 |
|
|
return ret; |
199 |
|
|
} |
200 |
|
|
} |
201 |
|
|
|
202 |
|
2 |
return 0; |
203 |
|
|
} |
204 |
|
|
|
205 |
|
2 |
static av_cold void join_uninit(AVFilterContext *ctx) |
206 |
|
|
{ |
207 |
|
2 |
JoinContext *s = ctx->priv; |
208 |
|
|
int i; |
209 |
|
|
|
210 |
✓✓✓✗
|
6 |
for (i = 0; i < s->inputs && s->input_frames; i++) { |
211 |
|
4 |
av_frame_free(&s->input_frames[i]); |
212 |
|
|
} |
213 |
|
|
|
214 |
✓✓ |
6 |
for (i = 0; i < ctx->nb_inputs; i++) { |
215 |
|
4 |
av_freep(&ctx->input_pads[i].name); |
216 |
|
|
} |
217 |
|
|
|
218 |
|
2 |
av_freep(&s->channels); |
219 |
|
2 |
av_freep(&s->buffers); |
220 |
|
2 |
av_freep(&s->input_frames); |
221 |
|
2 |
} |
222 |
|
|
|
223 |
|
1 |
static int join_query_formats(AVFilterContext *ctx) |
224 |
|
|
{ |
225 |
|
1 |
JoinContext *s = ctx->priv; |
226 |
|
1 |
AVFilterChannelLayouts *layouts = NULL; |
227 |
|
|
int i, ret; |
228 |
|
|
|
229 |
✓✗✗✓
|
2 |
if ((ret = ff_add_channel_layout(&layouts, s->channel_layout)) < 0 || |
230 |
|
1 |
(ret = ff_channel_layouts_ref(layouts, &ctx->outputs[0]->incfg.channel_layouts)) < 0) |
231 |
|
|
return ret; |
232 |
|
|
|
233 |
✓✓ |
3 |
for (i = 0; i < ctx->nb_inputs; i++) { |
234 |
|
2 |
layouts = ff_all_channel_layouts(); |
235 |
✗✓ |
2 |
if ((ret = ff_channel_layouts_ref(layouts, &ctx->inputs[i]->outcfg.channel_layouts)) < 0) |
236 |
|
|
return ret; |
237 |
|
|
} |
238 |
|
|
|
239 |
✓✗✗✓
|
2 |
if ((ret = ff_set_common_formats(ctx, ff_planar_sample_fmts())) < 0 || |
240 |
|
1 |
(ret = ff_set_common_samplerates(ctx, ff_all_samplerates())) < 0) |
241 |
|
|
return ret; |
242 |
|
|
|
243 |
|
1 |
return 0; |
244 |
|
|
} |
245 |
|
|
|
246 |
|
5 |
static void guess_map_matching(AVFilterContext *ctx, ChannelMap *ch, |
247 |
|
|
uint64_t *inputs) |
248 |
|
|
{ |
249 |
|
|
int i; |
250 |
|
|
|
251 |
✓✓ |
11 |
for (i = 0; i < ctx->nb_inputs; i++) { |
252 |
|
8 |
AVFilterLink *link = ctx->inputs[i]; |
253 |
|
|
|
254 |
✓✓ |
8 |
if (ch->out_channel & link->channel_layout && |
255 |
✓✗ |
2 |
!(ch->out_channel & inputs[i])) { |
256 |
|
2 |
ch->input = i; |
257 |
|
2 |
ch->in_channel = ch->out_channel; |
258 |
|
2 |
inputs[i] |= ch->out_channel; |
259 |
|
2 |
return; |
260 |
|
|
} |
261 |
|
|
} |
262 |
|
|
} |
263 |
|
|
|
264 |
|
3 |
static void guess_map_any(AVFilterContext *ctx, ChannelMap *ch, |
265 |
|
|
uint64_t *inputs) |
266 |
|
|
{ |
267 |
|
|
int i; |
268 |
|
|
|
269 |
✓✗ |
6 |
for (i = 0; i < ctx->nb_inputs; i++) { |
270 |
|
6 |
AVFilterLink *link = ctx->inputs[i]; |
271 |
|
|
|
272 |
✓✓ |
6 |
if ((inputs[i] & link->channel_layout) != link->channel_layout) { |
273 |
|
3 |
uint64_t unused = link->channel_layout & ~inputs[i]; |
274 |
|
|
|
275 |
|
3 |
ch->input = i; |
276 |
|
3 |
ch->in_channel = av_channel_layout_extract_channel(unused, 0); |
277 |
|
3 |
inputs[i] |= ch->in_channel; |
278 |
|
3 |
return; |
279 |
|
|
} |
280 |
|
|
} |
281 |
|
|
} |
282 |
|
|
|
283 |
|
1 |
static int join_config_output(AVFilterLink *outlink) |
284 |
|
|
{ |
285 |
|
1 |
AVFilterContext *ctx = outlink->src; |
286 |
|
1 |
JoinContext *s = ctx->priv; |
287 |
|
|
uint64_t *inputs; // nth element tracks which channels are used from nth input |
288 |
|
1 |
int i, ret = 0; |
289 |
|
|
|
290 |
|
|
/* initialize inputs to user-specified mappings */ |
291 |
✗✓ |
1 |
if (!(inputs = av_mallocz_array(ctx->nb_inputs, sizeof(*inputs)))) |
292 |
|
|
return AVERROR(ENOMEM); |
293 |
✓✓ |
6 |
for (i = 0; i < s->nb_channels; i++) { |
294 |
|
5 |
ChannelMap *ch = &s->channels[i]; |
295 |
|
|
AVFilterLink *inlink; |
296 |
|
|
|
297 |
✓✗ |
5 |
if (ch->input < 0) |
298 |
|
5 |
continue; |
299 |
|
|
|
300 |
|
|
inlink = ctx->inputs[ch->input]; |
301 |
|
|
|
302 |
|
|
if (!ch->in_channel) |
303 |
|
|
ch->in_channel = av_channel_layout_extract_channel(inlink->channel_layout, |
304 |
|
|
ch->in_channel_idx); |
305 |
|
|
|
306 |
|
|
if (!(ch->in_channel & inlink->channel_layout)) { |
307 |
|
|
av_log(ctx, AV_LOG_ERROR, "Requested channel %s is not present in " |
308 |
|
|
"input stream #%d.\n", av_get_channel_name(ch->in_channel), |
309 |
|
|
ch->input); |
310 |
|
|
ret = AVERROR(EINVAL); |
311 |
|
|
goto fail; |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
inputs[ch->input] |= ch->in_channel; |
315 |
|
|
} |
316 |
|
|
|
317 |
|
|
/* guess channel maps when not explicitly defined */ |
318 |
|
|
/* first try unused matching channels */ |
319 |
✓✓ |
6 |
for (i = 0; i < s->nb_channels; i++) { |
320 |
|
5 |
ChannelMap *ch = &s->channels[i]; |
321 |
|
|
|
322 |
✓✗ |
5 |
if (ch->input < 0) |
323 |
|
5 |
guess_map_matching(ctx, ch, inputs); |
324 |
|
|
} |
325 |
|
|
|
326 |
|
|
/* if the above failed, try to find _any_ unused input channel */ |
327 |
✓✓ |
6 |
for (i = 0; i < s->nb_channels; i++) { |
328 |
|
5 |
ChannelMap *ch = &s->channels[i]; |
329 |
|
|
|
330 |
✓✓ |
5 |
if (ch->input < 0) |
331 |
|
3 |
guess_map_any(ctx, ch, inputs); |
332 |
|
|
|
333 |
✗✓ |
5 |
if (ch->input < 0) { |
334 |
|
|
av_log(ctx, AV_LOG_ERROR, "Could not find input channel for " |
335 |
|
|
"output channel '%s'.\n", |
336 |
|
|
av_get_channel_name(ch->out_channel)); |
337 |
|
|
goto fail; |
338 |
|
|
} |
339 |
|
|
|
340 |
|
5 |
ch->in_channel_idx = av_get_channel_layout_channel_index(ctx->inputs[ch->input]->channel_layout, |
341 |
|
|
ch->in_channel); |
342 |
|
|
} |
343 |
|
|
|
344 |
|
|
/* print mappings */ |
345 |
|
1 |
av_log(ctx, AV_LOG_VERBOSE, "mappings: "); |
346 |
✓✓ |
6 |
for (i = 0; i < s->nb_channels; i++) { |
347 |
|
5 |
ChannelMap *ch = &s->channels[i]; |
348 |
|
5 |
av_log(ctx, AV_LOG_VERBOSE, "%d.%s => %s ", ch->input, |
349 |
|
|
av_get_channel_name(ch->in_channel), |
350 |
|
|
av_get_channel_name(ch->out_channel)); |
351 |
|
|
} |
352 |
|
1 |
av_log(ctx, AV_LOG_VERBOSE, "\n"); |
353 |
|
|
|
354 |
✓✓ |
3 |
for (i = 0; i < ctx->nb_inputs; i++) { |
355 |
✗✓ |
2 |
if (!inputs[i]) |
356 |
|
|
av_log(ctx, AV_LOG_WARNING, "No channels are used from input " |
357 |
|
|
"stream %d.\n", i); |
358 |
|
|
} |
359 |
|
|
|
360 |
|
1 |
fail: |
361 |
|
1 |
av_freep(&inputs); |
362 |
|
1 |
return ret; |
363 |
|
|
} |
364 |
|
|
|
365 |
|
261 |
static int try_push_frame(AVFilterContext *ctx) |
366 |
|
|
{ |
367 |
|
261 |
AVFilterLink *outlink = ctx->outputs[0]; |
368 |
|
261 |
JoinContext *s = ctx->priv; |
369 |
|
|
AVFrame *frame; |
370 |
|
261 |
int linesize = INT_MAX; |
371 |
|
261 |
int nb_samples = INT_MAX; |
372 |
|
261 |
int nb_buffers = 0; |
373 |
|
|
int i, j, ret; |
374 |
|
|
|
375 |
✓✓ |
781 |
for (i = 0; i < ctx->nb_inputs; i++) { |
376 |
✓✓ |
522 |
if (!s->input_frames[i]) |
377 |
|
2 |
return 0; |
378 |
|
520 |
nb_samples = FFMIN(nb_samples, s->input_frames[i]->nb_samples); |
379 |
|
|
} |
380 |
✗✓ |
259 |
if (!nb_samples) |
381 |
|
|
return 0; |
382 |
|
|
|
383 |
|
|
/* setup the output frame */ |
384 |
|
259 |
frame = av_frame_alloc(); |
385 |
✗✓ |
259 |
if (!frame) |
386 |
|
|
return AVERROR(ENOMEM); |
387 |
✗✓ |
259 |
if (s->nb_channels > FF_ARRAY_ELEMS(frame->data)) { |
388 |
|
|
frame->extended_data = av_mallocz_array(s->nb_channels, |
389 |
|
|
sizeof(*frame->extended_data)); |
390 |
|
|
if (!frame->extended_data) { |
391 |
|
|
ret = AVERROR(ENOMEM); |
392 |
|
|
goto fail; |
393 |
|
|
} |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
/* copy the data pointers */ |
397 |
✓✓ |
1554 |
for (i = 0; i < s->nb_channels; i++) { |
398 |
|
1295 |
ChannelMap *ch = &s->channels[i]; |
399 |
|
1295 |
AVFrame *cur = s->input_frames[ch->input]; |
400 |
|
|
AVBufferRef *buf; |
401 |
|
|
|
402 |
|
1295 |
frame->extended_data[i] = cur->extended_data[ch->in_channel_idx]; |
403 |
|
1295 |
linesize = FFMIN(linesize, cur->linesize[0]); |
404 |
|
|
|
405 |
|
|
/* add the buffer where this plan is stored to the list if it's |
406 |
|
|
* not already there */ |
407 |
|
1295 |
buf = av_frame_get_plane_buffer(cur, ch->in_channel_idx); |
408 |
✗✓ |
1295 |
if (!buf) { |
409 |
|
|
ret = AVERROR(EINVAL); |
410 |
|
|
goto fail; |
411 |
|
|
} |
412 |
✓✓ |
3885 |
for (j = 0; j < nb_buffers; j++) |
413 |
✗✓ |
2590 |
if (s->buffers[j]->buffer == buf->buffer) |
414 |
|
|
break; |
415 |
✓✗ |
1295 |
if (j == i) |
416 |
|
1295 |
s->buffers[nb_buffers++] = buf; |
417 |
|
|
} |
418 |
|
|
|
419 |
|
|
/* create references to the buffers we copied to output */ |
420 |
✗✓ |
259 |
if (nb_buffers > FF_ARRAY_ELEMS(frame->buf)) { |
421 |
|
|
frame->nb_extended_buf = nb_buffers - FF_ARRAY_ELEMS(frame->buf); |
422 |
|
|
frame->extended_buf = av_mallocz_array(frame->nb_extended_buf, |
423 |
|
|
sizeof(*frame->extended_buf)); |
424 |
|
|
if (!frame->extended_buf) { |
425 |
|
|
frame->nb_extended_buf = 0; |
426 |
|
|
ret = AVERROR(ENOMEM); |
427 |
|
|
goto fail; |
428 |
|
|
} |
429 |
|
|
} |
430 |
✓✗✓✓
|
1554 |
for (i = 0; i < FFMIN(FF_ARRAY_ELEMS(frame->buf), nb_buffers); i++) { |
431 |
|
1295 |
frame->buf[i] = av_buffer_ref(s->buffers[i]); |
432 |
✗✓ |
1295 |
if (!frame->buf[i]) { |
433 |
|
|
ret = AVERROR(ENOMEM); |
434 |
|
|
goto fail; |
435 |
|
|
} |
436 |
|
|
} |
437 |
✗✓ |
259 |
for (i = 0; i < frame->nb_extended_buf; i++) { |
438 |
|
|
frame->extended_buf[i] = av_buffer_ref(s->buffers[i + |
439 |
|
|
FF_ARRAY_ELEMS(frame->buf)]); |
440 |
|
|
if (!frame->extended_buf[i]) { |
441 |
|
|
ret = AVERROR(ENOMEM); |
442 |
|
|
goto fail; |
443 |
|
|
} |
444 |
|
|
} |
445 |
|
|
|
446 |
|
259 |
frame->nb_samples = nb_samples; |
447 |
|
259 |
frame->channel_layout = outlink->channel_layout; |
448 |
|
259 |
frame->channels = outlink->channels; |
449 |
|
259 |
frame->sample_rate = outlink->sample_rate; |
450 |
|
259 |
frame->format = outlink->format; |
451 |
|
259 |
frame->pts = s->input_frames[0]->pts; |
452 |
|
259 |
frame->linesize[0] = linesize; |
453 |
✗✓ |
259 |
if (frame->data != frame->extended_data) { |
454 |
|
|
memcpy(frame->data, frame->extended_data, sizeof(*frame->data) * |
455 |
|
|
FFMIN(FF_ARRAY_ELEMS(frame->data), s->nb_channels)); |
456 |
|
|
} |
457 |
|
|
|
458 |
|
259 |
ret = ff_filter_frame(outlink, frame); |
459 |
|
|
|
460 |
✓✓ |
777 |
for (i = 0; i < ctx->nb_inputs; i++) |
461 |
|
518 |
av_frame_free(&s->input_frames[i]); |
462 |
|
|
|
463 |
|
259 |
return ret; |
464 |
|
|
|
465 |
|
|
fail: |
466 |
|
|
av_frame_free(&frame); |
467 |
|
|
return ret; |
468 |
|
|
} |
469 |
|
|
|
470 |
|
909 |
static int activate(AVFilterContext *ctx) |
471 |
|
|
{ |
472 |
|
909 |
JoinContext *s = ctx->priv; |
473 |
|
|
int i, ret, status; |
474 |
|
909 |
int nb_samples = 0; |
475 |
|
|
int64_t pts; |
476 |
|
|
|
477 |
✓✓✓✓
|
911 |
FF_FILTER_FORWARD_STATUS_BACK_ALL(ctx->outputs[0], ctx); |
478 |
|
|
|
479 |
✓✓ |
908 |
if (!s->input_frames[0]) { |
480 |
|
519 |
ret = ff_inlink_consume_frame(ctx->inputs[0], &s->input_frames[0]); |
481 |
✗✓ |
519 |
if (ret < 0) { |
482 |
|
|
return ret; |
483 |
✓✓ |
519 |
} else if (ff_inlink_acknowledge_status(ctx->inputs[0], &status, &pts)) { |
484 |
|
1 |
ff_outlink_set_status(ctx->outputs[0], status, pts); |
485 |
|
1 |
return 0; |
486 |
|
|
} else { |
487 |
✓✓✓✓
|
518 |
if (ff_outlink_frame_wanted(ctx->outputs[0]) && !s->input_frames[0]) { |
488 |
|
259 |
ff_inlink_request_frame(ctx->inputs[0]); |
489 |
|
259 |
return 0; |
490 |
|
|
} |
491 |
|
|
} |
492 |
✗✓ |
259 |
if (!s->input_frames[0]) { |
493 |
|
|
return 0; |
494 |
|
|
} |
495 |
|
|
} |
496 |
|
|
|
497 |
|
648 |
nb_samples = s->input_frames[0]->nb_samples; |
498 |
|
|
|
499 |
✓✓✓✗
|
909 |
for (i = 1; i < ctx->nb_inputs && nb_samples > 0; i++) { |
500 |
✗✓ |
648 |
if (s->input_frames[i]) |
501 |
|
|
continue; |
502 |
|
|
|
503 |
✓✓ |
648 |
if (ff_inlink_check_available_samples(ctx->inputs[i], nb_samples) > 0) { |
504 |
|
259 |
ret = ff_inlink_consume_samples(ctx->inputs[i], nb_samples, nb_samples, &s->input_frames[i]); |
505 |
✗✓ |
259 |
if (ret < 0) { |
506 |
|
|
return ret; |
507 |
✗✓ |
259 |
} else if (ff_inlink_acknowledge_status(ctx->inputs[i], &status, &pts)) { |
508 |
|
|
ff_outlink_set_status(ctx->outputs[0], status, pts); |
509 |
|
|
return 0; |
510 |
|
|
} |
511 |
|
|
} else { |
512 |
✓✓ |
389 |
if (ff_outlink_frame_wanted(ctx->outputs[0])) { |
513 |
|
387 |
ff_inlink_request_frame(ctx->inputs[i]); |
514 |
|
387 |
return 0; |
515 |
|
|
} |
516 |
|
|
} |
517 |
|
|
} |
518 |
|
|
|
519 |
|
261 |
return try_push_frame(ctx); |
520 |
|
|
} |
521 |
|
|
|
522 |
|
|
static const AVFilterPad avfilter_af_join_outputs[] = { |
523 |
|
|
{ |
524 |
|
|
.name = "default", |
525 |
|
|
.type = AVMEDIA_TYPE_AUDIO, |
526 |
|
|
.config_props = join_config_output, |
527 |
|
|
}, |
528 |
|
|
{ NULL } |
529 |
|
|
}; |
530 |
|
|
|
531 |
|
|
AVFilter ff_af_join = { |
532 |
|
|
.name = "join", |
533 |
|
|
.description = NULL_IF_CONFIG_SMALL("Join multiple audio streams into " |
534 |
|
|
"multi-channel output."), |
535 |
|
|
.priv_size = sizeof(JoinContext), |
536 |
|
|
.priv_class = &join_class, |
537 |
|
|
.init = join_init, |
538 |
|
|
.uninit = join_uninit, |
539 |
|
|
.activate = activate, |
540 |
|
|
.query_formats = join_query_formats, |
541 |
|
|
.inputs = NULL, |
542 |
|
|
.outputs = avfilter_af_join_outputs, |
543 |
|
|
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS, |
544 |
|
|
}; |