diff options
author | Anton Khirnov <anton@khirnov.net> | 2012-05-04 17:09:17 +0400 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2012-05-12 20:09:28 +0400 |
commit | d371e7b9885aabdb29c038fa723bc890276aa366 (patch) | |
tree | 2474b1ad15442bca7a1dfeb0673e30a7d10c1660 /libavfilter/af_resample.c | |
parent | ea60dfe284b82fc74569bbd562e68ec48f69f154 (diff) |
lavfi: add lavr-based audio resampling filter.
Diffstat (limited to 'libavfilter/af_resample.c')
-rw-r--r-- | libavfilter/af_resample.c | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/libavfilter/af_resample.c b/libavfilter/af_resample.c new file mode 100644 index 0000000000..f46e24b1b6 --- /dev/null +++ b/libavfilter/af_resample.c @@ -0,0 +1,225 @@ +/* + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * sample format and channel layout conversion audio filter + */ + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" + +#include "libavresample/avresample.h" + +#include "audio.h" +#include "avfilter.h" +#include "internal.h" + +typedef struct ResampleContext { + AVAudioResampleContext *avr; + + int64_t next_pts; +} ResampleContext; + +static av_cold void uninit(AVFilterContext *ctx) +{ + ResampleContext *s = ctx->priv; + + if (s->avr) { + avresample_close(s->avr); + avresample_free(&s->avr); + } +} + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + + AVFilterFormats *in_formats = avfilter_all_formats(AVMEDIA_TYPE_AUDIO); + AVFilterFormats *out_formats = avfilter_all_formats(AVMEDIA_TYPE_AUDIO); + + avfilter_formats_ref(in_formats, &inlink->out_formats); + avfilter_formats_ref(out_formats, &outlink->in_formats); + + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AVFilterLink *inlink = ctx->inputs[0]; + ResampleContext *s = ctx->priv; + char buf1[64], buf2[64]; + int ret; + + if (s->avr) { + avresample_close(s->avr); + avresample_free(&s->avr); + } + + if (inlink->channel_layout == outlink->channel_layout && + inlink->sample_rate == outlink->sample_rate && + inlink->format == outlink->format) + return 0; + + if (!(s->avr = avresample_alloc_context())) + return AVERROR(ENOMEM); + + av_opt_set_int(s->avr, "in_channel_layout", inlink ->channel_layout, 0); + av_opt_set_int(s->avr, "out_channel_layout", outlink->channel_layout, 0); + av_opt_set_int(s->avr, "in_sample_fmt", inlink ->format, 0); + av_opt_set_int(s->avr, "out_sample_fmt", outlink->format, 0); + av_opt_set_int(s->avr, "in_sample_rate", inlink ->sample_rate, 0); + av_opt_set_int(s->avr, "out_sample_rate", outlink->sample_rate, 0); + + /* if both the input and output formats are s16 or u8, use s16 as + the internal sample format */ + if (av_get_bytes_per_sample(inlink->format) <= 2 && + av_get_bytes_per_sample(outlink->format) <= 2) + av_opt_set_int(s->avr, "internal_sample_fmt", AV_SAMPLE_FMT_S16P, 0); + + if ((ret = avresample_open(s->avr)) < 0) + return ret; + + outlink->time_base = (AVRational){ 1, outlink->sample_rate }; + s->next_pts = AV_NOPTS_VALUE; + + av_get_channel_layout_string(buf1, sizeof(buf1), + -1, inlink ->channel_layout); + av_get_channel_layout_string(buf2, sizeof(buf2), + -1, outlink->channel_layout); + av_log(ctx, AV_LOG_VERBOSE, + "fmt:%s srate: %d cl:%s -> fmt:%s srate: %d cl:%s\n", + av_get_sample_fmt_name(inlink ->format), inlink ->sample_rate, buf1, + av_get_sample_fmt_name(outlink->format), outlink->sample_rate, buf2); + + return 0; +} + +static int request_frame(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + ResampleContext *s = ctx->priv; + int ret = avfilter_request_frame(ctx->inputs[0]); + + /* flush the lavr delay buffer */ + if (ret == AVERROR_EOF && s->avr) { + AVFilterBufferRef *buf; + int nb_samples = av_rescale_rnd(avresample_get_delay(s->avr), + outlink->sample_rate, + ctx->inputs[0]->sample_rate, + AV_ROUND_UP); + + if (!nb_samples) + return ret; + + buf = ff_get_audio_buffer(outlink, AV_PERM_WRITE, nb_samples); + if (!buf) + return AVERROR(ENOMEM); + + ret = avresample_convert(s->avr, (void**)buf->extended_data, + buf->linesize[0], nb_samples, + NULL, 0, 0); + if (ret <= 0) { + avfilter_unref_buffer(buf); + return (ret == 0) ? AVERROR_EOF : ret; + } + + buf->pts = s->next_pts; + ff_filter_samples(outlink, buf); + return 0; + } + return ret; +} + +static void filter_samples(AVFilterLink *inlink, AVFilterBufferRef *buf) +{ + AVFilterContext *ctx = inlink->dst; + ResampleContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + + if (s->avr) { + AVFilterBufferRef *buf_out; + int delay, nb_samples, ret; + + /* maximum possible samples lavr can output */ + delay = avresample_get_delay(s->avr); + nb_samples = av_rescale_rnd(buf->audio->nb_samples + delay, + outlink->sample_rate, inlink->sample_rate, + AV_ROUND_UP); + + buf_out = ff_get_audio_buffer(outlink, AV_PERM_WRITE, nb_samples); + ret = avresample_convert(s->avr, (void**)buf_out->extended_data, + buf_out->linesize[0], nb_samples, + (void**)buf->extended_data, buf->linesize[0], + buf->audio->nb_samples); + + av_assert0(!avresample_available(s->avr)); + + if (s->next_pts == AV_NOPTS_VALUE) { + if (buf->pts == AV_NOPTS_VALUE) { + av_log(ctx, AV_LOG_WARNING, "First timestamp is missing, " + "assuming 0.\n"); + s->next_pts = 0; + } else + s->next_pts = av_rescale_q(buf->pts, inlink->time_base, + outlink->time_base); + } + + if (ret > 0) { + buf_out->audio->nb_samples = ret; + if (buf->pts != AV_NOPTS_VALUE) { + buf_out->pts = av_rescale_q(buf->pts, inlink->time_base, + outlink->time_base) - + av_rescale(delay, outlink->sample_rate, + inlink->sample_rate); + } else + buf_out->pts = s->next_pts; + + s->next_pts = buf_out->pts + buf_out->audio->nb_samples; + + ff_filter_samples(outlink, buf_out); + } + avfilter_unref_buffer(buf); + } else + ff_filter_samples(outlink, buf); +} + +AVFilter avfilter_af_resample = { + .name = "resample", + .description = NULL_IF_CONFIG_SMALL("Audio resampling and conversion."), + .priv_size = sizeof(ResampleContext), + + .uninit = uninit, + .query_formats = query_formats, + + .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", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_output, + .request_frame = request_frame }, + { .name = NULL}}, +}; |