| /* |
| * 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/avstring.h" |
| #include "libavutil/channel_layout.h" |
| #include "libavutil/opt.h" |
| #include "libavutil/samplefmt.h" |
| #include "libavutil/avassert.h" |
| #include "libswresample/swresample.h" |
| #include "avfilter.h" |
| #include "audio.h" |
| #include "internal.h" |
| |
| typedef struct AResampleContext { |
| const AVClass *class; |
| int sample_rate_arg; |
| double ratio; |
| struct SwrContext *swr; |
| int64_t next_pts; |
| int more_data; |
| } AResampleContext; |
| |
| static av_cold int init_dict(AVFilterContext *ctx, AVDictionary **opts) |
| { |
| AResampleContext *aresample = ctx->priv; |
| int ret = 0; |
| |
| aresample->next_pts = AV_NOPTS_VALUE; |
| aresample->swr = swr_alloc(); |
| if (!aresample->swr) { |
| ret = AVERROR(ENOMEM); |
| goto end; |
| } |
| |
| if (opts) { |
| AVDictionaryEntry *e = NULL; |
| |
| while ((e = av_dict_get(*opts, "", e, AV_DICT_IGNORE_SUFFIX))) { |
| if ((ret = av_opt_set(aresample->swr, e->key, e->value, 0)) < 0) |
| goto end; |
| } |
| av_dict_free(opts); |
| } |
| if (aresample->sample_rate_arg > 0) |
| av_opt_set_int(aresample->swr, "osr", aresample->sample_rate_arg, 0); |
| end: |
| return ret; |
| } |
| |
| static av_cold void uninit(AVFilterContext *ctx) |
| { |
| AResampleContext *aresample = ctx->priv; |
| swr_free(&aresample->swr); |
| } |
| |
| static int query_formats(AVFilterContext *ctx) |
| { |
| AResampleContext *aresample = ctx->priv; |
| enum AVSampleFormat out_format; |
| int64_t out_rate, out_layout; |
| |
| AVFilterLink *inlink = ctx->inputs[0]; |
| AVFilterLink *outlink = ctx->outputs[0]; |
| |
| AVFilterFormats *in_formats, *out_formats; |
| AVFilterFormats *in_samplerates, *out_samplerates; |
| AVFilterChannelLayouts *in_layouts, *out_layouts; |
| int ret; |
| |
| av_opt_get_sample_fmt(aresample->swr, "osf", 0, &out_format); |
| av_opt_get_int(aresample->swr, "osr", 0, &out_rate); |
| av_opt_get_int(aresample->swr, "ocl", 0, &out_layout); |
| |
| in_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO); |
| if ((ret = ff_formats_ref(in_formats, &inlink->outcfg.formats)) < 0) |
| return ret; |
| |
| in_samplerates = ff_all_samplerates(); |
| if ((ret = ff_formats_ref(in_samplerates, &inlink->outcfg.samplerates)) < 0) |
| return ret; |
| |
| in_layouts = ff_all_channel_counts(); |
| if ((ret = ff_channel_layouts_ref(in_layouts, &inlink->outcfg.channel_layouts)) < 0) |
| return ret; |
| |
| if(out_rate > 0) { |
| int ratelist[] = { out_rate, -1 }; |
| out_samplerates = ff_make_format_list(ratelist); |
| } else { |
| out_samplerates = ff_all_samplerates(); |
| } |
| |
| if ((ret = ff_formats_ref(out_samplerates, &outlink->incfg.samplerates)) < 0) |
| return ret; |
| |
| if(out_format != AV_SAMPLE_FMT_NONE) { |
| int formatlist[] = { out_format, -1 }; |
| out_formats = ff_make_format_list(formatlist); |
| } else |
| out_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO); |
| if ((ret = ff_formats_ref(out_formats, &outlink->incfg.formats)) < 0) |
| return ret; |
| |
| if(out_layout) { |
| int64_t layout_list[] = { out_layout, -1 }; |
| out_layouts = ff_make_format64_list(layout_list); |
| } else |
| out_layouts = ff_all_channel_counts(); |
| |
| return ff_channel_layouts_ref(out_layouts, &outlink->incfg.channel_layouts); |
| } |
| |
| |
| static int config_output(AVFilterLink *outlink) |
| { |
| int ret; |
| AVFilterContext *ctx = outlink->src; |
| AVFilterLink *inlink = ctx->inputs[0]; |
| AResampleContext *aresample = ctx->priv; |
| int64_t out_rate, out_layout; |
| enum AVSampleFormat out_format; |
| char inchl_buf[128], outchl_buf[128]; |
| |
| aresample->swr = swr_alloc_set_opts(aresample->swr, |
| outlink->channel_layout, outlink->format, outlink->sample_rate, |
| inlink->channel_layout, inlink->format, inlink->sample_rate, |
| 0, ctx); |
| if (!aresample->swr) |
| return AVERROR(ENOMEM); |
| if (!inlink->channel_layout) |
| av_opt_set_int(aresample->swr, "ich", inlink->channels, 0); |
| if (!outlink->channel_layout) |
| av_opt_set_int(aresample->swr, "och", outlink->channels, 0); |
| |
| ret = swr_init(aresample->swr); |
| if (ret < 0) |
| return ret; |
| |
| av_opt_get_int(aresample->swr, "osr", 0, &out_rate); |
| av_opt_get_int(aresample->swr, "ocl", 0, &out_layout); |
| av_opt_get_sample_fmt(aresample->swr, "osf", 0, &out_format); |
| outlink->time_base = (AVRational) {1, out_rate}; |
| |
| av_assert0(outlink->sample_rate == out_rate); |
| av_assert0(outlink->channel_layout == out_layout || !outlink->channel_layout); |
| av_assert0(outlink->format == out_format); |
| |
| aresample->ratio = (double)outlink->sample_rate / inlink->sample_rate; |
| |
| av_get_channel_layout_string(inchl_buf, sizeof(inchl_buf), inlink ->channels, inlink ->channel_layout); |
| av_get_channel_layout_string(outchl_buf, sizeof(outchl_buf), outlink->channels, outlink->channel_layout); |
| |
| av_log(ctx, AV_LOG_VERBOSE, "ch:%d chl:%s fmt:%s r:%dHz -> ch:%d chl:%s fmt:%s r:%dHz\n", |
| inlink ->channels, inchl_buf, av_get_sample_fmt_name(inlink->format), inlink->sample_rate, |
| outlink->channels, outchl_buf, av_get_sample_fmt_name(outlink->format), outlink->sample_rate); |
| return 0; |
| } |
| |
| static int filter_frame(AVFilterLink *inlink, AVFrame *insamplesref) |
| { |
| AResampleContext *aresample = inlink->dst->priv; |
| const int n_in = insamplesref->nb_samples; |
| int64_t delay; |
| int n_out = n_in * aresample->ratio + 32; |
| AVFilterLink *const outlink = inlink->dst->outputs[0]; |
| AVFrame *outsamplesref; |
| int ret; |
| |
| delay = swr_get_delay(aresample->swr, outlink->sample_rate); |
| if (delay > 0) |
| n_out += FFMIN(delay, FFMAX(4096, n_out)); |
| |
| outsamplesref = ff_get_audio_buffer(outlink, n_out); |
| |
| if(!outsamplesref) { |
| av_frame_free(&insamplesref); |
| return AVERROR(ENOMEM); |
| } |
| |
| av_frame_copy_props(outsamplesref, insamplesref); |
| outsamplesref->format = outlink->format; |
| outsamplesref->channels = outlink->channels; |
| outsamplesref->channel_layout = outlink->channel_layout; |
| outsamplesref->sample_rate = outlink->sample_rate; |
| |
| if(insamplesref->pts != AV_NOPTS_VALUE) { |
| int64_t inpts = av_rescale(insamplesref->pts, inlink->time_base.num * (int64_t)outlink->sample_rate * inlink->sample_rate, inlink->time_base.den); |
| int64_t outpts= swr_next_pts(aresample->swr, inpts); |
| aresample->next_pts = |
| outsamplesref->pts = ROUNDED_DIV(outpts, inlink->sample_rate); |
| } else { |
| outsamplesref->pts = AV_NOPTS_VALUE; |
| } |
| n_out = swr_convert(aresample->swr, outsamplesref->extended_data, n_out, |
| (void *)insamplesref->extended_data, n_in); |
| if (n_out <= 0) { |
| av_frame_free(&outsamplesref); |
| av_frame_free(&insamplesref); |
| return 0; |
| } |
| |
| aresample->more_data = outsamplesref->nb_samples == n_out; // Indicate that there is probably more data in our buffers |
| |
| outsamplesref->nb_samples = n_out; |
| |
| ret = ff_filter_frame(outlink, outsamplesref); |
| av_frame_free(&insamplesref); |
| return ret; |
| } |
| |
| static int flush_frame(AVFilterLink *outlink, int final, AVFrame **outsamplesref_ret) |
| { |
| AVFilterContext *ctx = outlink->src; |
| AResampleContext *aresample = ctx->priv; |
| AVFilterLink *const inlink = outlink->src->inputs[0]; |
| AVFrame *outsamplesref; |
| int n_out = 4096; |
| int64_t pts; |
| |
| outsamplesref = ff_get_audio_buffer(outlink, n_out); |
| *outsamplesref_ret = outsamplesref; |
| if (!outsamplesref) |
| return AVERROR(ENOMEM); |
| |
| pts = swr_next_pts(aresample->swr, INT64_MIN); |
| pts = ROUNDED_DIV(pts, inlink->sample_rate); |
| |
| n_out = swr_convert(aresample->swr, outsamplesref->extended_data, n_out, final ? NULL : (void*)outsamplesref->extended_data, 0); |
| if (n_out <= 0) { |
| av_frame_free(&outsamplesref); |
| return (n_out == 0) ? AVERROR_EOF : n_out; |
| } |
| |
| outsamplesref->sample_rate = outlink->sample_rate; |
| outsamplesref->nb_samples = n_out; |
| |
| outsamplesref->pts = pts; |
| |
| return 0; |
| } |
| |
| static int request_frame(AVFilterLink *outlink) |
| { |
| AVFilterContext *ctx = outlink->src; |
| AResampleContext *aresample = ctx->priv; |
| int ret; |
| |
| // First try to get data from the internal buffers |
| if (aresample->more_data) { |
| AVFrame *outsamplesref; |
| |
| if (flush_frame(outlink, 0, &outsamplesref) >= 0) { |
| return ff_filter_frame(outlink, outsamplesref); |
| } |
| } |
| aresample->more_data = 0; |
| |
| // Second request more data from the input |
| ret = ff_request_frame(ctx->inputs[0]); |
| |
| // Third if we hit the end flush |
| if (ret == AVERROR_EOF) { |
| AVFrame *outsamplesref; |
| |
| if ((ret = flush_frame(outlink, 1, &outsamplesref)) < 0) |
| return ret; |
| |
| return ff_filter_frame(outlink, outsamplesref); |
| } |
| return ret; |
| } |
| |
| #if FF_API_CHILD_CLASS_NEXT |
| static const AVClass *resample_child_class_next(const AVClass *prev) |
| { |
| return prev ? NULL : swr_get_class(); |
| } |
| #endif |
| |
| static const AVClass *resample_child_class_iterate(void **iter) |
| { |
| const AVClass *c = *iter ? NULL : swr_get_class(); |
| *iter = (void*)(uintptr_t)c; |
| return c; |
| } |
| |
| static void *resample_child_next(void *obj, void *prev) |
| { |
| AResampleContext *s = obj; |
| return prev ? NULL : s->swr; |
| } |
| |
| #define OFFSET(x) offsetof(AResampleContext, x) |
| #define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
| |
| static const AVOption options[] = { |
| {"sample_rate", NULL, OFFSET(sample_rate_arg), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS }, |
| {NULL} |
| }; |
| |
| static const AVClass aresample_class = { |
| .class_name = "aresample", |
| .item_name = av_default_item_name, |
| .option = options, |
| .version = LIBAVUTIL_VERSION_INT, |
| #if FF_API_CHILD_CLASS_NEXT |
| .child_class_next = resample_child_class_next, |
| #endif |
| .child_class_iterate = resample_child_class_iterate, |
| .child_next = resample_child_next, |
| }; |
| |
| static const AVFilterPad aresample_inputs[] = { |
| { |
| .name = "default", |
| .type = AVMEDIA_TYPE_AUDIO, |
| .filter_frame = filter_frame, |
| }, |
| { NULL } |
| }; |
| |
| static const AVFilterPad aresample_outputs[] = { |
| { |
| .name = "default", |
| .config_props = config_output, |
| .request_frame = request_frame, |
| .type = AVMEDIA_TYPE_AUDIO, |
| }, |
| { NULL } |
| }; |
| |
| AVFilter ff_af_aresample = { |
| .name = "aresample", |
| .description = NULL_IF_CONFIG_SMALL("Resample audio data."), |
| .init_dict = init_dict, |
| .uninit = uninit, |
| .query_formats = query_formats, |
| .priv_size = sizeof(AResampleContext), |
| .priv_class = &aresample_class, |
| .inputs = aresample_inputs, |
| .outputs = aresample_outputs, |
| }; |