| /* |
| * Copyright (c) 2011 Stefano Sabatini |
| * Copyright (c) 2011 Mina Nagy Zaki |
| * |
| * This file is part of FFmpeg. |
| * |
| * FFmpeg is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * FFmpeg is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with FFmpeg; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /** |
| * @file |
| * resampling audio filter |
| */ |
| |
| #include "libavutil/eval.h" |
| #include "libavcodec/avcodec.h" |
| #include "avfilter.h" |
| #include "internal.h" |
| |
| typedef struct { |
| struct AVResampleContext *resample; |
| int out_rate; |
| double ratio; |
| AVFilterBufferRef *outsamplesref; |
| int unconsumed_nb_samples, |
| max_cached_nb_samples; |
| int16_t *cached_data[8], |
| *resampled_data[8]; |
| } AResampleContext; |
| |
| static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
| { |
| AResampleContext *aresample = ctx->priv; |
| int ret; |
| |
| if (args) { |
| if ((ret = ff_parse_sample_rate(&aresample->out_rate, args, ctx)) < 0) |
| return ret; |
| } else { |
| aresample->out_rate = -1; |
| } |
| |
| return 0; |
| } |
| |
| static av_cold void uninit(AVFilterContext *ctx) |
| { |
| AResampleContext *aresample = ctx->priv; |
| if (aresample->outsamplesref) { |
| int nb_channels = |
| av_get_channel_layout_nb_channels( |
| aresample->outsamplesref->audio->channel_layout); |
| avfilter_unref_buffer(aresample->outsamplesref); |
| while (nb_channels--) { |
| av_freep(&(aresample->cached_data[nb_channels])); |
| av_freep(&(aresample->resampled_data[nb_channels])); |
| } |
| } |
| |
| if (aresample->resample) |
| av_resample_close(aresample->resample); |
| } |
| |
| static int config_output(AVFilterLink *outlink) |
| { |
| AVFilterContext *ctx = outlink->src; |
| AVFilterLink *inlink = ctx->inputs[0]; |
| AResampleContext *aresample = ctx->priv; |
| |
| if (aresample->out_rate == -1) |
| aresample->out_rate = outlink->sample_rate; |
| else |
| outlink->sample_rate = aresample->out_rate; |
| outlink->time_base = (AVRational) {1, aresample->out_rate}; |
| |
| //TODO: make the resampling parameters configurable |
| aresample->resample = av_resample_init(aresample->out_rate, inlink->sample_rate, |
| 16, 10, 0, 0.8); |
| |
| aresample->ratio = (double)outlink->sample_rate / inlink->sample_rate; |
| |
| av_log(ctx, AV_LOG_INFO, "r:%"PRId64"Hz -> r:%"PRId64"Hz\n", |
| inlink->sample_rate, outlink->sample_rate); |
| return 0; |
| } |
| |
| static int query_formats(AVFilterContext *ctx) |
| { |
| AVFilterFormats *formats = NULL; |
| |
| avfilter_add_format(&formats, AV_SAMPLE_FMT_S16); |
| if (!formats) |
| return AVERROR(ENOMEM); |
| avfilter_set_common_sample_formats(ctx, formats); |
| |
| formats = avfilter_make_all_channel_layouts(); |
| if (!formats) |
| return AVERROR(ENOMEM); |
| avfilter_set_common_channel_layouts(ctx, formats); |
| |
| formats = avfilter_make_all_packing_formats(); |
| if (!formats) |
| return AVERROR(ENOMEM); |
| avfilter_set_common_packing_formats(ctx, formats); |
| |
| return 0; |
| } |
| |
| static void deinterleave(int16_t **outp, int16_t *in, |
| int nb_channels, int nb_samples) |
| { |
| int16_t *out[8]; |
| memcpy(out, outp, nb_channels * sizeof(int16_t*)); |
| |
| switch (nb_channels) { |
| case 2: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| } |
| break; |
| case 3: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| *out[2]++ = *in++; |
| } |
| break; |
| case 4: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| *out[2]++ = *in++; |
| *out[3]++ = *in++; |
| } |
| break; |
| case 5: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| *out[2]++ = *in++; |
| *out[3]++ = *in++; |
| *out[4]++ = *in++; |
| } |
| break; |
| case 6: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| *out[2]++ = *in++; |
| *out[3]++ = *in++; |
| *out[4]++ = *in++; |
| *out[5]++ = *in++; |
| } |
| break; |
| case 8: |
| while (nb_samples--) { |
| *out[0]++ = *in++; |
| *out[1]++ = *in++; |
| *out[2]++ = *in++; |
| *out[3]++ = *in++; |
| *out[4]++ = *in++; |
| *out[5]++ = *in++; |
| *out[6]++ = *in++; |
| *out[7]++ = *in++; |
| } |
| break; |
| } |
| } |
| |
| static void interleave(int16_t *out, int16_t **inp, |
| int nb_channels, int nb_samples) |
| { |
| int16_t *in[8]; |
| memcpy(in, inp, nb_channels * sizeof(int16_t*)); |
| |
| switch (nb_channels) { |
| case 2: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| } |
| break; |
| case 3: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| *out++ = *in[2]++; |
| } |
| break; |
| case 4: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| *out++ = *in[2]++; |
| *out++ = *in[3]++; |
| } |
| break; |
| case 5: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| *out++ = *in[2]++; |
| *out++ = *in[3]++; |
| *out++ = *in[4]++; |
| } |
| break; |
| case 6: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| *out++ = *in[2]++; |
| *out++ = *in[3]++; |
| *out++ = *in[4]++; |
| *out++ = *in[5]++; |
| } |
| break; |
| case 8: |
| while (nb_samples--) { |
| *out++ = *in[0]++; |
| *out++ = *in[1]++; |
| *out++ = *in[2]++; |
| *out++ = *in[3]++; |
| *out++ = *in[4]++; |
| *out++ = *in[5]++; |
| *out++ = *in[6]++; |
| *out++ = *in[7]++; |
| } |
| break; |
| } |
| } |
| |
| static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *insamplesref) |
| { |
| AResampleContext *aresample = inlink->dst->priv; |
| AVFilterLink * const outlink = inlink->dst->outputs[0]; |
| int i, |
| in_nb_samples = insamplesref->audio->nb_samples, |
| cached_nb_samples = in_nb_samples + aresample->unconsumed_nb_samples, |
| requested_out_nb_samples = aresample->ratio * cached_nb_samples, |
| nb_channels = |
| av_get_channel_layout_nb_channels(inlink->channel_layout); |
| |
| if (cached_nb_samples > aresample->max_cached_nb_samples) { |
| for (i = 0; i < nb_channels; i++) { |
| aresample->cached_data[i] = |
| av_realloc(aresample->cached_data[i], cached_nb_samples * sizeof(int16_t)); |
| aresample->resampled_data[i] = |
| av_realloc(aresample->resampled_data[i], |
| FFALIGN(sizeof(int16_t) * requested_out_nb_samples, 16)); |
| |
| if (aresample->cached_data[i] == NULL || aresample->resampled_data[i] == NULL) |
| return; |
| } |
| aresample->max_cached_nb_samples = cached_nb_samples; |
| |
| if (aresample->outsamplesref) |
| avfilter_unref_buffer(aresample->outsamplesref); |
| |
| aresample->outsamplesref = |
| avfilter_get_audio_buffer(outlink, AV_PERM_WRITE, requested_out_nb_samples); |
| outlink->out_buf = aresample->outsamplesref; |
| } |
| |
| avfilter_copy_buffer_ref_props(aresample->outsamplesref, insamplesref); |
| aresample->outsamplesref->audio->sample_rate = outlink->sample_rate; |
| aresample->outsamplesref->pts = |
| av_rescale(outlink->sample_rate, insamplesref->pts, inlink->sample_rate); |
| |
| /* av_resample() works with planar audio buffers */ |
| if (!inlink->planar && nb_channels > 1) { |
| int16_t *out[8]; |
| for (i = 0; i < nb_channels; i++) |
| out[i] = aresample->cached_data[i] + aresample->unconsumed_nb_samples; |
| |
| deinterleave(out, (int16_t *)insamplesref->data[0], |
| nb_channels, in_nb_samples); |
| } else { |
| for (i = 0; i < nb_channels; i++) |
| memcpy(aresample->cached_data[i] + aresample->unconsumed_nb_samples, |
| insamplesref->data[i], |
| in_nb_samples * sizeof(int16_t)); |
| } |
| |
| for (i = 0; i < nb_channels; i++) { |
| int consumed_nb_samples; |
| const int is_last = i+1 == nb_channels; |
| |
| aresample->outsamplesref->audio->nb_samples = |
| av_resample(aresample->resample, |
| aresample->resampled_data[i], aresample->cached_data[i], |
| &consumed_nb_samples, |
| cached_nb_samples, |
| requested_out_nb_samples, is_last); |
| |
| /* move unconsumed data back to the beginning of the cache */ |
| aresample->unconsumed_nb_samples = cached_nb_samples - consumed_nb_samples; |
| memmove(aresample->cached_data[i], |
| aresample->cached_data[i] + consumed_nb_samples, |
| aresample->unconsumed_nb_samples * sizeof(int16_t)); |
| } |
| |
| |
| /* copy resampled data to the output samplesref */ |
| if (!inlink->planar && nb_channels > 1) { |
| interleave((int16_t *)aresample->outsamplesref->data[0], |
| aresample->resampled_data, |
| nb_channels, aresample->outsamplesref->audio->nb_samples); |
| } else { |
| for (i = 0; i < nb_channels; i++) |
| memcpy(aresample->outsamplesref->data[i], aresample->resampled_data[i], |
| aresample->outsamplesref->audio->nb_samples * sizeof(int16_t)); |
| } |
| |
| avfilter_filter_samples(outlink, avfilter_ref_buffer(aresample->outsamplesref, ~0)); |
| avfilter_unref_buffer(insamplesref); |
| } |
| |
| AVFilter avfilter_af_aresample = { |
| .name = "aresample", |
| .description = NULL_IF_CONFIG_SMALL("Resample audio data."), |
| .init = init, |
| .uninit = uninit, |
| .query_formats = query_formats, |
| .priv_size = sizeof(AResampleContext), |
| |
| .inputs = (const AVFilterPad[]) {{ .name = "default", |
| .type = AVMEDIA_TYPE_AUDIO, |
| .filter_samples = filter_samples, |
| .min_perms = AV_PERM_READ, }, |
| { .name = NULL}}, |
| .outputs = (const AVFilterPad[]) {{ .name = "default", |
| .config_props = config_output, |
| .type = AVMEDIA_TYPE_AUDIO, }, |
| { .name = NULL}}, |
| }; |