annotate ffmpeg/libavfilter/af_join.c @ 13:844d341cf643 tip

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