yading@10: /* yading@10: * Copyright (c) 2002 Anders Johansson yading@10: * Copyright (c) 2011 Clément Bœsch yading@10: * Copyright (c) 2011 Nicolas George yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg 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: * FFmpeg 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 yading@10: * GNU 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 FFmpeg; 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 panning filter (channels mixing) yading@10: * Original code written by Anders Johansson for MPlayer, yading@10: * reimplemented for FFmpeg. yading@10: */ yading@10: yading@10: #include yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/channel_layout.h" yading@10: #include "libavutil/opt.h" yading@10: #include "libswresample/swresample.h" yading@10: #include "audio.h" yading@10: #include "avfilter.h" yading@10: #include "formats.h" yading@10: #include "internal.h" yading@10: yading@10: #define MAX_CHANNELS 63 yading@10: yading@10: typedef struct PanContext { yading@10: const AVClass *class; yading@10: char *args; yading@10: int64_t out_channel_layout; yading@10: double gain[MAX_CHANNELS][MAX_CHANNELS]; yading@10: int64_t need_renorm; yading@10: int need_renumber; yading@10: int nb_input_channels; yading@10: int nb_output_channels; yading@10: yading@10: int pure_gains; yading@10: /* channel mapping specific */ yading@10: int channel_map[SWR_CH_MAX]; yading@10: struct SwrContext *swr; yading@10: } PanContext; yading@10: yading@10: static void skip_spaces(char **arg) yading@10: { yading@10: int len = 0; yading@10: yading@10: sscanf(*arg, " %n", &len); yading@10: *arg += len; yading@10: } yading@10: yading@10: static int parse_channel_name(char **arg, int *rchannel, int *rnamed) yading@10: { yading@10: char buf[8]; yading@10: int len, i, channel_id = 0; yading@10: int64_t layout, layout0; yading@10: yading@10: skip_spaces(arg); yading@10: /* try to parse a channel name, e.g. "FL" */ yading@10: if (sscanf(*arg, "%7[A-Z]%n", buf, &len)) { yading@10: layout0 = layout = av_get_channel_layout(buf); yading@10: /* channel_id <- first set bit in layout */ yading@10: for (i = 32; i > 0; i >>= 1) { yading@10: if (layout >= (int64_t)1 << i) { yading@10: channel_id += i; yading@10: layout >>= i; yading@10: } yading@10: } yading@10: /* reject layouts that are not a single channel */ yading@10: if (channel_id >= MAX_CHANNELS || layout0 != (int64_t)1 << channel_id) yading@10: return AVERROR(EINVAL); yading@10: *rchannel = channel_id; yading@10: *rnamed = 1; yading@10: *arg += len; yading@10: return 0; yading@10: } yading@10: /* try to parse a channel number, e.g. "c2" */ yading@10: if (sscanf(*arg, "c%d%n", &channel_id, &len) && yading@10: channel_id >= 0 && channel_id < MAX_CHANNELS) { yading@10: *rchannel = channel_id; yading@10: *rnamed = 0; yading@10: *arg += len; yading@10: return 0; yading@10: } yading@10: return AVERROR(EINVAL); yading@10: } yading@10: yading@10: static av_cold int init(AVFilterContext *ctx) yading@10: { yading@10: PanContext *const pan = ctx->priv; yading@10: char *arg, *arg0, *tokenizer, *args = av_strdup(pan->args); yading@10: int out_ch_id, in_ch_id, len, named, ret; yading@10: int nb_in_channels[2] = { 0, 0 }; // number of unnamed and named input channels yading@10: double gain; yading@10: yading@10: if (!pan->args) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "pan filter needs a channel layout and a set " yading@10: "of channels definitions as parameter\n"); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: if (!args) yading@10: return AVERROR(ENOMEM); yading@10: arg = av_strtok(args, "|", &tokenizer); yading@10: ret = ff_parse_channel_layout(&pan->out_channel_layout, arg, ctx); yading@10: if (ret < 0) yading@10: goto fail; yading@10: pan->nb_output_channels = av_get_channel_layout_nb_channels(pan->out_channel_layout); yading@10: yading@10: /* parse channel specifications */ yading@10: while ((arg = arg0 = av_strtok(NULL, "|", &tokenizer))) { yading@10: /* channel name */ yading@10: if (parse_channel_name(&arg, &out_ch_id, &named)) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Expected out channel name, got \"%.8s\"\n", arg); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: if (named) { yading@10: if (!((pan->out_channel_layout >> out_ch_id) & 1)) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Channel \"%.8s\" does not exist in the chosen layout\n", arg0); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: /* get the channel number in the output channel layout: yading@10: * out_channel_layout & ((1 << out_ch_id) - 1) are all the yading@10: * channels that come before out_ch_id, yading@10: * so their count is the index of out_ch_id */ yading@10: out_ch_id = av_get_channel_layout_nb_channels(pan->out_channel_layout & (((int64_t)1 << out_ch_id) - 1)); yading@10: } yading@10: if (out_ch_id < 0 || out_ch_id >= pan->nb_output_channels) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Invalid out channel name \"%.8s\"\n", arg0); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: skip_spaces(&arg); yading@10: if (*arg == '=') { yading@10: arg++; yading@10: } else if (*arg == '<') { yading@10: pan->need_renorm |= (int64_t)1 << out_ch_id; yading@10: arg++; yading@10: } else { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Syntax error after channel name in \"%.8s\"\n", arg0); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: /* gains */ yading@10: while (1) { yading@10: gain = 1; yading@10: if (sscanf(arg, "%lf%n *%n", &gain, &len, &len)) yading@10: arg += len; yading@10: if (parse_channel_name(&arg, &in_ch_id, &named)){ yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Expected in channel name, got \"%.8s\"\n", arg); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: nb_in_channels[named]++; yading@10: if (nb_in_channels[!named]) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Can not mix named and numbered channels\n"); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: pan->gain[out_ch_id][in_ch_id] = gain; yading@10: skip_spaces(&arg); yading@10: if (!*arg) yading@10: break; yading@10: if (*arg != '+') { yading@10: av_log(ctx, AV_LOG_ERROR, "Syntax error near \"%.8s\"\n", arg); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: arg++; yading@10: } yading@10: } yading@10: pan->need_renumber = !!nb_in_channels[1]; yading@10: yading@10: ret = 0; yading@10: fail: yading@10: av_free(args); yading@10: return ret; yading@10: } yading@10: yading@10: static int are_gains_pure(const PanContext *pan) yading@10: { yading@10: int i, j; yading@10: yading@10: for (i = 0; i < MAX_CHANNELS; i++) { yading@10: int nb_gain = 0; yading@10: yading@10: for (j = 0; j < MAX_CHANNELS; j++) { yading@10: double gain = pan->gain[i][j]; yading@10: yading@10: /* channel mapping is effective only if 0% or 100% of a channel is yading@10: * selected... */ yading@10: if (gain != 0. && gain != 1.) yading@10: return 0; yading@10: /* ...and if the output channel is only composed of one input */ yading@10: if (gain && nb_gain++) yading@10: return 0; yading@10: } yading@10: } yading@10: return 1; yading@10: } yading@10: yading@10: static int query_formats(AVFilterContext *ctx) yading@10: { yading@10: PanContext *pan = ctx->priv; yading@10: AVFilterLink *inlink = ctx->inputs[0]; yading@10: AVFilterLink *outlink = ctx->outputs[0]; yading@10: AVFilterFormats *formats = NULL; yading@10: AVFilterChannelLayouts *layouts; yading@10: yading@10: pan->pure_gains = are_gains_pure(pan); yading@10: /* libswr supports any sample and packing formats */ yading@10: ff_set_common_formats(ctx, ff_all_formats(AVMEDIA_TYPE_AUDIO)); yading@10: yading@10: formats = ff_all_samplerates(); yading@10: if (!formats) yading@10: return AVERROR(ENOMEM); yading@10: ff_set_common_samplerates(ctx, formats); yading@10: yading@10: // inlink supports any channel layout yading@10: layouts = ff_all_channel_layouts(); yading@10: ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts); yading@10: yading@10: // outlink supports only requested output channel layout yading@10: layouts = NULL; yading@10: ff_add_channel_layout(&layouts, pan->out_channel_layout); yading@10: ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts); yading@10: return 0; yading@10: } yading@10: yading@10: static int config_props(AVFilterLink *link) yading@10: { yading@10: AVFilterContext *ctx = link->dst; yading@10: PanContext *pan = ctx->priv; yading@10: char buf[1024], *cur; yading@10: int i, j, k, r; yading@10: double t; yading@10: yading@10: pan->nb_input_channels = av_get_channel_layout_nb_channels(link->channel_layout); yading@10: if (pan->need_renumber) { yading@10: // input channels were given by their name: renumber them yading@10: for (i = j = 0; i < MAX_CHANNELS; i++) { yading@10: if ((link->channel_layout >> i) & 1) { yading@10: for (k = 0; k < pan->nb_output_channels; k++) yading@10: pan->gain[k][j] = pan->gain[k][i]; yading@10: j++; yading@10: } yading@10: } yading@10: } yading@10: yading@10: // sanity check; can't be done in query_formats since the inlink yading@10: // channel layout is unknown at that time yading@10: if (pan->nb_input_channels > SWR_CH_MAX || yading@10: pan->nb_output_channels > SWR_CH_MAX) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "libswresample support a maximum of %d channels. " yading@10: "Feel free to ask for a higher limit.\n", SWR_CH_MAX); yading@10: return AVERROR_PATCHWELCOME; yading@10: } yading@10: yading@10: // init libswresample context yading@10: pan->swr = swr_alloc_set_opts(pan->swr, yading@10: pan->out_channel_layout, link->format, link->sample_rate, yading@10: link->channel_layout, link->format, link->sample_rate, yading@10: 0, ctx); yading@10: if (!pan->swr) yading@10: return AVERROR(ENOMEM); yading@10: yading@10: // gains are pure, init the channel mapping yading@10: if (pan->pure_gains) { yading@10: yading@10: // get channel map from the pure gains yading@10: for (i = 0; i < pan->nb_output_channels; i++) { yading@10: int ch_id = -1; yading@10: for (j = 0; j < pan->nb_input_channels; j++) { yading@10: if (pan->gain[i][j]) { yading@10: ch_id = j; yading@10: break; yading@10: } yading@10: } yading@10: pan->channel_map[i] = ch_id; yading@10: } yading@10: yading@10: av_opt_set_int(pan->swr, "icl", pan->out_channel_layout, 0); yading@10: av_opt_set_int(pan->swr, "uch", pan->nb_output_channels, 0); yading@10: swr_set_channel_mapping(pan->swr, pan->channel_map); yading@10: } else { yading@10: // renormalize yading@10: for (i = 0; i < pan->nb_output_channels; i++) { yading@10: if (!((pan->need_renorm >> i) & 1)) yading@10: continue; yading@10: t = 0; yading@10: for (j = 0; j < pan->nb_input_channels; j++) yading@10: t += pan->gain[i][j]; yading@10: if (t > -1E-5 && t < 1E-5) { yading@10: // t is almost 0 but not exactly, this is probably a mistake yading@10: if (t) yading@10: av_log(ctx, AV_LOG_WARNING, yading@10: "Degenerate coefficients while renormalizing\n"); yading@10: continue; yading@10: } yading@10: for (j = 0; j < pan->nb_input_channels; j++) yading@10: pan->gain[i][j] /= t; yading@10: } yading@10: av_opt_set_int(pan->swr, "icl", link->channel_layout, 0); yading@10: av_opt_set_int(pan->swr, "ocl", pan->out_channel_layout, 0); yading@10: swr_set_matrix(pan->swr, pan->gain[0], pan->gain[1] - pan->gain[0]); yading@10: } yading@10: yading@10: r = swr_init(pan->swr); yading@10: if (r < 0) yading@10: return r; yading@10: yading@10: // summary yading@10: for (i = 0; i < pan->nb_output_channels; i++) { yading@10: cur = buf; yading@10: for (j = 0; j < pan->nb_input_channels; j++) { yading@10: r = snprintf(cur, buf + sizeof(buf) - cur, "%s%.3g i%d", yading@10: j ? " + " : "", pan->gain[i][j], j); yading@10: cur += FFMIN(buf + sizeof(buf) - cur, r); yading@10: } yading@10: av_log(ctx, AV_LOG_VERBOSE, "o%d = %s\n", i, buf); yading@10: } yading@10: // add channel mapping summary if possible yading@10: if (pan->pure_gains) { yading@10: av_log(ctx, AV_LOG_INFO, "Pure channel mapping detected:"); yading@10: for (i = 0; i < pan->nb_output_channels; i++) yading@10: if (pan->channel_map[i] < 0) yading@10: av_log(ctx, AV_LOG_INFO, " M"); yading@10: else yading@10: av_log(ctx, AV_LOG_INFO, " %d", pan->channel_map[i]); yading@10: av_log(ctx, AV_LOG_INFO, "\n"); yading@10: return 0; yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) yading@10: { yading@10: int ret; yading@10: int n = insamples->nb_samples; yading@10: AVFilterLink *const outlink = inlink->dst->outputs[0]; yading@10: AVFrame *outsamples = ff_get_audio_buffer(outlink, n); yading@10: PanContext *pan = inlink->dst->priv; yading@10: yading@10: if (!outsamples) yading@10: return AVERROR(ENOMEM); yading@10: swr_convert(pan->swr, outsamples->data, n, (void *)insamples->data, n); yading@10: av_frame_copy_props(outsamples, insamples); yading@10: outsamples->channel_layout = outlink->channel_layout; yading@10: av_frame_set_channels(outsamples, outlink->channels); yading@10: yading@10: ret = ff_filter_frame(outlink, outsamples); yading@10: av_frame_free(&insamples); yading@10: return ret; yading@10: } yading@10: yading@10: static av_cold void uninit(AVFilterContext *ctx) yading@10: { yading@10: PanContext *pan = ctx->priv; yading@10: swr_free(&pan->swr); yading@10: } yading@10: yading@10: #define OFFSET(x) offsetof(PanContext, x) yading@10: yading@10: static const AVOption pan_options[] = { yading@10: { "args", NULL, OFFSET(args), AV_OPT_TYPE_STRING, { .str = NULL }, CHAR_MIN, CHAR_MAX, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFILTER_DEFINE_CLASS(pan); yading@10: yading@10: yading@10: static const AVFilterPad pan_inputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_AUDIO, yading@10: .config_props = config_props, yading@10: .filter_frame = filter_frame, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: static const AVFilterPad pan_outputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_AUDIO, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFilter avfilter_af_pan = { yading@10: .name = "pan", yading@10: .description = NULL_IF_CONFIG_SMALL("Remix channels with coefficients (panning)."), yading@10: .priv_size = sizeof(PanContext), yading@10: .priv_class = &pan_class, yading@10: .init = init, yading@10: .uninit = uninit, yading@10: .query_formats = query_formats, yading@10: .inputs = pan_inputs, yading@10: .outputs = pan_outputs, yading@10: };