X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Faf_amix.c;h=ec2556f920742d4655e9d3d72b73928ac383f929;hb=a04ad248a05e7b613abe09b3bb067f555108d794;hp=ec764375c0b6a973806fb07b3bb73f36d1d022f3;hpb=061337a073236cb0bc6c56036f50f883d2887681;p=ffmpeg diff --git a/libavfilter/af_amix.c b/libavfilter/af_amix.c index ec764375c0b..2296b49243a 100644 --- a/libavfilter/af_amix.c +++ b/libavfilter/af_amix.c @@ -34,6 +34,7 @@ #include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/common.h" +#include "libavutil/eval.h" #include "libavutil/float_dsp.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" @@ -162,6 +163,8 @@ typedef struct MixContext { int active_inputs; /**< number of input currently active */ int duration_mode; /**< mode for determining duration */ float dropout_transition; /**< transition time when an input drops out */ + char *weights_str; /**< string for custom weights for every input */ + int normalize; /**< if inputs are scaled */ int nb_channels; /**< number of channels */ int sample_rate; /**< sample rate */ @@ -169,7 +172,9 @@ typedef struct MixContext { AVAudioFifo **fifos; /**< audio fifo for each input */ uint8_t *input_state; /**< current state of each input */ float *input_scale; /**< mixing scale factor for each input */ - float scale_norm; /**< normalization factor for all inputs */ + float *weights; /**< custom weights for every input */ + float weight_sum; /**< sum of custom weights for every input */ + float *scale_norm; /**< normalization factor for every input */ int64_t next_pts; /**< calculated pts for next output frame */ FrameList *frame_list; /**< list of frame info for the first input */ } MixContext; @@ -177,9 +182,10 @@ typedef struct MixContext { #define OFFSET(x) offsetof(MixContext, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define F AV_OPT_FLAG_FILTERING_PARAM +#define T AV_OPT_FLAG_RUNTIME_PARAM static const AVOption amix_options[] = { { "inputs", "Number of inputs.", - OFFSET(nb_inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, 1024, A|F }, + OFFSET(nb_inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT16_MAX, A|F }, { "duration", "How to determine the end-of-stream.", OFFSET(duration_mode), AV_OPT_TYPE_INT, { .i64 = DURATION_LONGEST }, 0, 2, A|F, "duration" }, { "longest", "Duration of longest input.", 0, AV_OPT_TYPE_CONST, { .i64 = DURATION_LONGEST }, 0, 0, A|F, "duration" }, @@ -188,6 +194,10 @@ static const AVOption amix_options[] = { { "dropout_transition", "Transition time, in seconds, for volume " "renormalization when an input stream ends.", OFFSET(dropout_transition), AV_OPT_TYPE_FLOAT, { .dbl = 2.0 }, 0, INT_MAX, A|F }, + { "weights", "Set weight for each input.", + OFFSET(weights_str), AV_OPT_TYPE_STRING, {.str="1 1"}, 0, 0, A|F|T }, + { "normalize", "Scale inputs", + OFFSET(normalize), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, A|F|T }, { NULL } }; @@ -202,18 +212,32 @@ AVFILTER_DEFINE_CLASS(amix); */ static void calculate_scales(MixContext *s, int nb_samples) { + float weight_sum = 0.f; int i; - if (s->scale_norm > s->active_inputs) { - s->scale_norm -= nb_samples / (s->dropout_transition * s->sample_rate); - s->scale_norm = FFMAX(s->scale_norm, s->active_inputs); + for (i = 0; i < s->nb_inputs; i++) + if (s->input_state[i] & INPUT_ON) + weight_sum += FFABS(s->weights[i]); + + for (i = 0; i < s->nb_inputs; i++) { + if (s->input_state[i] & INPUT_ON) { + if (s->scale_norm[i] > weight_sum / FFABS(s->weights[i])) { + s->scale_norm[i] -= ((s->weight_sum / FFABS(s->weights[i])) / s->nb_inputs) * + nb_samples / (s->dropout_transition * s->sample_rate); + s->scale_norm[i] = FFMAX(s->scale_norm[i], weight_sum / FFABS(s->weights[i])); + } + } } for (i = 0; i < s->nb_inputs; i++) { - if (s->input_state[i] & INPUT_ON) - s->input_scale[i] = 1.0f / s->scale_norm; - else + if (s->input_state[i] & INPUT_ON) { + if (!s->normalize) + s->input_scale[i] = FFABS(s->weights[i]); + else + s->input_scale[i] = 1.0f / s->scale_norm[i] * FFSIGN(s->weights[i]); + } else { s->input_scale[i] = 0.0f; + } } } @@ -251,9 +275,11 @@ static int config_output(AVFilterLink *outlink) s->active_inputs = s->nb_inputs; s->input_scale = av_mallocz_array(s->nb_inputs, sizeof(*s->input_scale)); - if (!s->input_scale) + s->scale_norm = av_mallocz_array(s->nb_inputs, sizeof(*s->scale_norm)); + if (!s->input_scale || !s->scale_norm) return AVERROR(ENOMEM); - s->scale_norm = s->active_inputs; + for (i = 0; i < s->nb_inputs; i++) + s->scale_norm[i] = s->weight_sum / FFABS(s->weights[i]); calculate_scales(s, 0); av_get_channel_layout_string(buf, sizeof(buf), -1, outlink->channel_layout); @@ -290,6 +316,8 @@ static int output_frame(AVFilterLink *outlink) } } } + + s->next_pts = frame_list_next_pts(s->frame_list); } else { /* first input closed: use the available samples */ nb_samples = INT_MAX; @@ -305,7 +333,6 @@ static int output_frame(AVFilterLink *outlink) } } - s->next_pts = frame_list_next_pts(s->frame_list); frame_list_remove_samples(s->frame_list, nb_samples); calculate_scales(s, nb_samples); @@ -408,6 +435,8 @@ static int activate(AVFilterContext *ctx) AVFrame *buf = NULL; int i, ret; + FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, ctx); + for (i = 0; i < s->nb_inputs; i++) { AVFilterLink *inlink = ctx->inputs[i]; @@ -484,6 +513,33 @@ static int activate(AVFilterContext *ctx) return 0; } +static void parse_weights(AVFilterContext *ctx) +{ + MixContext *s = ctx->priv; + float last_weight = 1.f; + char *p; + int i; + + s->weight_sum = 0.f; + p = s->weights_str; + for (i = 0; i < s->nb_inputs; i++) { + last_weight = av_strtod(p, &p); + s->weights[i] = last_weight; + s->weight_sum += FFABS(last_weight); + if (p && *p) { + p++; + } else { + i++; + break; + } + } + + for (; i < s->nb_inputs; i++) { + s->weights[i] = last_weight; + s->weight_sum += FFABS(last_weight); + } +} + static av_cold int init(AVFilterContext *ctx) { MixContext *s = ctx->priv; @@ -507,6 +563,12 @@ static av_cold int init(AVFilterContext *ctx) if (!s->fdsp) return AVERROR(ENOMEM); + s->weights = av_mallocz_array(s->nb_inputs, sizeof(*s->weights)); + if (!s->weights) + return AVERROR(ENOMEM); + + parse_weights(ctx); + return 0; } @@ -524,6 +586,8 @@ static av_cold void uninit(AVFilterContext *ctx) av_freep(&s->frame_list); av_freep(&s->input_state); av_freep(&s->input_scale); + av_freep(&s->scale_norm); + av_freep(&s->weights); av_freep(&s->fdsp); for (i = 0; i < ctx->nb_inputs; i++) @@ -532,30 +596,36 @@ static av_cold void uninit(AVFilterContext *ctx) static int query_formats(AVFilterContext *ctx) { - AVFilterFormats *formats = NULL; - AVFilterChannelLayouts *layouts; + static const enum AVSampleFormat sample_fmts[] = { + AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP, + AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBLP, + AV_SAMPLE_FMT_NONE + }; int ret; - layouts = ff_all_channel_counts(); - if (!layouts) { - ret = AVERROR(ENOMEM); - goto fail; - } - - if ((ret = ff_add_format(&formats, AV_SAMPLE_FMT_FLT )) < 0 || - (ret = ff_add_format(&formats, AV_SAMPLE_FMT_FLTP)) < 0 || - (ret = ff_add_format(&formats, AV_SAMPLE_FMT_DBL )) < 0 || - (ret = ff_add_format(&formats, AV_SAMPLE_FMT_DBLP)) < 0 || - (ret = ff_set_common_formats (ctx, formats)) < 0 || - (ret = ff_set_common_channel_layouts(ctx, layouts)) < 0 || + if ((ret = ff_set_common_formats(ctx, ff_make_format_list(sample_fmts))) < 0 || (ret = ff_set_common_samplerates(ctx, ff_all_samplerates())) < 0) - goto fail; + return ret; + + return ff_set_common_channel_layouts(ctx, ff_all_channel_counts()); +} + +static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, + char *res, int res_len, int flags) +{ + MixContext *s = ctx->priv; + int ret; + + ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags); + if (ret < 0) + return ret; + + parse_weights(ctx); + for (int i = 0; i < s->nb_inputs; i++) + s->scale_norm[i] = s->weight_sum / FFABS(s->weights[i]); + calculate_scales(s, 0); + return 0; -fail: - if (layouts) - av_freep(&layouts->channel_layouts); - av_freep(&layouts); - return ret; } static const AVFilterPad avfilter_af_amix_outputs[] = { @@ -567,7 +637,7 @@ static const AVFilterPad avfilter_af_amix_outputs[] = { { NULL } }; -AVFilter ff_af_amix = { +const AVFilter ff_af_amix = { .name = "amix", .description = NULL_IF_CONFIG_SMALL("Audio mixing."), .priv_size = sizeof(MixContext), @@ -578,5 +648,6 @@ AVFilter ff_af_amix = { .query_formats = query_formats, .inputs = NULL, .outputs = avfilter_af_amix_outputs, + .process_command = process_command, .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, };