yading@10: /* yading@10: * yading@10: * This file is part of Libav. yading@10: * yading@10: * Libav is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * Libav is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@10: * Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public yading@10: * License along with Libav; if not, write to the Free Software yading@10: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * Audio join filter yading@10: * yading@10: * Join multiple audio inputs as different channels in yading@10: * a single output yading@10: */ yading@10: yading@10: #include "libavutil/avassert.h" yading@10: #include "libavutil/channel_layout.h" yading@10: #include "libavutil/common.h" yading@10: #include "libavutil/opt.h" yading@10: yading@10: #include "audio.h" yading@10: #include "avfilter.h" yading@10: #include "formats.h" yading@10: #include "internal.h" yading@10: yading@10: typedef struct ChannelMap { yading@10: int input; ///< input stream index yading@10: int in_channel_idx; ///< index of in_channel in the input stream data yading@10: uint64_t in_channel; ///< layout describing the input channel yading@10: uint64_t out_channel; ///< layout describing the output channel yading@10: } ChannelMap; yading@10: yading@10: typedef struct JoinContext { yading@10: const AVClass *class; yading@10: yading@10: int inputs; yading@10: char *map; yading@10: char *channel_layout_str; yading@10: uint64_t channel_layout; yading@10: yading@10: int nb_channels; yading@10: ChannelMap *channels; yading@10: yading@10: /** yading@10: * Temporary storage for input frames, until we get one on each input. yading@10: */ yading@10: AVFrame **input_frames; yading@10: yading@10: /** yading@10: * Temporary storage for buffer references, for assembling the output frame. yading@10: */ yading@10: AVBufferRef **buffers; yading@10: } JoinContext; yading@10: yading@10: #define OFFSET(x) offsetof(JoinContext, x) yading@10: #define A AV_OPT_FLAG_AUDIO_PARAM yading@10: #define F AV_OPT_FLAG_FILTERING_PARAM yading@10: static const AVOption join_options[] = { yading@10: { "inputs", "Number of input streams.", OFFSET(inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, A|F }, yading@10: { "channel_layout", "Channel layout of the " yading@10: "output stream.", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, A|F }, yading@10: { "map", "A comma-separated list of channels maps in the format " yading@10: "'input_stream.input_channel-output_channel.", yading@10: OFFSET(map), AV_OPT_TYPE_STRING, .flags = A|F }, yading@10: { NULL }, yading@10: }; yading@10: yading@10: static const AVClass join_class = { yading@10: .class_name = "join filter", yading@10: .item_name = av_default_item_name, yading@10: .option = join_options, yading@10: .version = LIBAVUTIL_VERSION_INT, yading@10: }; yading@10: yading@10: static int filter_frame(AVFilterLink *link, AVFrame *frame) yading@10: { yading@10: AVFilterContext *ctx = link->dst; yading@10: JoinContext *s = ctx->priv; yading@10: int i; yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) yading@10: if (link == ctx->inputs[i]) yading@10: break; yading@10: av_assert0(i < ctx->nb_inputs); yading@10: av_assert0(!s->input_frames[i]); yading@10: s->input_frames[i] = frame; yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static int parse_maps(AVFilterContext *ctx) yading@10: { yading@10: JoinContext *s = ctx->priv; yading@10: char separator = '|'; yading@10: char *cur = s->map; yading@10: yading@10: #if FF_API_OLD_FILTER_OPTS yading@10: if (cur && strchr(cur, ',')) { yading@10: av_log(ctx, AV_LOG_WARNING, "This syntax is deprecated, use '|' to " yading@10: "separate the mappings.\n"); yading@10: separator = ','; yading@10: } yading@10: #endif yading@10: yading@10: while (cur && *cur) { yading@10: char *sep, *next, *p; yading@10: uint64_t in_channel = 0, out_channel = 0; yading@10: int input_idx, out_ch_idx, in_ch_idx; yading@10: yading@10: next = strchr(cur, separator); yading@10: if (next) yading@10: *next++ = 0; yading@10: yading@10: /* split the map into input and output parts */ yading@10: if (!(sep = strchr(cur, '-'))) { yading@10: av_log(ctx, AV_LOG_ERROR, "Missing separator '-' in channel " yading@10: "map '%s'\n", cur); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: *sep++ = 0; yading@10: yading@10: #define PARSE_CHANNEL(str, var, inout) \ yading@10: if (!(var = av_get_channel_layout(str))) { \ yading@10: av_log(ctx, AV_LOG_ERROR, "Invalid " inout " channel: %s.\n", str);\ yading@10: return AVERROR(EINVAL); \ yading@10: } \ yading@10: if (av_get_channel_layout_nb_channels(var) != 1) { \ yading@10: av_log(ctx, AV_LOG_ERROR, "Channel map describes more than one " \ yading@10: inout " channel.\n"); \ yading@10: return AVERROR(EINVAL); \ yading@10: } yading@10: yading@10: /* parse output channel */ yading@10: PARSE_CHANNEL(sep, out_channel, "output"); yading@10: if (!(out_channel & s->channel_layout)) { yading@10: av_log(ctx, AV_LOG_ERROR, "Output channel '%s' is not present in " yading@10: "requested channel layout.\n", sep); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: yading@10: out_ch_idx = av_get_channel_layout_channel_index(s->channel_layout, yading@10: out_channel); yading@10: if (s->channels[out_ch_idx].input >= 0) { yading@10: av_log(ctx, AV_LOG_ERROR, "Multiple maps for output channel " yading@10: "'%s'.\n", sep); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: yading@10: /* parse input channel */ yading@10: input_idx = strtol(cur, &cur, 0); yading@10: if (input_idx < 0 || input_idx >= s->inputs) { yading@10: av_log(ctx, AV_LOG_ERROR, "Invalid input stream index: %d.\n", yading@10: input_idx); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: yading@10: if (*cur) yading@10: cur++; yading@10: yading@10: in_ch_idx = strtol(cur, &p, 0); yading@10: if (p == cur) { yading@10: /* channel specifier is not a number, yading@10: * try to parse as channel name */ yading@10: PARSE_CHANNEL(cur, in_channel, "input"); yading@10: } yading@10: yading@10: s->channels[out_ch_idx].input = input_idx; yading@10: if (in_channel) yading@10: s->channels[out_ch_idx].in_channel = in_channel; yading@10: else yading@10: s->channels[out_ch_idx].in_channel_idx = in_ch_idx; yading@10: yading@10: cur = next; yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static int join_init(AVFilterContext *ctx) yading@10: { yading@10: JoinContext *s = ctx->priv; yading@10: int ret, i; yading@10: yading@10: if (!(s->channel_layout = av_get_channel_layout(s->channel_layout_str))) { yading@10: av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout '%s'.\n", yading@10: s->channel_layout_str); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: yading@10: s->nb_channels = av_get_channel_layout_nb_channels(s->channel_layout); yading@10: s->channels = av_mallocz(sizeof(*s->channels) * s->nb_channels); yading@10: s->buffers = av_mallocz(sizeof(*s->buffers) * s->nb_channels); yading@10: s->input_frames = av_mallocz(sizeof(*s->input_frames) * s->inputs); yading@10: if (!s->channels || !s->buffers|| !s->input_frames) { yading@10: ret = AVERROR(ENOMEM); yading@10: goto fail; yading@10: } yading@10: yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: s->channels[i].out_channel = av_channel_layout_extract_channel(s->channel_layout, i); yading@10: s->channels[i].input = -1; yading@10: } yading@10: yading@10: if ((ret = parse_maps(ctx)) < 0) yading@10: goto fail; yading@10: yading@10: for (i = 0; i < s->inputs; i++) { yading@10: char name[32]; yading@10: AVFilterPad pad = { 0 }; yading@10: yading@10: snprintf(name, sizeof(name), "input%d", i); yading@10: pad.type = AVMEDIA_TYPE_AUDIO; yading@10: pad.name = av_strdup(name); yading@10: pad.filter_frame = filter_frame; yading@10: yading@10: pad.needs_fifo = 1; yading@10: yading@10: ff_insert_inpad(ctx, i, &pad); yading@10: } yading@10: yading@10: fail: yading@10: av_opt_free(s); yading@10: return ret; yading@10: } yading@10: yading@10: static void join_uninit(AVFilterContext *ctx) yading@10: { yading@10: JoinContext *s = ctx->priv; yading@10: int i; yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: av_freep(&ctx->input_pads[i].name); yading@10: av_frame_free(&s->input_frames[i]); yading@10: } yading@10: yading@10: av_freep(&s->channels); yading@10: av_freep(&s->buffers); yading@10: av_freep(&s->input_frames); yading@10: } yading@10: yading@10: static int join_query_formats(AVFilterContext *ctx) yading@10: { yading@10: JoinContext *s = ctx->priv; yading@10: AVFilterChannelLayouts *layouts = NULL; yading@10: int i; yading@10: yading@10: ff_add_channel_layout(&layouts, s->channel_layout); yading@10: ff_channel_layouts_ref(layouts, &ctx->outputs[0]->in_channel_layouts); yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) yading@10: ff_channel_layouts_ref(ff_all_channel_layouts(), yading@10: &ctx->inputs[i]->out_channel_layouts); yading@10: yading@10: ff_set_common_formats (ctx, ff_planar_sample_fmts()); yading@10: ff_set_common_samplerates(ctx, ff_all_samplerates()); yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static void guess_map_matching(AVFilterContext *ctx, ChannelMap *ch, yading@10: uint64_t *inputs) yading@10: { yading@10: int i; yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: AVFilterLink *link = ctx->inputs[i]; yading@10: yading@10: if (ch->out_channel & link->channel_layout && yading@10: !(ch->out_channel & inputs[i])) { yading@10: ch->input = i; yading@10: ch->in_channel = ch->out_channel; yading@10: inputs[i] |= ch->out_channel; yading@10: return; yading@10: } yading@10: } yading@10: } yading@10: yading@10: static void guess_map_any(AVFilterContext *ctx, ChannelMap *ch, yading@10: uint64_t *inputs) yading@10: { yading@10: int i; yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: AVFilterLink *link = ctx->inputs[i]; yading@10: yading@10: if ((inputs[i] & link->channel_layout) != link->channel_layout) { yading@10: uint64_t unused = link->channel_layout & ~inputs[i]; yading@10: yading@10: ch->input = i; yading@10: ch->in_channel = av_channel_layout_extract_channel(unused, 0); yading@10: inputs[i] |= ch->in_channel; yading@10: return; yading@10: } yading@10: } yading@10: } yading@10: yading@10: static int join_config_output(AVFilterLink *outlink) yading@10: { yading@10: AVFilterContext *ctx = outlink->src; yading@10: JoinContext *s = ctx->priv; yading@10: uint64_t *inputs; // nth element tracks which channels are used from nth input yading@10: int i, ret = 0; yading@10: yading@10: /* initialize inputs to user-specified mappings */ yading@10: if (!(inputs = av_mallocz(sizeof(*inputs) * ctx->nb_inputs))) yading@10: return AVERROR(ENOMEM); yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: ChannelMap *ch = &s->channels[i]; yading@10: AVFilterLink *inlink; yading@10: yading@10: if (ch->input < 0) yading@10: continue; yading@10: yading@10: inlink = ctx->inputs[ch->input]; yading@10: yading@10: if (!ch->in_channel) yading@10: ch->in_channel = av_channel_layout_extract_channel(inlink->channel_layout, yading@10: ch->in_channel_idx); yading@10: yading@10: if (!(ch->in_channel & inlink->channel_layout)) { yading@10: av_log(ctx, AV_LOG_ERROR, "Requested channel %s is not present in " yading@10: "input stream #%d.\n", av_get_channel_name(ch->in_channel), yading@10: ch->input); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: yading@10: inputs[ch->input] |= ch->in_channel; yading@10: } yading@10: yading@10: /* guess channel maps when not explicitly defined */ yading@10: /* first try unused matching channels */ yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: ChannelMap *ch = &s->channels[i]; yading@10: yading@10: if (ch->input < 0) yading@10: guess_map_matching(ctx, ch, inputs); yading@10: } yading@10: yading@10: /* if the above failed, try to find _any_ unused input channel */ yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: ChannelMap *ch = &s->channels[i]; yading@10: yading@10: if (ch->input < 0) yading@10: guess_map_any(ctx, ch, inputs); yading@10: yading@10: if (ch->input < 0) { yading@10: av_log(ctx, AV_LOG_ERROR, "Could not find input channel for " yading@10: "output channel '%s'.\n", yading@10: av_get_channel_name(ch->out_channel)); yading@10: goto fail; yading@10: } yading@10: yading@10: ch->in_channel_idx = av_get_channel_layout_channel_index(ctx->inputs[ch->input]->channel_layout, yading@10: ch->in_channel); yading@10: } yading@10: yading@10: /* print mappings */ yading@10: av_log(ctx, AV_LOG_VERBOSE, "mappings: "); yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: ChannelMap *ch = &s->channels[i]; yading@10: av_log(ctx, AV_LOG_VERBOSE, "%d.%s => %s ", ch->input, yading@10: av_get_channel_name(ch->in_channel), yading@10: av_get_channel_name(ch->out_channel)); yading@10: } yading@10: av_log(ctx, AV_LOG_VERBOSE, "\n"); yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: if (!inputs[i]) yading@10: av_log(ctx, AV_LOG_WARNING, "No channels are used from input " yading@10: "stream %d.\n", i); yading@10: } yading@10: yading@10: fail: yading@10: av_freep(&inputs); yading@10: return ret; yading@10: } yading@10: yading@10: static int join_request_frame(AVFilterLink *outlink) yading@10: { yading@10: AVFilterContext *ctx = outlink->src; yading@10: JoinContext *s = ctx->priv; yading@10: AVFrame *frame; yading@10: int linesize = INT_MAX; yading@10: int nb_samples = 0; yading@10: int nb_buffers = 0; yading@10: int i, j, ret; yading@10: yading@10: /* get a frame on each input */ yading@10: for (i = 0; i < ctx->nb_inputs; i++) { yading@10: AVFilterLink *inlink = ctx->inputs[i]; yading@10: yading@10: if (!s->input_frames[i] && yading@10: (ret = ff_request_frame(inlink)) < 0) yading@10: return ret; yading@10: yading@10: /* request the same number of samples on all inputs */ yading@10: if (i == 0) { yading@10: nb_samples = s->input_frames[0]->nb_samples; yading@10: yading@10: for (j = 1; !i && j < ctx->nb_inputs; j++) yading@10: ctx->inputs[j]->request_samples = nb_samples; yading@10: } yading@10: } yading@10: yading@10: /* setup the output frame */ yading@10: frame = av_frame_alloc(); yading@10: if (!frame) yading@10: return AVERROR(ENOMEM); yading@10: if (s->nb_channels > FF_ARRAY_ELEMS(frame->data)) { yading@10: frame->extended_data = av_mallocz(s->nb_channels * yading@10: sizeof(*frame->extended_data)); yading@10: if (!frame->extended_data) { yading@10: ret = AVERROR(ENOMEM); yading@10: goto fail; yading@10: } yading@10: } yading@10: yading@10: /* copy the data pointers */ yading@10: for (i = 0; i < s->nb_channels; i++) { yading@10: ChannelMap *ch = &s->channels[i]; yading@10: AVFrame *cur = s->input_frames[ch->input]; yading@10: AVBufferRef *buf; yading@10: yading@10: frame->extended_data[i] = cur->extended_data[ch->in_channel_idx]; yading@10: linesize = FFMIN(linesize, cur->linesize[0]); yading@10: yading@10: /* add the buffer where this plan is stored to the list if it's yading@10: * not already there */ yading@10: buf = av_frame_get_plane_buffer(cur, ch->in_channel_idx); yading@10: if (!buf) { yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: for (j = 0; j < nb_buffers; j++) yading@10: if (s->buffers[j]->buffer == buf->buffer) yading@10: break; yading@10: if (j == i) yading@10: s->buffers[nb_buffers++] = buf; yading@10: } yading@10: yading@10: /* create references to the buffers we copied to output */ yading@10: if (nb_buffers > FF_ARRAY_ELEMS(frame->buf)) { yading@10: frame->nb_extended_buf = nb_buffers - FF_ARRAY_ELEMS(frame->buf); yading@10: frame->extended_buf = av_mallocz(sizeof(*frame->extended_buf) * yading@10: frame->nb_extended_buf); yading@10: if (!frame->extended_buf) { yading@10: frame->nb_extended_buf = 0; yading@10: ret = AVERROR(ENOMEM); yading@10: goto fail; yading@10: } yading@10: } yading@10: for (i = 0; i < FFMIN(FF_ARRAY_ELEMS(frame->buf), nb_buffers); i++) { yading@10: frame->buf[i] = av_buffer_ref(s->buffers[i]); yading@10: if (!frame->buf[i]) { yading@10: ret = AVERROR(ENOMEM); yading@10: goto fail; yading@10: } yading@10: } yading@10: for (i = 0; i < frame->nb_extended_buf; i++) { yading@10: frame->extended_buf[i] = av_buffer_ref(s->buffers[i + yading@10: FF_ARRAY_ELEMS(frame->buf)]); yading@10: if (!frame->extended_buf[i]) { yading@10: ret = AVERROR(ENOMEM); yading@10: goto fail; yading@10: } yading@10: } yading@10: yading@10: frame->nb_samples = nb_samples; yading@10: frame->channel_layout = outlink->channel_layout; yading@10: av_frame_set_channels(frame, outlink->channels); yading@10: frame->format = outlink->format; yading@10: frame->sample_rate = outlink->sample_rate; yading@10: frame->pts = s->input_frames[0]->pts; yading@10: frame->linesize[0] = linesize; yading@10: if (frame->data != frame->extended_data) { yading@10: memcpy(frame->data, frame->extended_data, sizeof(*frame->data) * yading@10: FFMIN(FF_ARRAY_ELEMS(frame->data), s->nb_channels)); yading@10: } yading@10: yading@10: ret = ff_filter_frame(outlink, frame); yading@10: yading@10: for (i = 0; i < ctx->nb_inputs; i++) yading@10: av_frame_free(&s->input_frames[i]); yading@10: yading@10: return ret; yading@10: yading@10: fail: yading@10: av_frame_free(&frame); yading@10: return ret; yading@10: } yading@10: yading@10: static const AVFilterPad avfilter_af_join_outputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_AUDIO, yading@10: .config_props = join_config_output, yading@10: .request_frame = join_request_frame, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFilter avfilter_af_join = { yading@10: .name = "join", yading@10: .description = NULL_IF_CONFIG_SMALL("Join multiple audio streams into " yading@10: "multi-channel output."), yading@10: .priv_size = sizeof(JoinContext), yading@10: .priv_class = &join_class, yading@10: yading@10: .init = join_init, yading@10: .uninit = join_uninit, yading@10: .query_formats = join_query_formats, yading@10: yading@10: .inputs = NULL, yading@10: .outputs = avfilter_af_join_outputs, yading@10: yading@10: .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, yading@10: };