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