diff options
27 files changed, 3838 insertions, 8 deletions
@@ -16,6 +16,7 @@ version <next>: - RealAudio Lossless decoder - ZeroCodec decoder - drop support for avconv without libavfilter +- add libavresample audio conversion library version 0.8: @@ -20,7 +20,7 @@ $(foreach VAR,$(SILENT),$(eval override $(VAR) = @$($(VAR)))) $(eval INSTALL = @$(call ECHO,INSTALL,$$(^:$(SRC_PATH)/%=%)); $(INSTALL)) endif -ALLFFLIBS = avcodec avdevice avfilter avformat avutil swscale +ALLFFLIBS = avcodec avdevice avfilter avformat avresample avutil swscale IFLAGS := -I. -I$(SRC_PATH) CPPFLAGS := $(IFLAGS) $(CPPFLAGS) @@ -71,6 +71,7 @@ ALLMANPAGES = $(BASENAMES:%=%.1) FFLIBS-$(CONFIG_AVDEVICE) += avdevice FFLIBS-$(CONFIG_AVFILTER) += avfilter FFLIBS-$(CONFIG_AVFORMAT) += avformat +FFLIBS-$(CONFIG_AVRESAMPLE) += avresample FFLIBS-$(CONFIG_AVCODEC) += avcodec FFLIBS-$(CONFIG_SWSCALE) += swscale @@ -110,6 +110,7 @@ Component options: --disable-avformat disable libavformat build --disable-swscale disable libswscale build --disable-avfilter disable video filter support [no] + --disable-avresample disable libavresample build [no] --disable-pthreads disable pthreads [auto] --disable-w32threads disable Win32 threads [auto] --enable-x11grab enable X11 grabbing [no] @@ -927,6 +928,7 @@ CONFIG_LIST=" avdevice avfilter avformat + avresample avisynth bzlib dct @@ -1536,7 +1538,7 @@ avdevice_deps="avcodec avformat" avformat_deps="avcodec" # programs -avconv_deps="avcodec avfilter avformat swscale" +avconv_deps="avcodec avfilter avformat avresample swscale" avplay_deps="avcodec avformat swscale sdl" avplay_select="rdft" avprobe_deps="avcodec avformat" @@ -1684,6 +1686,7 @@ enable avcodec enable avdevice enable avfilter enable avformat +enable avresample enable avutil enable swscale @@ -3385,6 +3388,7 @@ get_version LIBAVCODEC libavcodec/version.h get_version LIBAVDEVICE libavdevice/avdevice.h get_version LIBAVFILTER libavfilter/version.h get_version LIBAVFORMAT libavformat/version.h +get_version LIBAVRESAMPLE libavresample/version.h get_version LIBAVUTIL libavutil/avutil.h get_version LIBSWSCALE libswscale/swscale.h @@ -3504,4 +3508,5 @@ pkgconfig_generate libavcodec "Libav codec library" "$LIBAVCODEC_VERSION" "$extr pkgconfig_generate libavformat "Libav container format library" "$LIBAVFORMAT_VERSION" "$extralibs" "libavcodec = $LIBAVCODEC_VERSION" pkgconfig_generate libavdevice "Libav device handling library" "$LIBAVDEVICE_VERSION" "$extralibs" "libavformat = $LIBAVFORMAT_VERSION" pkgconfig_generate libavfilter "Libav video filtering library" "$LIBAVFILTER_VERSION" "$extralibs" +pkgconfig_generate libavresample "Libav audio resampling library" "$LIBAVRESAMPLE_VERSION" "$extralibs" pkgconfig_generate libswscale "Libav image rescaling library" "$LIBSWSCALE_VERSION" "$LIBM" "libavutil = $LIBAVUTIL_VERSION" diff --git a/doc/APIchanges b/doc/APIchanges index fed77b0eb5..ba20c80538 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,16 +2,20 @@ Never assume the API of libav* to be stable unless at least 1 month has passed since the last major version increase. The last version increases were: -libavcodec: 2012-01-27 -libavdevice: 2011-04-18 -libavfilter: 2011-04-18 -libavformat: 2012-01-27 -libswscale: 2011-06-20 -libavutil: 2011-04-18 +libavcodec: 2012-01-27 +libavdevice: 2011-04-18 +libavfilter: 2011-04-18 +libavformat: 2012-01-27 +libavresample: 2012-xx-xx +libswscale: 2011-06-20 +libavutil: 2011-04-18 API changes, most recent first: +2012-xx-xx - xxxxxxx - lavr 0.0.0 + Add libavresample audio conversion library + 2012-xx-xx - xxxxxxx - lavu 51.28.0 - audio_fifo.h Add audio FIFO functions: av_audio_fifo_free() diff --git a/libavresample/Makefile b/libavresample/Makefile new file mode 100644 index 0000000000..ce3fe81953 --- /dev/null +++ b/libavresample/Makefile @@ -0,0 +1,15 @@ +NAME = avresample +FFLIBS = avutil + +HEADERS = avresample.h \ + version.h + +OBJS = audio_convert.o \ + audio_data.o \ + audio_mix.o \ + audio_mix_matrix.o \ + options.o \ + resample.o \ + utils.o + +TESTPROGS = avresample diff --git a/libavresample/audio_convert.c b/libavresample/audio_convert.c new file mode 100644 index 0000000000..200eb100cc --- /dev/null +++ b/libavresample/audio_convert.c @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at> + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include <stdint.h> + +#include "config.h" +#include "libavutil/libm.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/samplefmt.h" +#include "audio_convert.h" +#include "audio_data.h" + +enum ConvFuncType { + CONV_FUNC_TYPE_FLAT, + CONV_FUNC_TYPE_INTERLEAVE, + CONV_FUNC_TYPE_DEINTERLEAVE, +}; + +typedef void (conv_func_flat)(uint8_t *out, const uint8_t *in, int len); + +typedef void (conv_func_interleave)(uint8_t *out, uint8_t *const *in, + int len, int channels); + +typedef void (conv_func_deinterleave)(uint8_t **out, const uint8_t *in, int len, + int channels); + +struct AudioConvert { + AVAudioResampleContext *avr; + enum AVSampleFormat in_fmt; + enum AVSampleFormat out_fmt; + int channels; + int planes; + int ptr_align; + int samples_align; + int has_optimized_func; + const char *func_descr; + const char *func_descr_generic; + enum ConvFuncType func_type; + conv_func_flat *conv_flat; + conv_func_flat *conv_flat_generic; + conv_func_interleave *conv_interleave; + conv_func_interleave *conv_interleave_generic; + conv_func_deinterleave *conv_deinterleave; + conv_func_deinterleave *conv_deinterleave_generic; +}; + +void ff_audio_convert_set_func(AudioConvert *ac, enum AVSampleFormat out_fmt, + enum AVSampleFormat in_fmt, int channels, + int ptr_align, int samples_align, + const char *descr, void *conv) +{ + int found = 0; + + switch (ac->func_type) { + case CONV_FUNC_TYPE_FLAT: + if (av_get_packed_sample_fmt(ac->in_fmt) == in_fmt && + av_get_packed_sample_fmt(ac->out_fmt) == out_fmt) { + ac->conv_flat = conv; + ac->func_descr = descr; + ac->ptr_align = ptr_align; + ac->samples_align = samples_align; + if (ptr_align == 1 && samples_align == 1) { + ac->conv_flat_generic = conv; + ac->func_descr_generic = descr; + } else { + ac->has_optimized_func = 1; + } + found = 1; + } + break; + case CONV_FUNC_TYPE_INTERLEAVE: + if (ac->in_fmt == in_fmt && ac->out_fmt == out_fmt && + (!channels || ac->channels == channels)) { + ac->conv_interleave = conv; + ac->func_descr = descr; + ac->ptr_align = ptr_align; + ac->samples_align = samples_align; + if (ptr_align == 1 && samples_align == 1) { + ac->conv_interleave_generic = conv; + ac->func_descr_generic = descr; + } else { + ac->has_optimized_func = 1; + } + found = 1; + } + break; + case CONV_FUNC_TYPE_DEINTERLEAVE: + if (ac->in_fmt == in_fmt && ac->out_fmt == out_fmt && + (!channels || ac->channels == channels)) { + ac->conv_deinterleave = conv; + ac->func_descr = descr; + ac->ptr_align = ptr_align; + ac->samples_align = samples_align; + if (ptr_align == 1 && samples_align == 1) { + ac->conv_deinterleave_generic = conv; + ac->func_descr_generic = descr; + } else { + ac->has_optimized_func = 1; + } + found = 1; + } + break; + } + if (found) { + av_log(ac->avr, AV_LOG_DEBUG, "audio_convert: found function: %-4s " + "to %-4s (%s)\n", av_get_sample_fmt_name(ac->in_fmt), + av_get_sample_fmt_name(ac->out_fmt), descr); + } +} + +#define CONV_FUNC_NAME(dst_fmt, src_fmt) conv_ ## src_fmt ## _to_ ## dst_fmt + +#define CONV_LOOP(otype, expr) \ + do { \ + *(otype *)po = expr; \ + pi += is; \ + po += os; \ + } while (po < end); \ + +#define CONV_FUNC_FLAT(ofmt, otype, ifmt, itype, expr) \ +static void CONV_FUNC_NAME(ofmt, ifmt)(uint8_t *out, const uint8_t *in, \ + int len) \ +{ \ + int is = sizeof(itype); \ + int os = sizeof(otype); \ + const uint8_t *pi = in; \ + uint8_t *po = out; \ + uint8_t *end = out + os * len; \ + CONV_LOOP(otype, expr) \ +} + +#define CONV_FUNC_INTERLEAVE(ofmt, otype, ifmt, itype, expr) \ +static void CONV_FUNC_NAME(ofmt, ifmt)(uint8_t *out, const uint8_t **in, \ + int len, int channels) \ +{ \ + int ch; \ + int out_bps = sizeof(otype); \ + int is = sizeof(itype); \ + int os = channels * out_bps; \ + for (ch = 0; ch < channels; ch++) { \ + const uint8_t *pi = in[ch]; \ + uint8_t *po = out + ch * out_bps; \ + uint8_t *end = po + os * len; \ + CONV_LOOP(otype, expr) \ + } \ +} + +#define CONV_FUNC_DEINTERLEAVE(ofmt, otype, ifmt, itype, expr) \ +static void CONV_FUNC_NAME(ofmt, ifmt)(uint8_t **out, const uint8_t *in, \ + int len, int channels) \ +{ \ + int ch; \ + int in_bps = sizeof(itype); \ + int is = channels * in_bps; \ + int os = sizeof(otype); \ + for (ch = 0; ch < channels; ch++) { \ + const uint8_t *pi = in + ch * in_bps; \ + uint8_t *po = out[ch]; \ + uint8_t *end = po + os * len; \ + CONV_LOOP(otype, expr) \ + } \ +} + +#define CONV_FUNC_GROUP(ofmt, otype, ifmt, itype, expr) \ +CONV_FUNC_FLAT( ofmt, otype, ifmt, itype, expr) \ +CONV_FUNC_INTERLEAVE( ofmt, otype, ifmt ## P, itype, expr) \ +CONV_FUNC_DEINTERLEAVE(ofmt ## P, otype, ifmt, itype, expr) + +CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, uint8_t, AV_SAMPLE_FMT_U8, uint8_t, *(const uint8_t *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_U8, uint8_t, (*(const uint8_t *)pi - 0x80) << 8) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_U8, uint8_t, (*(const uint8_t *)pi - 0x80) << 24) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, float, AV_SAMPLE_FMT_U8, uint8_t, (*(const uint8_t *)pi - 0x80) * (1.0f / (1 << 7))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, double, AV_SAMPLE_FMT_U8, uint8_t, (*(const uint8_t *)pi - 0x80) * (1.0 / (1 << 7))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, uint8_t, AV_SAMPLE_FMT_S16, int16_t, (*(const int16_t *)pi >> 8) + 0x80) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_S16, int16_t, *(const int16_t *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_S16, int16_t, *(const int16_t *)pi << 16) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, float, AV_SAMPLE_FMT_S16, int16_t, *(const int16_t *)pi * (1.0f / (1 << 15))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, double, AV_SAMPLE_FMT_S16, int16_t, *(const int16_t *)pi * (1.0 / (1 << 15))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, uint8_t, AV_SAMPLE_FMT_S32, int32_t, (*(const int32_t *)pi >> 24) + 0x80) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_S32, int32_t, *(const int32_t *)pi >> 16) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_S32, int32_t, *(const int32_t *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, float, AV_SAMPLE_FMT_S32, int32_t, *(const int32_t *)pi * (1.0f / (1U << 31))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, double, AV_SAMPLE_FMT_S32, int32_t, *(const int32_t *)pi * (1.0 / (1U << 31))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, uint8_t, AV_SAMPLE_FMT_FLT, float, av_clip_uint8( lrintf(*(const float *)pi * (1 << 7)) + 0x80)) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_FLT, float, av_clip_int16( lrintf(*(const float *)pi * (1 << 15)))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_FLT, float, av_clipl_int32(llrintf(*(const float *)pi * (1U << 31)))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, float, AV_SAMPLE_FMT_FLT, float, *(const float *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, double, AV_SAMPLE_FMT_FLT, float, *(const float *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, uint8_t, AV_SAMPLE_FMT_DBL, double, av_clip_uint8( lrint(*(const double *)pi * (1 << 7)) + 0x80)) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_DBL, double, av_clip_int16( lrint(*(const double *)pi * (1 << 15)))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_DBL, double, av_clipl_int32(llrint(*(const double *)pi * (1U << 31)))) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, float, AV_SAMPLE_FMT_DBL, double, *(const double *)pi) +CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, double, AV_SAMPLE_FMT_DBL, double, *(const double *)pi) + +#define SET_CONV_FUNC_GROUP(ofmt, ifmt) \ +ff_audio_convert_set_func(ac, ofmt, ifmt, 0, 1, 1, "C", CONV_FUNC_NAME(ofmt, ifmt)); \ +ff_audio_convert_set_func(ac, ofmt ## P, ifmt, 0, 1, 1, "C", CONV_FUNC_NAME(ofmt ## P, ifmt)); \ +ff_audio_convert_set_func(ac, ofmt, ifmt ## P, 0, 1, 1, "C", CONV_FUNC_NAME(ofmt, ifmt ## P)); + +static void set_generic_function(AudioConvert *ac) +{ + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_U8) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_U8) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_U8) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_U8) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_U8) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_S16) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S16) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_S16) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_S16) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_S32) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S32) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S32) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_S32) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_S32) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_FLT) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_FLT) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLT) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_FLT) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_DBL) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_DBL) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_DBL) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_DBL) + SET_CONV_FUNC_GROUP(AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBL) +} + +AudioConvert *ff_audio_convert_alloc(AVAudioResampleContext *avr, + enum AVSampleFormat out_fmt, + enum AVSampleFormat in_fmt, + int channels) +{ + AudioConvert *ac; + int in_planar, out_planar; + + ac = av_mallocz(sizeof(*ac)); + if (!ac) + return NULL; + + ac->avr = avr; + ac->out_fmt = out_fmt; + ac->in_fmt = in_fmt; + ac->channels = channels; + + in_planar = av_sample_fmt_is_planar(in_fmt); + out_planar = av_sample_fmt_is_planar(out_fmt); + + if (in_planar == out_planar) { + ac->func_type = CONV_FUNC_TYPE_FLAT; + ac->planes = in_planar ? ac->channels : 1; + } else if (in_planar) + ac->func_type = CONV_FUNC_TYPE_INTERLEAVE; + else + ac->func_type = CONV_FUNC_TYPE_DEINTERLEAVE; + + set_generic_function(ac); + + if (ARCH_X86) + ff_audio_convert_init_x86(ac); + + return ac; +} + +int ff_audio_convert(AudioConvert *ac, AudioData *out, AudioData *in, int len) +{ + int use_generic = 1; + + /* determine whether to use the optimized function based on pointer and + samples alignment in both the input and output */ + if (ac->has_optimized_func) { + int ptr_align = FFMIN(in->ptr_align, out->ptr_align); + int samples_align = FFMIN(in->samples_align, out->samples_align); + int aligned_len = FFALIGN(len, ac->samples_align); + if (!(ptr_align % ac->ptr_align) && samples_align >= aligned_len) { + len = aligned_len; + use_generic = 0; + } + } + av_dlog(ac->avr, "%d samples - audio_convert: %s to %s (%s)\n", len, + av_get_sample_fmt_name(ac->in_fmt), + av_get_sample_fmt_name(ac->out_fmt), + use_generic ? ac->func_descr_generic : ac->func_descr); + + switch (ac->func_type) { + case CONV_FUNC_TYPE_FLAT: { + int p; + if (!in->is_planar) + len *= in->channels; + if (use_generic) { + for (p = 0; p < ac->planes; p++) + ac->conv_flat_generic(out->data[p], in->data[p], len); + } else { + for (p = 0; p < ac->planes; p++) + ac->conv_flat(out->data[p], in->data[p], len); + } + break; + } + case CONV_FUNC_TYPE_INTERLEAVE: + if (use_generic) + ac->conv_interleave_generic(out->data[0], in->data, len, ac->channels); + else + ac->conv_interleave(out->data[0], in->data, len, ac->channels); + break; + case CONV_FUNC_TYPE_DEINTERLEAVE: + if (use_generic) + ac->conv_deinterleave_generic(out->data, in->data[0], len, ac->channels); + else + ac->conv_deinterleave(out->data, in->data[0], len, ac->channels); + break; + } + + out->nb_samples = in->nb_samples; + return 0; +} diff --git a/libavresample/audio_convert.h b/libavresample/audio_convert.h new file mode 100644 index 0000000000..9227763628 --- /dev/null +++ b/libavresample/audio_convert.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#ifndef AVRESAMPLE_AUDIO_CONVERT_H +#define AVRESAMPLE_AUDIO_CONVERT_H + +#include "libavutil/samplefmt.h" +#include "avresample.h" +#include "audio_data.h" + +typedef struct AudioConvert AudioConvert; + +/** + * Set conversion function if the parameters match. + * + * This compares the parameters of the conversion function to the parameters + * in the AudioConvert context. If the parameters do not match, no changes are + * made to the active functions. If the parameters do match and the alignment + * is not constrained, the function is set as the generic conversion function. + * If the parameters match and the alignment is constrained, the function is + * set as the optimized conversion function. + * + * @param ac AudioConvert context + * @param out_fmt output sample format + * @param in_fmt input sample format + * @param channels number of channels, or 0 for any number of channels + * @param ptr_align buffer pointer alignment, in bytes + * @param sample_align buffer size alignment, in samples + * @param descr function type description (e.g. "C" or "SSE") + * @param conv conversion function pointer + */ +void ff_audio_convert_set_func(AudioConvert *ac, enum AVSampleFormat out_fmt, + enum AVSampleFormat in_fmt, int channels, + int ptr_align, int samples_align, + const char *descr, void *conv); + +/** + * Allocate and initialize AudioConvert context for sample format conversion. + * + * @param avr AVAudioResampleContext + * @param out_fmt output sample format + * @param in_fmt input sample format + * @param channels number of channels + * @return newly-allocated AudioConvert context + */ +AudioConvert *ff_audio_convert_alloc(AVAudioResampleContext *avr, + enum AVSampleFormat out_fmt, + enum AVSampleFormat in_fmt, + int channels); + +/** + * Convert audio data from one sample format to another. + * + * For each call, the alignment of the input and output AudioData buffers are + * examined to determine whether to use the generic or optimized conversion + * function (when available). + * + * @param ac AudioConvert context + * @param out output audio data + * @param in input audio data + * @param len number of samples to convert + * @return 0 on success, negative AVERROR code on failure + */ +int ff_audio_convert(AudioConvert *ac, AudioData *out, AudioData *in, int len); + +/* arch-specific initialization functions */ + +void ff_audio_convert_init_x86(AudioConvert *ac); + +#endif /* AVRESAMPLE_AUDIO_CONVERT_H */ diff --git a/libavresample/audio_data.c b/libavresample/audio_data.c new file mode 100644 index 0000000000..3f82c50ef0 --- /dev/null +++ b/libavresample/audio_data.c @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include <stdint.h> + +#include "libavutil/mem.h" +#include "audio_data.h" + +static const AVClass audio_data_class = { + .class_name = "AudioData", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +/* + * Calculate alignment for data pointers. + */ +static void calc_ptr_alignment(AudioData *a) +{ + int p; + int min_align = 128; + + for (p = 0; p < a->planes; p++) { + int cur_align = 128; + while ((intptr_t)a->data[p] % cur_align) + cur_align >>= 1; + if (cur_align < min_align) + min_align = cur_align; + } + a->ptr_align = min_align; +} + +int ff_audio_data_set_channels(AudioData *a, int channels) +{ + if (channels < 1 || channels > AVRESAMPLE_MAX_CHANNELS || + channels > a->allocated_channels) + return AVERROR(EINVAL); + + a->channels = channels; + a->planes = a->is_planar ? channels : 1; + + calc_ptr_alignment(a); + + return 0; +} + +int ff_audio_data_init(AudioData *a, void **src, int plane_size, int channels, + int nb_samples, enum AVSampleFormat sample_fmt, + int read_only, const char *name) +{ + int p; + + memset(a, 0, sizeof(*a)); + a->class = &audio_data_class; + + if (channels < 1 || channels > AVRESAMPLE_MAX_CHANNELS) { + av_log(a, AV_LOG_ERROR, "invalid channel count: %d\n", channels); + return AVERROR(EINVAL); + } + + a->sample_size = av_get_bytes_per_sample(sample_fmt); + if (!a->sample_size) { + av_log(a, AV_LOG_ERROR, "invalid sample format\n"); + return AVERROR(EINVAL); + } + a->is_planar = av_sample_fmt_is_planar(sample_fmt); + a->planes = a->is_planar ? channels : 1; + a->stride = a->sample_size * (a->is_planar ? 1 : channels); + + for (p = 0; p < (a->is_planar ? channels : 1); p++) { + if (!src[p]) { + av_log(a, AV_LOG_ERROR, "invalid NULL pointer for src[%d]\n", p); + return AVERROR(EINVAL); + } + a->data[p] = src[p]; + } + a->allocated_samples = nb_samples * !read_only; + a->nb_samples = nb_samples; + a->sample_fmt = sample_fmt; + a->channels = channels; + a->allocated_channels = channels; + a->read_only = read_only; + a->allow_realloc = 0; + a->name = name ? name : "{no name}"; + + calc_ptr_alignment(a); + a->samples_align = plane_size / a->stride; + + return 0; +} + +AudioData *ff_audio_data_alloc(int channels, int nb_samples, + enum AVSampleFormat sample_fmt, const char *name) +{ + AudioData *a; + int ret; + + if (channels < 1 || channels > AVRESAMPLE_MAX_CHANNELS) + return NULL; + + a = av_mallocz(sizeof(*a)); + if (!a) + return NULL; + + a->sample_size = av_get_bytes_per_sample(sample_fmt); + if (!a->sample_size) { + av_free(a); + return NULL; + } + a->is_planar = av_sample_fmt_is_planar(sample_fmt); + a->planes = a->is_planar ? channels : 1; + a->stride = a->sample_size * (a->is_planar ? 1 : channels); + + a->class = &audio_data_class; + a->sample_fmt = sample_fmt; + a->channels = channels; + a->allocated_channels = channels; + a->read_only = 0; + a->allow_realloc = 1; + a->name = name ? name : "{no name}"; + + if (nb_samples > 0) { + ret = ff_audio_data_realloc(a, nb_samples); + if (ret < 0) { + av_free(a); + return NULL; + } + return a; + } else { + calc_ptr_alignment(a); + return a; + } +} + +int ff_audio_data_realloc(AudioData *a, int nb_samples) +{ + int ret, new_buf_size, plane_size, p; + + /* check if buffer is already large enough */ + if (a->allocated_samples >= nb_samples) + return 0; + + /* validate that the output is not read-only and realloc is allowed */ + if (a->read_only || !a->allow_realloc) + return AVERROR(EINVAL); + + new_buf_size = av_samples_get_buffer_size(&plane_size, + a->allocated_channels, nb_samples, + a->sample_fmt, 0); + if (new_buf_size < 0) + return new_buf_size; + + /* if there is already data in the buffer and the sample format is planar, + allocate a new buffer and copy the data, otherwise just realloc the + internal buffer and set new data pointers */ + if (a->nb_samples > 0 && a->is_planar) { + uint8_t *new_data[AVRESAMPLE_MAX_CHANNELS] = { NULL }; + + ret = av_samples_alloc(new_data, &plane_size, a->allocated_channels, + nb_samples, a->sample_fmt, 0); + if (ret < 0) + return ret; + + for (p = 0; p < a->planes; p++) + memcpy(new_data[p], a->data[p], a->nb_samples * a->stride); + + av_freep(&a->buffer); + memcpy(a->data, new_data, sizeof(new_data)); + a->buffer = a->data[0]; + } else { + av_freep(&a->buffer); + a->buffer = av_malloc(new_buf_size); + if (!a->buffer) + return AVERROR(ENOMEM); + ret = av_samples_fill_arrays(a->data, &plane_size, a->buffer, + a->allocated_channels, nb_samples, + a->sample_fmt, 0); + if (ret < 0) + return ret; + } + a->buffer_size = new_buf_size; + a->allocated_samples = nb_samples; + + calc_ptr_alignment(a); + a->samples_align = plane_size / a->stride; + + return 0; +} + +void ff_audio_data_free(AudioData **a) +{ + if (!*a) + return; + av_free((*a)->buffer); + av_freep(a); +} + +int ff_audio_data_copy(AudioData *dst, AudioData *src) +{ + int ret, p; + + /* validate input/output compatibility */ + if (dst->sample_fmt != src->sample_fmt || dst->channels < src->channels) + return AVERROR(EINVAL); + + /* if the input is empty, just empty the output */ + if (!src->nb_samples) { + dst->nb_samples = 0; + return 0; + } + + /* reallocate output if necessary */ + ret = ff_audio_data_realloc(dst, src->nb_samples); + if (ret < 0) + return ret; + + /* copy data */ + for (p = 0; p < src->planes; p++) + memcpy(dst->data[p], src->data[p], src->nb_samples * src->stride); + dst->nb_samples = src->nb_samples; + + return 0; +} + +int ff_audio_data_combine(AudioData *dst, int dst_offset, AudioData *src, + int src_offset, int nb_samples) +{ + int ret, p, dst_offset2, dst_move_size; + + /* validate input/output compatibility */ + if (dst->sample_fmt != src->sample_fmt || dst->channels != src->channels) { + av_log(src, AV_LOG_ERROR, "sample format mismatch\n"); + return AVERROR(EINVAL); + } + + /* validate offsets are within the buffer bounds */ + if (dst_offset < 0 || dst_offset > dst->nb_samples || + src_offset < 0 || src_offset > src->nb_samples) { + av_log(src, AV_LOG_ERROR, "offset out-of-bounds: src=%d dst=%d\n", + src_offset, dst_offset); + return AVERROR(EINVAL); + } + + /* check offsets and sizes to see if we can just do nothing and return */ + if (nb_samples > src->nb_samples - src_offset) + nb_samples = src->nb_samples - src_offset; + if (nb_samples <= 0) + return 0; + + /* validate that the output is not read-only */ + if (dst->read_only) { + av_log(dst, AV_LOG_ERROR, "dst is read-only\n"); + return AVERROR(EINVAL); + } + + /* reallocate output if necessary */ + ret = ff_audio_data_realloc(dst, dst->nb_samples + nb_samples); + if (ret < 0) { + av_log(dst, AV_LOG_ERROR, "error reallocating dst\n"); + return ret; + } + + dst_offset2 = dst_offset + nb_samples; + dst_move_size = dst->nb_samples - dst_offset; + + for (p = 0; p < src->planes; p++) { + if (dst_move_size > 0) { + memmove(dst->data[p] + dst_offset2 * dst->stride, + dst->data[p] + dst_offset * dst->stride, + dst_move_size * dst->stride); + } + memcpy(dst->data[p] + dst_offset * dst->stride, + src->data[p] + src_offset * src->stride, + nb_samples * src->stride); + } + dst->nb_samples += nb_samples; + + return 0; +} + +void ff_audio_data_drain(AudioData *a, int nb_samples) +{ + if (a->nb_samples <= nb_samples) { + /* drain the whole buffer */ + a->nb_samples = 0; + } else { + int p; + int move_offset = a->stride * nb_samples; + int move_size = a->stride * (a->nb_samples - nb_samples); + + for (p = 0; p < a->planes; p++) + memmove(a->data[p], a->data[p] + move_offset, move_size); + + a->nb_samples -= nb_samples; + } +} + +int ff_audio_data_add_to_fifo(AVAudioFifo *af, AudioData *a, int offset, + int nb_samples) +{ + uint8_t *offset_data[AVRESAMPLE_MAX_CHANNELS]; + int offset_size, p; + + if (offset >= a->nb_samples) + return 0; + offset_size = offset * a->stride; + for (p = 0; p < a->planes; p++) + offset_data[p] = a->data[p] + offset_size; + + return av_audio_fifo_write(af, (void **)offset_data, nb_samples); +} + +int ff_audio_data_read_from_fifo(AVAudioFifo *af, AudioData *a, int nb_samples) +{ + int ret; + + if (a->read_only) + return AVERROR(EINVAL); + + ret = ff_audio_data_realloc(a, nb_samples); + if (ret < 0) + return ret; + + ret = av_audio_fifo_read(af, (void **)a->data, nb_samples); + if (ret >= 0) + a->nb_samples = ret; + return ret; +} diff --git a/libavresample/audio_data.h b/libavresample/audio_data.h new file mode 100644 index 0000000000..4609ebc284 --- /dev/null +++ b/libavresample/audio_data.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#ifndef AVRESAMPLE_AUDIO_DATA_H +#define AVRESAMPLE_AUDIO_DATA_H + +#include <stdint.h> + +#include "libavutil/audio_fifo.h" +#include "libavutil/log.h" +#include "libavutil/samplefmt.h" +#include "avresample.h" + +/** + * Audio buffer used for intermediate storage between conversion phases. + */ +typedef struct AudioData { + const AVClass *class; /**< AVClass for logging */ + uint8_t *data[AVRESAMPLE_MAX_CHANNELS]; /**< data plane pointers */ + uint8_t *buffer; /**< data buffer */ + unsigned int buffer_size; /**< allocated buffer size */ + int allocated_samples; /**< number of samples the buffer can hold */ + int nb_samples; /**< current number of samples */ + enum AVSampleFormat sample_fmt; /**< sample format */ + int channels; /**< channel count */ + int allocated_channels; /**< allocated channel count */ + int is_planar; /**< sample format is planar */ + int planes; /**< number of data planes */ + int sample_size; /**< bytes per sample */ + int stride; /**< sample byte offset within a plane */ + int read_only; /**< data is read-only */ + int allow_realloc; /**< realloc is allowed */ + int ptr_align; /**< minimum data pointer alignment */ + int samples_align; /**< allocated samples alignment */ + const char *name; /**< name for debug logging */ +} AudioData; + +int ff_audio_data_set_channels(AudioData *a, int channels); + +/** + * Initialize AudioData using a given source. + * + * This does not allocate an internal buffer. It only sets the data pointers + * and audio parameters. + * + * @param a AudioData struct + * @param src source data pointers + * @param plane_size plane size, in bytes. + * This can be 0 if unknown, but that will lead to + * optimized functions not being used in many cases, + * which could slow down some conversions. + * @param channels channel count + * @param nb_samples number of samples in the source data + * @param sample_fmt sample format + * @param read_only indicates if buffer is read only or read/write + * @param name name for debug logging (can be NULL) + * @return 0 on success, negative AVERROR value on error + */ +int ff_audio_data_init(AudioData *a, void **src, int plane_size, int channels, + int nb_samples, enum AVSampleFormat sample_fmt, + int read_only, const char *name); + +/** + * Allocate AudioData. + * + * This allocates an internal buffer and sets audio parameters. + * + * @param channels channel count + * @param nb_samples number of samples to allocate space for + * @param sample_fmt sample format + * @param name name for debug logging (can be NULL) + * @return newly allocated AudioData struct, or NULL on error + */ +AudioData *ff_audio_data_alloc(int channels, int nb_samples, + enum AVSampleFormat sample_fmt, + const char *name); + +/** + * Reallocate AudioData. + * + * The AudioData must have been previously allocated with ff_audio_data_alloc(). + * + * @param a AudioData struct + * @param nb_samples number of samples to allocate space for + * @return 0 on success, negative AVERROR value on error + */ +int ff_audio_data_realloc(AudioData *a, int nb_samples); + +/** + * Free AudioData. + * + * The AudioData must have been previously allocated with ff_audio_data_alloc(). + * + * @param a AudioData struct + */ +void ff_audio_data_free(AudioData **a); + +/** + * Copy data from one AudioData to another. + * + * @param out output AudioData + * @param in input AudioData + * @return 0 on success, negative AVERROR value on error + */ +int ff_audio_data_copy(AudioData *out, AudioData *in); + +/** + * Append data from one AudioData to the end of another. + * + * @param dst destination AudioData + * @param dst_offset offset, in samples, to start writing, relative to the + * start of dst + * @param src source AudioData + * @param src_offset offset, in samples, to start copying, relative to the + * start of the src + * @param nb_samples number of samples to copy + * @return 0 on success, negative AVERROR value on error + */ +int ff_audio_data_combine(AudioData *dst, int dst_offset, AudioData *src, + int src_offset, int nb_samples); + +/** + * Drain samples from the start of the AudioData. + * + * Remaining samples are shifted to the start of the AudioData. + * + * @param a AudioData struct + * @param nb_samples number of samples to drain + */ +void ff_audio_data_drain(AudioData *a, int nb_samples); + +/** + * Add samples in AudioData to an AVAudioFifo. + * + * @param af Audio FIFO Buffer + * @param a AudioData struct + * @param offset number of samples to skip from the start of the data + * @param nb_samples number of samples to add to the FIFO + * @return number of samples actually added to the FIFO, or + * negative AVERROR code on error + */ +int ff_audio_data_add_to_fifo(AVAudioFifo *af, AudioData *a, int offset, + int nb_samples); + +/** + * Read samples from an AVAudioFifo to AudioData. + * + * @param af Audio FIFO Buffer + * @param a AudioData struct + * @param nb_samples number of samples to read from the FIFO + * @return number of samples actually read from the FIFO, or + * negative AVERROR code on error + */ +int ff_audio_data_read_from_fifo(AVAudioFifo *af, AudioData *a, int nb_samples); + +#endif /* AVRESAMPLE_AUDIO_DATA_H */ diff --git a/libavresample/audio_mix.c b/libavresample/audio_mix.c new file mode 100644 index 0000000000..34252bf68d --- /dev/null +++ b/libavresample/audio_mix.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include <stdint.h> + +#include "libavutil/libm.h" +#include "libavutil/samplefmt.h" +#include "avresample.h" +#include "internal.h" +#include "audio_data.h" +#include "audio_mix.h" + +static const char *coeff_type_names[] = { "q6", "q15", "flt" }; + +void ff_audio_mix_set_func(AudioMix *am, enum AVSampleFormat fmt, + enum AVMixCoeffType coeff_type, int in_channels, + int out_channels, int ptr_align, int samples_align, + const char *descr, void *mix_func) +{ + if (fmt == am->fmt && coeff_type == am->coeff_type && + ( in_channels == am->in_channels || in_channels == 0) && + (out_channels == am->out_channels || out_channels == 0)) { + char chan_str[16]; + am->mix = mix_func; + am->func_descr = descr; + am->ptr_align = ptr_align; + am->samples_align = samples_align; + if (ptr_align == 1 && samples_align == 1) { + am->mix_generic = mix_func; + am->func_descr_generic = descr; + } else { + am->has_optimized_func = 1; + } + if (in_channels) { + if (out_channels) + snprintf(chan_str, sizeof(chan_str), "[%d to %d] ", + in_channels, out_channels); + else + snprintf(chan_str, sizeof(chan_str), "[%d to any] ", + in_channels); + } else if (out_channels) { + snprintf(chan_str, sizeof(chan_str), "[any to %d] ", + out_channels); + } + av_log(am->avr, AV_LOG_DEBUG, "audio_mix: found function: [fmt=%s] " + "[c=%s] %s(%s)\n", av_get_sample_fmt_name(fmt), + coeff_type_names[coeff_type], + (in_channels || out_channels) ? chan_str : "", descr); + } +} + +#define MIX_FUNC_NAME(fmt, cfmt) mix_any_ ## fmt ##_## cfmt ##_c + +#define MIX_FUNC_GENERIC(fmt, cfmt, stype, ctype, sumtype, expr) \ +static void MIX_FUNC_NAME(fmt, cfmt)(stype **samples, ctype **matrix, \ + int len, int out_ch, int in_ch) \ +{ \ + int i, in, out; \ + stype temp[AVRESAMPLE_MAX_CHANNELS]; \ + for (i = 0; i < len; i++) { \ + for (out = 0; out < out_ch; out++) { \ + sumtype sum = 0; \ + for (in = 0; in < in_ch; in++) \ + sum += samples[in][i] * matrix[out][in]; \ + temp[out] = expr; \ + } \ + for (out = 0; out < out_ch; out++) \ + samples[out][i] = temp[out]; \ + } \ +} + +MIX_FUNC_GENERIC(FLTP, FLT, float, float, float, sum) +MIX_FUNC_GENERIC(S16P, FLT, int16_t, float, float, av_clip_int16(lrintf(sum))) +MIX_FUNC_GENERIC(S16P, Q15, int16_t, int32_t, int64_t, av_clip_int16(sum >> 15)) +MIX_FUNC_GENERIC(S16P, Q6, int16_t, int16_t, int32_t, av_clip_int16(sum >> 6)) + +/* TODO: templatize the channel-specific C functions */ + +static void mix_2_to_1_fltp_flt_c(float **samples, float **matrix, int len, + int out_ch, int in_ch) +{ + float *src0 = samples[0]; + float *src1 = samples[1]; + float *dst = src0; + float m0 = matrix[0][0]; + float m1 = matrix[0][1]; + + while (len > 4) { + *dst++ = *src0++ * m0 + *src1++ * m1; + *dst++ = *src0++ * m0 + *src1++ * m1; + *dst++ = *src0++ * m0 + *src1++ * m1; + *dst++ = *src0++ * m0 + *src1++ * m1; + len -= 4; + } + while (len > 0) { + *dst++ = *src0++ * m0 + *src1++ * m1; + len--; + } +} + +static void mix_1_to_2_fltp_flt_c(float **samples, float **matrix, int len, + int out_ch, int in_ch) +{ + float v; + float *dst0 = samples[0]; + float *dst1 = samples[1]; + float *src = dst0; + float m0 = matrix[0][0]; + float m1 = matrix[1][0]; + + while (len > 4) { + v = *src++; + *dst0++ = v * m1; + *dst1++ = v * m0; + v = *src++; + *dst0++ = v * m1; + *dst1++ = v * m0; + v = *src++; + *dst0++ = v * m1; + *dst1++ = v * m0; + v = *src++; + *dst0++ = v * m1; + *dst1++ = v * m0; + len -= 4; + } + while (len > 0) { + v = *src++; + *dst0++ = v * m1; + *dst1++ = v * m0; + len--; + } +} + +static void mix_6_to_2_fltp_flt_c(float **samples, float **matrix, int len, + int out_ch, int in_ch) +{ + float v0, v1; + float *src0 = samples[0]; + float *src1 = samples[1]; + float *src2 = samples[2]; + float *src3 = samples[3]; + float *src4 = samples[4]; + float *src5 = samples[5]; + float *dst0 = src0; + float *dst1 = src1; + float *m0 = matrix[0]; + float *m1 = matrix[1]; + + while (len > 0) { + v0 = *src0++; + v1 = *src1++; + *dst0++ = v0 * m0[0] + + v1 * m0[1] + + *src2 * m0[2] + + *src3 * m0[3] + + *src4 * m0[4] + + *src5 * m0[5]; + *dst1++ = v0 * m1[0] + + v1 * m1[1] + + *src2++ * m1[2] + + *src3++ * m1[3] + + *src4++ * m1[4] + + *src5++ * m1[5]; + len--; + } +} + +static void mix_2_to_6_fltp_flt_c(float **samples, float **matrix, int len, + int out_ch, int in_ch) +{ + float v0, v1; + float *dst0 = samples[0]; + float *dst1 = samples[1]; + float *dst2 = samples[2]; + float *dst3 = samples[3]; + float *dst4 = samples[4]; + float *dst5 = samples[5]; + float *src0 = dst0; + float *src1 = dst1; + + while (len > 0) { + v0 = *src0++; + v1 = *src1++; + *dst0++ = v0 * matrix[0][0] + v1 * matrix[0][1]; + *dst1++ = v0 * matrix[1][0] + v1 * matrix[1][1]; + *dst2++ = v0 * matrix[2][0] + v1 * matrix[2][1]; + *dst3++ = v0 * matrix[3][0] + v1 * matrix[3][1]; + *dst4++ = v0 * matrix[4][0] + v1 * matrix[4][1]; + *dst5++ = v0 * matrix[5][0] + v1 * matrix[5][1]; + len--; + } +} + +static int mix_function_init(AudioMix *am) +{ + /* any-to-any C versions */ + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 0, 0, 1, 1, "C", MIX_FUNC_NAME(FLTP, FLT)); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_S16P, AV_MIX_COEFF_TYPE_FLT, + 0, 0, 1, 1, "C", MIX_FUNC_NAME(S16P, FLT)); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_S16P, AV_MIX_COEFF_TYPE_Q15, + 0, 0, 1, 1, "C", MIX_FUNC_NAME(S16P, Q15)); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_S16P, AV_MIX_COEFF_TYPE_Q6, + 0, 0, 1, 1, "C", MIX_FUNC_NAME(S16P, Q6)); + + /* channel-specific C versions */ + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 2, 1, 1, 1, "C", mix_2_to_1_fltp_flt_c); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 1, 2, 1, 1, "C", mix_1_to_2_fltp_flt_c); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 6, 2, 1, 1, "C", mix_6_to_2_fltp_flt_c); + + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 2, 6, 1, 1, "C", mix_2_to_6_fltp_flt_c); + + if (ARCH_X86) + ff_audio_mix_init_x86(am); + + if (!am->mix) { + av_log(am->avr, AV_LOG_ERROR, "audio_mix: NO FUNCTION FOUND: [fmt=%s] " + "[c=%s] [%d to %d]\n", av_get_sample_fmt_name(am->fmt), + coeff_type_names[am->coeff_type], am->in_channels, + am->out_channels); + return AVERROR_PATCHWELCOME; + } + return 0; +} + +int ff_audio_mix_init(AVAudioResampleContext *avr) +{ + int ret; + + /* build matrix if the user did not already set one */ + if (!avr->am->matrix) { + int i, j; + char in_layout_name[128]; + char out_layout_name[128]; + double *matrix_dbl = av_mallocz(avr->out_channels * avr->in_channels * + sizeof(*matrix_dbl)); + if (!matrix_dbl) + return AVERROR(ENOMEM); + + ret = avresample_build_matrix(avr->in_channel_layout, + avr->out_channel_layout, + avr->center_mix_level, + avr->surround_mix_level, + avr->lfe_mix_level, 1, matrix_dbl, + avr->in_channels); + if (ret < 0) { + av_free(matrix_dbl); + return ret; + } + + av_get_channel_layout_string(in_layout_name, sizeof(in_layout_name), + avr->in_channels, avr->in_channel_layout); + av_get_channel_layout_string(out_layout_name, sizeof(out_layout_name), + avr->out_channels, avr->out_channel_layout); + av_log(avr, AV_LOG_DEBUG, "audio_mix: %s to %s\n", + in_layout_name, out_layout_name); + for (i = 0; i < avr->out_channels; i++) { + for (j = 0; j < avr->in_channels; j++) { + av_log(avr, AV_LOG_DEBUG, " %0.3f ", + matrix_dbl[i * avr->in_channels + j]); + } + av_log(avr, AV_LOG_DEBUG, "\n"); + } + + ret = avresample_set_matrix(avr, matrix_dbl, avr->in_channels); + if (ret < 0) { + av_free(matrix_dbl); + return ret; + } + av_free(matrix_dbl); + } + + avr->am->fmt = avr->internal_sample_fmt; + avr->am->coeff_type = avr->mix_coeff_type; + avr->am->in_layout = avr->in_channel_layout; + avr->am->out_layout = avr->out_channel_layout; + avr->am->in_channels = avr->in_channels; + avr->am->out_channels = avr->out_channels; + + ret = mix_function_init(avr->am); + if (ret < 0) + return ret; + + return 0; +} + +void ff_audio_mix_close(AudioMix *am) +{ + if (!am) + return; + if (am->matrix) { + av_free(am->matrix[0]); + am->matrix = NULL; + } + memset(am->matrix_q6, 0, sizeof(am->matrix_q6 )); + memset(am->matrix_q15, 0, sizeof(am->matrix_q15)); + memset(am->matrix_flt, 0, sizeof(am->matrix_flt)); +} + +int ff_audio_mix(AudioMix *am, AudioData *src) +{ + int use_generic = 1; + int len = src->nb_samples; + + /* determine whether to use the optimized function based on pointer and + samples alignment in both the input and output */ + if (am->has_optimized_func) { + int aligned_len = FFALIGN(len, am->samples_align); + if (!(src->ptr_align % am->ptr_align) && + src->samples_align >= aligned_len) { + len = aligned_len; + use_generic = 0; + } + } + av_dlog(am->avr, "audio_mix: %d samples - %d to %d channels (%s)\n", + src->nb_samples, am->in_channels, am->out_channels, + use_generic ? am->func_descr_generic : am->func_descr); + + if (use_generic) + am->mix_generic(src->data, am->matrix, len, am->out_channels, + am->in_channels); + else + am->mix(src->data, am->matrix, len, am->out_channels, am->in_channels); + + ff_audio_data_set_channels(src, am->out_channels); + + return 0; +} diff --git a/libavresample/audio_mix.h b/libavresample/audio_mix.h new file mode 100644 index 0000000000..ffa1b23842 --- /dev/null +++ b/libavresample/audio_mix.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#ifndef AVRESAMPLE_AUDIO_MIX_H +#define AVRESAMPLE_AUDIO_MIX_H + +#include <stdint.h> + +#include "libavutil/samplefmt.h" +#include "avresample.h" +#include "audio_data.h" + +typedef void (mix_func)(uint8_t **src, void **matrix, int len, int out_ch, + int in_ch); + +typedef struct AudioMix { + AVAudioResampleContext *avr; + enum AVSampleFormat fmt; + enum AVMixCoeffType coeff_type; + uint64_t in_layout; + uint64_t out_layout; + int in_channels; + int out_channels; + + int ptr_align; + int samples_align; + int has_optimized_func; + const char *func_descr; + const char *func_descr_generic; + mix_func *mix; + mix_func *mix_generic; + + int16_t *matrix_q6[AVRESAMPLE_MAX_CHANNELS]; + int32_t *matrix_q15[AVRESAMPLE_MAX_CHANNELS]; + float *matrix_flt[AVRESAMPLE_MAX_CHANNELS]; + void **matrix; +} AudioMix; + +/** + * Set mixing function if the parameters match. + * + * This compares the parameters of the mixing function to the parameters in the + * AudioMix context. If the parameters do not match, no changes are made to the + * active functions. If the parameters do match and the alignment is not + * constrained, the function is set as the generic mixing function. If the + * parameters match and the alignment is constrained, the function is set as + * the optimized mixing function. + * + * @param am AudioMix context + * @param fmt input/output sample format + * @param coeff_type mixing coefficient type + * @param in_channels number of input channels, or 0 for any number of channels + * @param out_channels number of output channels, or 0 for any number of channels + * @param ptr_align buffer pointer alignment, in bytes + * @param sample_align buffer size alignment, in samples + * @param descr function type description (e.g. "C" or "SSE") + * @param mix_func mixing function pointer + */ +void ff_audio_mix_set_func(AudioMix *am, enum AVSampleFormat fmt, + enum AVMixCoeffType coeff_type, int in_channels, + int out_channels, int ptr_align, int samples_align, + const char *descr, void *mix_func); + +/** + * Initialize the AudioMix context in the AVAudioResampleContext. + * + * The parameters in the AVAudioResampleContext are used to initialize the + * AudioMix context and set the mixing matrix. + * + * @param avr AVAudioResampleContext + * @return 0 on success, negative AVERROR code on failure + */ +int ff_audio_mix_init(AVAudioResampleContext *avr); + +/** + * Close an AudioMix context. + * + * This clears and frees the mixing matrix arrays. + */ +void ff_audio_mix_close(AudioMix *am); + +/** + * Apply channel mixing to audio data using the current mixing matrix. + */ +int ff_audio_mix(AudioMix *am, AudioData *src); + +/* arch-specific initialization functions */ + +void ff_audio_mix_init_x86(AudioMix *am); + +#endif /* AVRESAMPLE_AUDIO_MIX_H */ diff --git a/libavresample/audio_mix_matrix.c b/libavresample/audio_mix_matrix.c new file mode 100644 index 0000000000..96c49ef26f --- /dev/null +++ b/libavresample/audio_mix_matrix.c @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2011 Michael Niedermayer (michaelni@gmx.at) + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include <stdint.h> + +#include "libavutil/libm.h" +#include "libavutil/samplefmt.h" +#include "avresample.h" +#include "internal.h" +#include "audio_data.h" +#include "audio_mix.h" + +/* channel positions */ +#define FRONT_LEFT 0 +#define FRONT_RIGHT 1 +#define FRONT_CENTER 2 +#define LOW_FREQUENCY 3 +#define BACK_LEFT 4 +#define BACK_RIGHT 5 +#define FRONT_LEFT_OF_CENTER 6 +#define FRONT_RIGHT_OF_CENTER 7 +#define BACK_CENTER 8 +#define SIDE_LEFT 9 +#define SIDE_RIGHT 10 +#define TOP_CENTER 11 +#define TOP_FRONT_LEFT 12 +#define TOP_FRONT_CENTER 13 +#define TOP_FRONT_RIGHT 14 +#define TOP_BACK_LEFT 15 +#define TOP_BACK_CENTER 16 +#define TOP_BACK_RIGHT 17 +#define STEREO_LEFT 29 +#define STEREO_RIGHT 30 +#define WIDE_LEFT 31 +#define WIDE_RIGHT 32 +#define SURROUND_DIRECT_LEFT 33 +#define SURROUND_DIRECT_RIGHT 34 + +static av_always_inline int even(uint64_t layout) +{ + return (!layout || (layout & (layout - 1))); +} + +static int sane_layout(uint64_t layout) +{ + /* check that there is at least 1 front speaker */ + if (!(layout & AV_CH_LAYOUT_SURROUND)) + return 0; + + /* check for left/right symmetry */ + if (!even(layout & (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT)) || + !even(layout & (AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT)) || + !even(layout & (AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT)) || + !even(layout & (AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER)) || + !even(layout & (AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT)) || + !even(layout & (AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT)) || + !even(layout & (AV_CH_STEREO_LEFT | AV_CH_STEREO_RIGHT)) || + !even(layout & (AV_CH_WIDE_LEFT | AV_CH_WIDE_RIGHT)) || + !even(layout & (AV_CH_SURROUND_DIRECT_LEFT | AV_CH_SURROUND_DIRECT_RIGHT))) + return 0; + + return 1; +} + +int avresample_build_matrix(uint64_t in_layout, uint64_t out_layout, + double center_mix_level, double surround_mix_level, + double lfe_mix_level, int normalize, + double *matrix_out, int stride) +{ + int i, j, out_i, out_j; + double matrix[64][64] = {{0}}; + int64_t unaccounted = in_layout & ~out_layout; + double maxcoef = 0; + int in_channels, out_channels; + + in_channels = av_get_channel_layout_nb_channels( in_layout); + out_channels = av_get_channel_layout_nb_channels(out_layout); + + memset(matrix_out, 0, out_channels * stride * sizeof(*matrix_out)); + + /* check if layouts are supported */ + if (!in_layout || in_channels > AVRESAMPLE_MAX_CHANNELS) + return AVERROR(EINVAL); + if (!out_layout || out_channels > AVRESAMPLE_MAX_CHANNELS) + return AVERROR(EINVAL); + + /* check if layouts are unbalanced or abnormal */ + if (!sane_layout(in_layout) || !sane_layout(out_layout)) + return AVERROR_PATCHWELCOME; + + /* route matching input/output channels */ + for (i = 0; i < 64; i++) { + if (in_layout & out_layout & (1ULL << i)) + matrix[i][i] = 1.0; + } + + /* mix front center to front left/right */ + if (unaccounted & AV_CH_FRONT_CENTER) { + if ((out_layout & AV_CH_LAYOUT_STEREO) == AV_CH_LAYOUT_STEREO) { + matrix[FRONT_LEFT ][FRONT_CENTER] += M_SQRT1_2; + matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix front left/right to center */ + if (unaccounted & AV_CH_LAYOUT_STEREO) { + if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][FRONT_LEFT ] += M_SQRT1_2; + matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2; + /* mix left/right/center to center */ + if (in_layout & AV_CH_FRONT_CENTER) + matrix[FRONT_CENTER][FRONT_CENTER] = center_mix_level * M_SQRT2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix back center to back, side, or front */ + if (unaccounted & AV_CH_BACK_CENTER) { + if (out_layout & AV_CH_BACK_LEFT) { + matrix[BACK_LEFT ][BACK_CENTER] += M_SQRT1_2; + matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2; + } else if (out_layout & AV_CH_SIDE_LEFT) { + matrix[SIDE_LEFT ][BACK_CENTER] += M_SQRT1_2; + matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2; + } else if (out_layout & AV_CH_FRONT_LEFT) { + matrix[FRONT_LEFT ][BACK_CENTER] += surround_mix_level * M_SQRT1_2; + matrix[FRONT_RIGHT][BACK_CENTER] += surround_mix_level * M_SQRT1_2; + } else if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][BACK_CENTER] += surround_mix_level * M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix back left/right to back center, side, or front */ + if (unaccounted & AV_CH_BACK_LEFT) { + if (out_layout & AV_CH_BACK_CENTER) { + matrix[BACK_CENTER][BACK_LEFT ] += M_SQRT1_2; + matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2; + } else if (out_layout & AV_CH_SIDE_LEFT) { + /* if side channels do not exist in the input, just copy back + channels to side channels, otherwise mix back into side */ + if (in_layout & AV_CH_SIDE_LEFT) { + matrix[SIDE_LEFT ][BACK_LEFT ] += M_SQRT1_2; + matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2; + } else { + matrix[SIDE_LEFT ][BACK_LEFT ] += 1.0; + matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0; + } + } else if (out_layout & AV_CH_FRONT_LEFT) { + matrix[FRONT_LEFT ][BACK_LEFT ] += surround_mix_level; + matrix[FRONT_RIGHT][BACK_RIGHT] += surround_mix_level; + } else if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][BACK_LEFT ] += surround_mix_level * M_SQRT1_2; + matrix[FRONT_CENTER][BACK_RIGHT] += surround_mix_level * M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix side left/right into back or front */ + if (unaccounted & AV_CH_SIDE_LEFT) { + if (out_layout & AV_CH_BACK_LEFT) { + /* if back channels do not exist in the input, just copy side + channels to back channels, otherwise mix side into back */ + if (in_layout & AV_CH_BACK_LEFT) { + matrix[BACK_LEFT ][SIDE_LEFT ] += M_SQRT1_2; + matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2; + } else { + matrix[BACK_LEFT ][SIDE_LEFT ] += 1.0; + matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0; + } + } else if (out_layout & AV_CH_BACK_CENTER) { + matrix[BACK_CENTER][SIDE_LEFT ] += M_SQRT1_2; + matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2; + } else if (out_layout & AV_CH_FRONT_LEFT) { + matrix[FRONT_LEFT ][SIDE_LEFT ] += surround_mix_level; + matrix[FRONT_RIGHT][SIDE_RIGHT] += surround_mix_level; + } else if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][SIDE_LEFT ] += surround_mix_level * M_SQRT1_2; + matrix[FRONT_CENTER][SIDE_RIGHT] += surround_mix_level * M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix left-of-center/right-of-center into front left/right or center */ + if (unaccounted & AV_CH_FRONT_LEFT_OF_CENTER) { + if (out_layout & AV_CH_FRONT_LEFT) { + matrix[FRONT_LEFT ][FRONT_LEFT_OF_CENTER ] += 1.0; + matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0; + } else if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER ] += M_SQRT1_2; + matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + /* mix LFE into front left/right or center */ + if (unaccounted & AV_CH_LOW_FREQUENCY) { + if (out_layout & AV_CH_FRONT_CENTER) { + matrix[FRONT_CENTER][LOW_FREQUENCY] += lfe_mix_level; + } else if (out_layout & AV_CH_FRONT_LEFT) { + matrix[FRONT_LEFT ][LOW_FREQUENCY] += lfe_mix_level * M_SQRT1_2; + matrix[FRONT_RIGHT][LOW_FREQUENCY] += lfe_mix_level * M_SQRT1_2; + } else + return AVERROR_PATCHWELCOME; + } + + /* transfer internal matrix to output matrix and calculate maximum + per-channel coefficient sum */ + for (out_i = i = 0; out_i < out_channels && i < 64; i++) { + double sum = 0; + for (out_j = j = 0; out_j < in_channels && j < 64; j++) { + matrix_out[out_i * stride + out_j] = matrix[i][j]; + sum += fabs(matrix[i][j]); + if (in_layout & (1ULL << j)) + out_j++; + } + maxcoef = FFMAX(maxcoef, sum); + if (out_layout & (1ULL << i)) + out_i++; + } + + /* normalize */ + if (normalize && maxcoef > 1.0) { + for (i = 0; i < out_channels; i++) + for (j = 0; j < in_channels; j++) + matrix_out[i * stride + j] /= maxcoef; + } + + return 0; +} + +int avresample_get_matrix(AVAudioResampleContext *avr, double *matrix, + int stride) +{ + int in_channels, out_channels, i, o; + + in_channels = av_get_channel_layout_nb_channels(avr->in_channel_layout); + out_channels = av_get_channel_layout_nb_channels(avr->out_channel_layout); + + if ( in_channels < 0 || in_channels > AVRESAMPLE_MAX_CHANNELS || + out_channels < 0 || out_channels > AVRESAMPLE_MAX_CHANNELS) { + av_log(avr, AV_LOG_ERROR, "Invalid channel layouts\n"); + return AVERROR(EINVAL); + } + + switch (avr->mix_coeff_type) { + case AV_MIX_COEFF_TYPE_Q6: + if (!avr->am->matrix_q6[0]) { + av_log(avr, AV_LOG_ERROR, "matrix is not set\n"); + return AVERROR(EINVAL); + } + for (o = 0; o < out_channels; o++) + for (i = 0; i < in_channels; i++) + matrix[o * stride + i] = avr->am->matrix_q6[o][i] / 64.0; + break; + case AV_MIX_COEFF_TYPE_Q15: + if (!avr->am->matrix_q15[0]) { + av_log(avr, AV_LOG_ERROR, "matrix is not set\n"); + return AVERROR(EINVAL); + } + for (o = 0; o < out_channels; o++) + for (i = 0; i < in_channels; i++) + matrix[o * stride + i] = avr->am->matrix_q15[o][i] / 32768.0; + break; + case AV_MIX_COEFF_TYPE_FLT: + if (!avr->am->matrix_flt[0]) { + av_log(avr, AV_LOG_ERROR, "matrix is not set\n"); + return AVERROR(EINVAL); + } + for (o = 0; o < out_channels; o++) + for (i = 0; i < in_channels; i++) + matrix[o * stride + i] = avr->am->matrix_flt[o][i]; + break; + default: + av_log(avr, AV_LOG_ERROR, "Invalid mix coeff type\n"); + return AVERROR(EINVAL); + } + return 0; +} + +int avresample_set_matrix(AVAudioResampleContext *avr, const double *matrix, + int stride) +{ + int in_channels, out_channels, i, o; + + in_channels = av_get_channel_layout_nb_channels(avr->in_channel_layout); + out_channels = av_get_channel_layout_nb_channels(avr->out_channel_layout); + + if ( in_channels < 0 || in_channels > AVRESAMPLE_MAX_CHANNELS || + out_channels < 0 || out_channels > AVRESAMPLE_MAX_CHANNELS) { + av_log(avr, AV_LOG_ERROR, "Invalid channel layouts\n"); + return AVERROR(EINVAL); + } + + if (avr->am->matrix) + av_freep(avr->am->matrix); + +#define CONVERT_MATRIX(type, expr) \ + avr->am->matrix_## type[0] = av_mallocz(out_channels * in_channels * \ + sizeof(*avr->am->matrix_## type[0])); \ + if (!avr->am->matrix_## type[0]) \ + return AVERROR(ENOMEM); \ + for (o = 0; o < out_channels; o++) { \ + if (o > 0) \ + avr->am->matrix_## type[o] = avr->am->matrix_## type[o - 1] + \ + in_channels; \ + for (i = 0; i < in_channels; i++) { \ + double v = matrix[o * stride + i]; \ + avr->am->matrix_## type[o][i] = expr; \ + } \ + } \ + avr->am->matrix = (void **)avr->am->matrix_## type; + + switch (avr->mix_coeff_type) { + case AV_MIX_COEFF_TYPE_Q6: + CONVERT_MATRIX(q6, av_clip_int16(lrint(64.0 * v))) + break; + case AV_MIX_COEFF_TYPE_Q15: + CONVERT_MATRIX(q15, av_clipl_int32(llrint(32768.0 * v))) + break; + case AV_MIX_COEFF_TYPE_FLT: + CONVERT_MATRIX(flt, v) + break; + default: + av_log(avr, AV_LOG_ERROR, "Invalid mix coeff type\n"); + return AVERROR(EINVAL); + } + + /* TODO: detect situations where we can just swap around pointers + instead of doing matrix multiplications with 0.0 and 1.0 */ + + return 0; +} diff --git a/libavresample/avresample-test.c b/libavresample/avresample-test.c new file mode 100644 index 0000000000..ad2f16d6f6 --- /dev/null +++ b/libavresample/avresample-test.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2002 Fabrice Bellard + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include <stdint.h> +#include <stdio.h> + +#include "libavutil/avstring.h" +#include "libavutil/lfg.h" +#include "libavutil/libm.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/samplefmt.h" +#include "avresample.h" + +static double dbl_rand(AVLFG *lfg) +{ + return 2.0 * (av_lfg_get(lfg) / (double)UINT_MAX) - 1.0; +} + +#define PUT_FUNC(name, fmt, type, expr) \ +static void put_sample_ ## name(void **data, enum AVSampleFormat sample_fmt,\ + int channels, int sample, int ch, \ + double v_dbl) \ +{ \ + type v = expr; \ + type **out = (type **)data; \ + if (av_sample_fmt_is_planar(sample_fmt)) \ + out[ch][sample] = v; \ + else \ + out[0][sample * channels + ch] = v; \ +} + +PUT_FUNC(u8, AV_SAMPLE_FMT_U8, uint8_t, av_clip_uint8 ( lrint(v_dbl * (1 << 7)) + 128)) +PUT_FUNC(s16, AV_SAMPLE_FMT_S16, int16_t, av_clip_int16 ( lrint(v_dbl * (1 << 15)))) +PUT_FUNC(s32, AV_SAMPLE_FMT_S32, int32_t, av_clipl_int32(llrint(v_dbl * (1U << 31)))) +PUT_FUNC(flt, AV_SAMPLE_FMT_FLT, float, v_dbl) +PUT_FUNC(dbl, AV_SAMPLE_FMT_DBL, double, v_dbl) + +static void put_sample(void **data, enum AVSampleFormat sample_fmt, + int channels, int sample, int ch, double v_dbl) +{ + switch (av_get_packed_sample_fmt(sample_fmt)) { + case AV_SAMPLE_FMT_U8: + put_sample_u8(data, sample_fmt, channels, sample, ch, v_dbl); + break; + case AV_SAMPLE_FMT_S16: + put_sample_s16(data, sample_fmt, channels, sample, ch, v_dbl); + break; + case AV_SAMPLE_FMT_S32: + put_sample_s32(data, sample_fmt, channels, sample, ch, v_dbl); + break; + case AV_SAMPLE_FMT_FLT: + put_sample_flt(data, sample_fmt, channels, sample, ch, v_dbl); + break; + case AV_SAMPLE_FMT_DBL: + put_sample_dbl(data, sample_fmt, channels, sample, ch, v_dbl); + break; + } +} + +static void audiogen(AVLFG *rnd, void **data, enum AVSampleFormat sample_fmt, + int channels, int sample_rate, int nb_samples) +{ + int i, ch, k; + double v, f, a, ampa; + double tabf1[AVRESAMPLE_MAX_CHANNELS]; + double tabf2[AVRESAMPLE_MAX_CHANNELS]; + double taba[AVRESAMPLE_MAX_CHANNELS]; + +#define PUT_SAMPLE put_sample(data, sample_fmt, channels, k, ch, v); + + k = 0; + + /* 1 second of single freq sinus at 1000 Hz */ + a = 0; + for (i = 0; i < 1 * sample_rate && k < nb_samples; i++, k++) { + v = sin(a) * 0.30; + for (ch = 0; ch < channels; ch++) + PUT_SAMPLE + a += M_PI * 1000.0 * 2.0 / sample_rate; + } + + /* 1 second of varing frequency between 100 and 10000 Hz */ + a = 0; + for (i = 0; i < 1 * sample_rate && k < nb_samples; i++, k++) { + v = sin(a) * 0.30; + for (ch = 0; ch < channels; ch++) + PUT_SAMPLE + f = 100.0 + (((10000.0 - 100.0) * i) / sample_rate); + a += M_PI * f * 2.0 / sample_rate; + } + + /* 0.5 second of low amplitude white noise */ + for (i = 0; i < sample_rate / 2 && k < nb_samples; i++, k++) { + v = dbl_rand(rnd) * 0.30; + for (ch = 0; ch < channels; ch++) + PUT_SAMPLE + } + + /* 0.5 second of high amplitude white noise */ + for (i = 0; i < sample_rate / 2 && k < nb_samples; i++, k++) { + v = dbl_rand(rnd); + for (ch = 0; ch < channels; ch++) + PUT_SAMPLE + } + + /* 1 second of unrelated ramps for each channel */ + for (ch = 0; ch < channels; ch++) { + taba[ch] = 0; + tabf1[ch] = 100 + av_lfg_get(rnd) % 5000; + tabf2[ch] = 100 + av_lfg_get(rnd) % 5000; + } + for (i = 0; i < 1 * sample_rate && k < nb_samples; i++, k++) { + for (ch = 0; ch < channels; ch++) { + v = sin(taba[ch]) * 0.30; + PUT_SAMPLE + f = tabf1[ch] + (((tabf2[ch] - tabf1[ch]) * i) / sample_rate); + taba[ch] += M_PI * f * 2.0 / sample_rate; + } + } + + /* 2 seconds of 500 Hz with varying volume */ + a = 0; + ampa = 0; + for (i = 0; i < 2 * sample_rate && k < nb_samples; i++, k++) { + for (ch = 0; ch < channels; ch++) { + double amp = (1.0 + sin(ampa)) * 0.15; + if (ch & 1) + amp = 0.30 - amp; + v = sin(a) * amp; + PUT_SAMPLE + a += M_PI * 500.0 * 2.0 / sample_rate; + ampa += M_PI * 2.0 / sample_rate; + } + } +} + +/* formats, rates, and layouts are ordered for priority in testing. + e.g. 'avresample-test 4 2 2' will test all input/output combinations of + S16/FLTP/S16P/FLT, 48000/44100, and stereo/mono */ + +static const enum AVSampleFormat formats[] = { + AV_SAMPLE_FMT_S16, + AV_SAMPLE_FMT_FLTP, + AV_SAMPLE_FMT_S16P, + AV_SAMPLE_FMT_FLT, + AV_SAMPLE_FMT_S32P, + AV_SAMPLE_FMT_S32, + AV_SAMPLE_FMT_U8P, + AV_SAMPLE_FMT_U8, + AV_SAMPLE_FMT_DBLP, + AV_SAMPLE_FMT_DBL, +}; + +static const int rates[] = { + 48000, + 44100, + 16000 +}; + +static const uint64_t layouts[] = { + AV_CH_LAYOUT_STEREO, + AV_CH_LAYOUT_MONO, + AV_CH_LAYOUT_5POINT1, + AV_CH_LAYOUT_7POINT1, +}; + +int main(int argc, char **argv) +{ + AVAudioResampleContext *s; + AVLFG rnd; + int ret = 0; + uint8_t *in_buf = NULL; + uint8_t *out_buf = NULL; + unsigned int in_buf_size; + unsigned int out_buf_size; + uint8_t *in_data[AVRESAMPLE_MAX_CHANNELS] = { 0 }; + uint8_t *out_data[AVRESAMPLE_MAX_CHANNELS] = { 0 }; + int in_linesize; + int out_linesize; + uint64_t in_ch_layout; + int in_channels; + enum AVSampleFormat in_fmt; + int in_rate; + uint64_t out_ch_layout; + int out_channels; + enum AVSampleFormat out_fmt; + int out_rate; + int num_formats, num_rates, num_layouts; + int i, j, k, l, m, n; + + num_formats = 2; + num_rates = 2; + num_layouts = 2; + if (argc > 1) { + if (!av_strncasecmp(argv[1], "-h", 3)) { + av_log(NULL, AV_LOG_INFO, "Usage: avresample-test [<num formats> " + "[<num sample rates> [<num channel layouts>]]]\n" + "Default is 2 2 2\n"); + return 0; + } + num_formats = strtol(argv[1], NULL, 0); + num_formats = av_clip(num_formats, 1, FF_ARRAY_ELEMS(formats)); + } + if (argc > 2) { + num_rates = strtol(argv[2], NULL, 0); + num_rates = av_clip(num_rates, 1, FF_ARRAY_ELEMS(rates)); + } + if (argc > 3) { + num_layouts = strtol(argv[3], NULL, 0); + num_layouts = av_clip(num_layouts, 1, FF_ARRAY_ELEMS(layouts)); + } + + av_log_set_level(AV_LOG_DEBUG); + + av_lfg_init(&rnd, 0xC0FFEE); + + in_buf_size = av_samples_get_buffer_size(&in_linesize, 8, 48000 * 6, + AV_SAMPLE_FMT_DBLP, 0); + out_buf_size = in_buf_size; + + in_buf = av_malloc(in_buf_size); + if (!in_buf) + goto end; + out_buf = av_malloc(out_buf_size); + if (!out_buf) + goto end; + + s = avresample_alloc_context(); + if (!s) { + av_log(NULL, AV_LOG_ERROR, "Error allocating AVAudioResampleContext\n"); + ret = 1; + goto end; + } + + for (i = 0; i < num_formats; i++) { + in_fmt = formats[i]; + for (k = 0; k < num_layouts; k++) { + in_ch_layout = layouts[k]; + in_channels = av_get_channel_layout_nb_channels(in_ch_layout); + for (m = 0; m < num_rates; m++) { + in_rate = rates[m]; + + ret = av_samples_fill_arrays(in_data, &in_linesize, in_buf, + in_channels, in_rate * 6, + in_fmt, 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "failed in_data fill arrays\n"); + goto end; + } + audiogen(&rnd, (void **)in_data, in_fmt, in_channels, in_rate, in_rate * 6); + + for (j = 0; j < num_formats; j++) { + out_fmt = formats[j]; + for (l = 0; l < num_layouts; l++) { + out_ch_layout = layouts[l]; + out_channels = av_get_channel_layout_nb_channels(out_ch_layout); + for (n = 0; n < num_rates; n++) { + out_rate = rates[n]; + + av_log(NULL, AV_LOG_INFO, "%s to %s, %d to %d channels, %d Hz to %d Hz\n", + av_get_sample_fmt_name(in_fmt), av_get_sample_fmt_name(out_fmt), + in_channels, out_channels, in_rate, out_rate); + + ret = av_samples_fill_arrays(out_data, &out_linesize, + out_buf, out_channels, + out_rate * 6, out_fmt, 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "failed out_data fill arrays\n"); + goto end; + } + + av_opt_set_int(s, "in_channel_layout", in_ch_layout, 0); + av_opt_set_int(s, "in_sample_fmt", in_fmt, 0); + av_opt_set_int(s, "in_sample_rate", in_rate, 0); + av_opt_set_int(s, "out_channel_layout", out_ch_layout, 0); + av_opt_set_int(s, "out_sample_fmt", out_fmt, 0); + av_opt_set_int(s, "out_sample_rate", out_rate, 0); + + av_opt_set_int(s, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + + ret = avresample_open(s); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Error opening context\n"); + goto end; + } + + ret = avresample_convert(s, (void **)out_data, out_linesize, out_rate * 6, + (void **) in_data, in_linesize, in_rate * 6); + if (ret < 0) { + char errbuf[256]; + av_strerror(ret, errbuf, sizeof(errbuf)); + av_log(NULL, AV_LOG_ERROR, "%s\n", errbuf); + goto end; + } + av_log(NULL, AV_LOG_INFO, "Converted %d samples to %d samples\n", + in_rate * 6, ret); + if (avresample_get_delay(s) > 0) + av_log(NULL, AV_LOG_INFO, "%d delay samples not converted\n", + avresample_get_delay(s)); + if (avresample_available(s) > 0) + av_log(NULL, AV_LOG_INFO, "%d samples available for output\n", + avresample_available(s)); + av_log(NULL, AV_LOG_INFO, "\n"); + + avresample_close(s); + } + } + } + } + } + } + + ret = 0; + +end: + av_freep(&in_buf); + av_freep(&out_buf); + avresample_free(&s); + return ret; +} diff --git a/libavresample/avresample.h b/libavresample/avresample.h new file mode 100644 index 0000000000..41688ed555 --- /dev/null +++ b/libavresample/avresample.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#ifndef AVRESAMPLE_AVRESAMPLE_H +#define AVRESAMPLE_AVRESAMPLE_H + +/** + * @file + * external API header + */ + +#include "libavutil/audioconvert.h" +#include "libavutil/avutil.h" +#include "libavutil/dict.h" +#include "libavutil/log.h" + +#include "libavresample/version.h" + +#define AVRESAMPLE_MAX_CHANNELS 32 + +typedef struct AVAudioResampleContext AVAudioResampleContext; + +/** Mixing Coefficient Types */ +enum AVMixCoeffType { + AV_MIX_COEFF_TYPE_Q6, /** 16-bit 10.6 fixed-point */ + AV_MIX_COEFF_TYPE_Q15, /** 32-bit 17.15 fixed-point */ + AV_MIX_COEFF_TYPE_FLT, /** floating-point */ + AV_MIX_COEFF_TYPE_NB, /** Number of coeff types. Not part of ABI */ +}; + +/** + * Return the LIBAVRESAMPLE_VERSION_INT constant. + */ +unsigned avresample_version(void); + +/** + * Return the libavresample build-time configuration. + * @return configure string + */ +const char *avresample_configuration(void); + +/** + * Return the libavresample license. + */ +const char *avresample_license(void); + +/** + * Get the AVClass for AVAudioResampleContext. + * + * Can be used in combination with AV_OPT_SEARCH_FAKE_OBJ for examining options + * without allocating a context. + * + * @see av_opt_find(). + * + * @return AVClass for AVAudioResampleContext + */ +const AVClass *avresample_get_class(void); + +/** + * Allocate AVAudioResampleContext and set options. + * + * @return allocated audio resample context, or NULL on failure + */ +AVAudioResampleContext *avresample_alloc_context(void); + +/** + * Initialize AVAudioResampleContext. + * + * @param avr audio resample context + * @return 0 on success, negative AVERROR code on failure + */ +int avresample_open(AVAudioResampleContext *avr); + +/** + * Close AVAudioResampleContext. + * + * This closes the context, but it does not change the parameters. The context + * can be reopened with avresample_open(). It does, however, clear the output + * FIFO and any remaining leftover samples in the resampling delay buffer. If + * there was a custom matrix being used, that is also cleared. + * + * @see avresample_convert() + * @see avresample_set_matrix() + * + * @param avr audio resample context + */ +void avresample_close(AVAudioResampleContext *avr); + +/** + * Free AVAudioResampleContext and associated AVOption values. + * + * This also calls avresample_close() before freeing. + * + * @param avr audio resample context + */ +void avresample_free(AVAudioResampleContext **avr); + +/** + * Generate a channel mixing matrix. + * + * This function is the one used internally by libavresample for building the + * default mixing matrix. It is made public just as a utility function for + * building custom matrices. + * + * @param in_layout input channel layout + * @param out_layout output channel layout + * @param center_mix_level mix level for the center channel + * @param surround_mix_level mix level for the surround channel(s) + * @param lfe_mix_level mix level for the low-frequency effects channel + * @param normalize if 1, coefficients will be normalized to prevent + * overflow. if 0, coefficients will not be + * normalized. + * @param[out] matrix mixing coefficients; matrix[i + stride * o] is + * the weight of input channel i in output channel o. + * @param stride distance between adjacent input channels in the + * matrix array + * @return 0 on success, negative AVERROR code on failure + */ +int avresample_build_matrix(uint64_t in_layout, uint64_t out_layout, + double center_mix_level, double surround_mix_level, + double lfe_mix_level, int normalize, double *matrix, + int stride); + +/** + * Get the current channel mixing matrix. + * + * @param avr audio resample context + * @param matrix mixing coefficients; matrix[i + stride * o] is the weight of + * input channel i in output channel o. + * @param stride distance between adjacent input channels in the matrix array + * @return 0 on success, negative AVERROR code on failure + */ +int avresample_get_matrix(AVAudioResampleContext *avr, double *matrix, + int stride); + +/** + * Set channel mixing matrix. + * + * Allows for setting a custom mixing matrix, overriding the default matrix + * generated internally during avresample_open(). This function can be called + * anytime on an allocated context, either before or after calling + * avresample_open(). avresample_convert() always uses the current matrix. + * Calling avresample_close() on the context will clear the current matrix. + * + * @see avresample_close() + * + * @param avr audio resample context + * @param matrix mixing coefficients; matrix[i + stride * o] is the weight of + * input channel i in output channel o. + * @param stride distance between adjacent input channels in the matrix array + * @return 0 on success, negative AVERROR code on failure + */ +int avresample_set_matrix(AVAudioResampleContext *avr, const double *matrix, + int stride); + +/** + * Set compensation for resampling. + * + * This can be called anytime after avresample_open(). If resampling was not + * being done previously, the AVAudioResampleContext is closed and reopened + * with resampling enabled. In this case, any samples remaining in the output + * FIFO and the current channel mixing matrix will be restored after reopening + * the context. + * + * @param avr audio resample context + * @param sample_delta compensation delta, in samples + * @param compensation_distance compensation distance, in samples + * @return 0 on success, negative AVERROR code on failure + */ +int avresample_set_compensation(AVAudioResampleContext *avr, int sample_delta, + int compensation_distance); + +/** + * Convert input samples and write them to the output FIFO. + * + * The output data can be NULL or have fewer allocated samples than required. + * In this case, any remaining samples not written to the output will be added + * to an internal FIFO buffer, to be returned at the next call to this function + * or to avresample_read(). + * + * If converting sample rate, there may be data remaining in the internal + * resampling delay buffer. avresample_get_delay() tells the number of remaining + * samples. To get this data as output, call avresample_convert() with NULL + * input. + * + * At the end of the conversion process, there may be data remaining in the + * internal FIFO buffer. avresample_available() tells the number of remaining + * samples. To get this data as output, either call avresample_convert() with + * NULL input or call avresample_read(). + * + * @see avresample_available() + * @see avresample_read() + * @see avresample_get_delay() + * + * @param avr audio resample context + * @param output output data pointers + * @param out_plane_size output plane size, in bytes. + * This can be 0 if unknown, but that will lead to + * optimized functions not being used directly on the + * output, which could slow down some conversions. + * @param out_samples maximum number of samples that the output buffer can hold + * @param input input data pointers + * @param in_plane_size input plane size, in bytes + * This can be 0 if unknown, but that will lead to + * optimized functions not being used directly on the + * input, which could slow down some conversions. + * @param in_samples number of input samples to convert + * @return number of samples written to the output buffer, + * not including converted samples added to the internal + * output FIFO + */ +int avresample_convert(AVAudioResampleContext *avr, void **output, + int out_plane_size, int out_samples, void **input, + int in_plane_size, int in_samples); + +/** + * Return the number of samples currently in the resampling delay buffer. + * + * When resampling, there may be a delay between the input and output. Any + * unconverted samples in each call are stored internally in a delay buffer. + * This function allows the user to determine the current number of samples in + * the delay buffer, which can be useful for synchronization. + * + * @see avresample_convert() + * + * @param avr audio resample context + * @return number of samples currently in the resampling delay buffer + */ +int avresample_get_delay(AVAudioResampleContext *avr); + +/** + * Return the number of available samples in the output FIFO. + * + * During conversion, if the user does not specify an output buffer or + * specifies an output buffer that is smaller than what is needed, remaining + * samples that are not written to the output are stored to an internal FIFO + * buffer. The samples in the FIFO can be read with avresample_read() or + * avresample_convert(). + * + * @see avresample_read() + * @see avresample_convert() + * + * @param avr audio resample context + * @return number of samples available for reading + */ +int avresample_available(AVAudioResampleContext *avr); + +/** + * Read samples from the output FIFO. + * + * During conversion, if the user does not specify an output buffer or + * specifies an output buffer that is smaller than what is needed, remaining + * samples that are not written to the output are stored to an internal FIFO + * buffer. This function can be used to read samples from that internal FIFO. + * + * @see avresample_available() + * @see avresample_convert() + * + * @param avr audio resample context + * @param output output data pointers + * @param nb_samples number of samples to read from the FIFO + * @return the number of samples written to output + */ +int avresample_read(AVAudioResampleContext *avr, void **output, int nb_samples); + +#endif /* AVRESAMPLE_AVRESAMPLE_H */ diff --git a/libavresample/internal.h b/libavresample/internal.h new file mode 100644 index 0000000000..49ea6a668e --- /dev/null +++ b/libavresample/internal.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#ifndef AVRESAMPLE_INTERNAL_H +#define AVRESAMPLE_INTERNAL_H + +#include "libavutil/audio_fifo.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavutil/samplefmt.h" +#include "avresample.h" +#include "audio_convert.h" +#include "audio_data.h" +#include "audio_mix.h" +#include "resample.h" + +struct AVAudioResampleContext { + const AVClass *av_class; /**< AVClass for logging and AVOptions */ + + uint64_t in_channel_layout; /**< input channel layout */ + enum AVSampleFormat in_sample_fmt; /**< input sample format */ + int in_sample_rate; /**< input sample rate */ + uint64_t out_channel_layout; /**< output channel layout */ + enum AVSampleFormat out_sample_fmt; /**< output sample format */ + int out_sample_rate; /**< output sample rate */ + enum AVSampleFormat internal_sample_fmt; /**< internal sample format */ + enum AVMixCoeffType mix_coeff_type; /**< mixing coefficient type */ + double center_mix_level; /**< center mix level */ + double surround_mix_level; /**< surround mix level */ + double lfe_mix_level; /**< lfe mix level */ + int force_resampling; /**< force resampling */ + int filter_size; /**< length of each FIR filter in the resampling filterbank relative to the cutoff frequency */ + int phase_shift; /**< log2 of the number of entries in the resampling polyphase filterbank */ + int linear_interp; /**< if 1 then the resampling FIR filter will be linearly interpolated */ + double cutoff; /**< resampling cutoff frequency. 1.0 corresponds to half the output sample rate */ + + int in_channels; /**< number of input channels */ + int out_channels; /**< number of output channels */ + int resample_channels; /**< number of channels used for resampling */ + int downmix_needed; /**< downmixing is needed */ + int upmix_needed; /**< upmixing is needed */ + int mixing_needed; /**< either upmixing or downmixing is needed */ + int resample_needed; /**< resampling is needed */ + int in_convert_needed; /**< input sample format conversion is needed */ + int out_convert_needed; /**< output sample format conversion is needed */ + + AudioData *in_buffer; /**< buffer for converted input */ + AudioData *resample_out_buffer; /**< buffer for output from resampler */ + AudioData *out_buffer; /**< buffer for converted output */ + AVAudioFifo *out_fifo; /**< FIFO for output samples */ + + AudioConvert *ac_in; /**< input sample format conversion context */ + AudioConvert *ac_out; /**< output sample format conversion context */ + ResampleContext *resample; /**< resampling context */ + AudioMix *am; /**< channel mixing context */ +}; + +#endif /* AVRESAMPLE_INTERNAL_H */ diff --git a/libavresample/libavresample.v b/libavresample/libavresample.v new file mode 100644 index 0000000000..b8c7c7d2e5 --- /dev/null +++ b/libavresample/libavresample.v @@ -0,0 +1,4 @@ +LIBAVRESAMPLE_$MAJOR { + global: av*; + local: *; +}; diff --git a/libavresample/options.c b/libavresample/options.c new file mode 100644 index 0000000000..0be1a26117 --- /dev/null +++ b/libavresample/options.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "avresample.h" +#include "internal.h" +#include "audio_mix.h" + +/** + * @file + * Options definition for AVAudioResampleContext. + */ + +#define OFFSET(x) offsetof(AVAudioResampleContext, x) +#define PARAM AV_OPT_FLAG_AUDIO_PARAM + +static const AVOption options[] = { + { "in_channel_layout", "Input Channel Layout", OFFSET(in_channel_layout), AV_OPT_TYPE_INT64, { 0 }, INT64_MIN, INT64_MAX, PARAM }, + { "in_sample_fmt", "Input Sample Format", OFFSET(in_sample_fmt), AV_OPT_TYPE_INT, { AV_SAMPLE_FMT_S16 }, AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_NB-1, PARAM }, + { "in_sample_rate", "Input Sample Rate", OFFSET(in_sample_rate), AV_OPT_TYPE_INT, { 48000 }, 1, INT_MAX, PARAM }, + { "out_channel_layout", "Output Channel Layout", OFFSET(out_channel_layout), AV_OPT_TYPE_INT64, { 0 }, INT64_MIN, INT64_MAX, PARAM }, + { "out_sample_fmt", "Output Sample Format", OFFSET(out_sample_fmt), AV_OPT_TYPE_INT, { AV_SAMPLE_FMT_S16 }, AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_NB-1, PARAM }, + { "out_sample_rate", "Output Sample Rate", OFFSET(out_sample_rate), AV_OPT_TYPE_INT, { 48000 }, 1, INT_MAX, PARAM }, + { "internal_sample_fmt", "Internal Sample Format", OFFSET(internal_sample_fmt), AV_OPT_TYPE_INT, { AV_SAMPLE_FMT_FLTP }, AV_SAMPLE_FMT_NONE, AV_SAMPLE_FMT_NB-1, PARAM }, + { "mix_coeff_type", "Mixing Coefficient Type", OFFSET(mix_coeff_type), AV_OPT_TYPE_INT, { AV_MIX_COEFF_TYPE_FLT }, AV_MIX_COEFF_TYPE_Q6, AV_MIX_COEFF_TYPE_NB-1, PARAM, "mix_coeff_type" }, + { "q6", "16-bit 10.6 Fixed-Point", 0, AV_OPT_TYPE_CONST, { AV_MIX_COEFF_TYPE_Q6 }, INT_MIN, INT_MAX, PARAM, "mix_coeff_type" }, + { "q15", "32-bit 17.15 Fixed-Point", 0, AV_OPT_TYPE_CONST, { AV_MIX_COEFF_TYPE_Q15 }, INT_MIN, INT_MAX, PARAM, "mix_coeff_type" }, + { "flt", "Floating-Point", 0, AV_OPT_TYPE_CONST, { AV_MIX_COEFF_TYPE_FLT }, INT_MIN, INT_MAX, PARAM, "mix_coeff_type" }, + { "center_mix_level", "Center Mix Level", OFFSET(center_mix_level), AV_OPT_TYPE_DOUBLE, { M_SQRT1_2 }, -32.0, 32.0, PARAM }, + { "surround_mix_level", "Surround Mix Level", OFFSET(surround_mix_level), AV_OPT_TYPE_DOUBLE, { M_SQRT1_2 }, -32.0, 32.0, PARAM }, + { "lfe_mix_level", "LFE Mix Level", OFFSET(lfe_mix_level), AV_OPT_TYPE_DOUBLE, { 0.0 }, -32.0, 32.0, PARAM }, + { "force_resampling", "Force Resampling", OFFSET(force_resampling), AV_OPT_TYPE_INT, { 0 }, 0, 1, PARAM }, + { "filter_size", "Resampling Filter Size", OFFSET(filter_size), AV_OPT_TYPE_INT, { 16 }, 0, 32, /* ??? */ PARAM }, + { "phase_shift", "Resampling Phase Shift", OFFSET(phase_shift), AV_OPT_TYPE_INT, { 10 }, 0, 30, /* ??? */ PARAM }, + { "linear_interp", "Use Linear Interpolation", OFFSET(linear_interp), AV_OPT_TYPE_INT, { 0 }, 0, 1, PARAM }, + { "cutoff", "Cutoff Frequency Ratio", OFFSET(cutoff), AV_OPT_TYPE_DOUBLE, { 0.8 }, 0.0, 1.0, PARAM }, + { NULL }, +}; + +static const AVClass av_resample_context_class = { + .class_name = "AVAudioResampleContext", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVAudioResampleContext *avresample_alloc_context(void) +{ + AVAudioResampleContext *avr; + + avr = av_mallocz(sizeof(*avr)); + if (!avr) + return NULL; + + avr->av_class = &av_resample_context_class; + av_opt_set_defaults(avr); + + avr->am = av_mallocz(sizeof(*avr->am)); + if (!avr->am) { + av_free(avr); + return NULL; + } + avr->am->avr = avr; + + return avr; +} + +const AVClass *avresample_get_class(void) +{ + return &av_resample_context_class; +} diff --git a/libavresample/resample.c b/libavresample/resample.c new file mode 100644 index 0000000000..5529fafe8d --- /dev/null +++ b/libavresample/resample.c @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include "libavutil/libm.h" +#include "libavutil/log.h" +#include "internal.h" +#include "audio_data.h" + +#ifdef CONFIG_RESAMPLE_FLT +/* float template */ +#define FILTER_SHIFT 0 +#define FELEM float +#define FELEM2 float +#define FELEML float +#define WINDOW_TYPE 24 +#elifdef CONFIG_RESAMPLE_S32 +/* s32 template */ +#define FILTER_SHIFT 30 +#define FELEM int32_t +#define FELEM2 int64_t +#define FELEML int64_t +#define FELEM_MAX INT32_MAX +#define FELEM_MIN INT32_MIN +#define WINDOW_TYPE 12 +#else +/* s16 template */ +#define FILTER_SHIFT 15 +#define FELEM int16_t +#define FELEM2 int32_t +#define FELEML int64_t +#define FELEM_MAX INT16_MAX +#define FELEM_MIN INT16_MIN +#define WINDOW_TYPE 9 +#endif + +struct ResampleContext { + AVAudioResampleContext *avr; + AudioData *buffer; + FELEM *filter_bank; + int filter_length; + int ideal_dst_incr; + int dst_incr; + int index; + int frac; + int src_incr; + int compensation_distance; + int phase_shift; + int phase_mask; + int linear; + double factor; +}; + +/** + * 0th order modified bessel function of the first kind. + */ +static double bessel(double x) +{ + double v = 1; + double lastv = 0; + double t = 1; + int i; + + x = x * x / 4; + for (i = 1; v != lastv; i++) { + lastv = v; + t *= x / (i * i); + v += t; + } + return v; +} + +/** + * Build a polyphase filterbank. + * + * @param[out] filter filter coefficients + * @param factor resampling factor + * @param tap_count tap count + * @param phase_count phase count + * @param scale wanted sum of coefficients for each filter + * @param type 0->cubic + * 1->blackman nuttall windowed sinc + * 2..16->kaiser windowed sinc beta=2..16 + * @return 0 on success, negative AVERROR code on failure + */ +static int build_filter(FELEM *filter, double factor, int tap_count, + int phase_count, int scale, int type) +{ + int ph, i; + double x, y, w; + double *tab; + const int center = (tap_count - 1) / 2; + + tab = av_malloc(tap_count * sizeof(*tab)); + if (!tab) + return AVERROR(ENOMEM); + + /* if upsampling, only need to interpolate, no filter */ + if (factor > 1.0) + factor = 1.0; + + for (ph = 0; ph < phase_count; ph++) { + double norm = 0; + for (i = 0; i < tap_count; i++) { + x = M_PI * ((double)(i - center) - (double)ph / phase_count) * factor; + if (x == 0) y = 1.0; + else y = sin(x) / x; + switch (type) { + case 0: { + const float d = -0.5; //first order derivative = -0.5 + x = fabs(((double)(i - center) - (double)ph / phase_count) * factor); + if (x < 1.0) y = 1 - 3 * x*x + 2 * x*x*x + d * ( -x*x + x*x*x); + else y = d * (-4 + 8 * x - 5 * x*x + x*x*x); + break; + } + case 1: + w = 2.0 * x / (factor * tap_count) + M_PI; + y *= 0.3635819 - 0.4891775 * cos( w) + + 0.1365995 * cos(2 * w) - + 0.0106411 * cos(3 * w); + break; + default: + w = 2.0 * x / (factor * tap_count * M_PI); + y *= bessel(type * sqrt(FFMAX(1 - w * w, 0))); + break; + } + + tab[i] = y; + norm += y; + } + + /* normalize so that an uniform color remains the same */ + for (i = 0; i < tap_count; i++) { +#ifdef CONFIG_RESAMPLE_FLT + filter[ph * tap_count + i] = tab[i] / norm; +#else + filter[ph * tap_count + i] = av_clip(lrintf(tab[i] * scale / norm), + FELEM_MIN, FELEM_MAX); +#endif + } + } + + av_free(tab); + return 0; +} + +ResampleContext *ff_audio_resample_init(AVAudioResampleContext *avr) +{ + ResampleContext *c; + int out_rate = avr->out_sample_rate; + int in_rate = avr->in_sample_rate; + double factor = FFMIN(out_rate * avr->cutoff / in_rate, 1.0); + int phase_count = 1 << avr->phase_shift; + + /* TODO: add support for s32 and float internal formats */ + if (avr->internal_sample_fmt != AV_SAMPLE_FMT_S16P) { + av_log(avr, AV_LOG_ERROR, "Unsupported internal format for " + "resampling: %s\n", + av_get_sample_fmt_name(avr->internal_sample_fmt)); + return NULL; + } + c = av_mallocz(sizeof(*c)); + if (!c) + return NULL; + + c->avr = avr; + c->phase_shift = avr->phase_shift; + c->phase_mask = phase_count - 1; + c->linear = avr->linear_interp; + c->factor = factor; + c->filter_length = FFMAX((int)ceil(avr->filter_size / factor), 1); + + c->filter_bank = av_mallocz(c->filter_length * (phase_count + 1) * sizeof(FELEM)); + if (!c->filter_bank) + goto error; + + if (build_filter(c->filter_bank, factor, c->filter_length, phase_count, + 1 << FILTER_SHIFT, WINDOW_TYPE) < 0) + goto error; + + memcpy(&c->filter_bank[c->filter_length * phase_count + 1], + c->filter_bank, (c->filter_length - 1) * sizeof(FELEM)); + c->filter_bank[c->filter_length * phase_count] = c->filter_bank[c->filter_length - 1]; + + c->compensation_distance = 0; + if (!av_reduce(&c->src_incr, &c->dst_incr, out_rate, + in_rate * (int64_t)phase_count, INT32_MAX / 2)) + goto error; + c->ideal_dst_incr = c->dst_incr; + + c->index = -phase_count * ((c->filter_length - 1) / 2); + c->frac = 0; + + /* allocate internal buffer */ + c->buffer = ff_audio_data_alloc(avr->resample_channels, 0, + avr->internal_sample_fmt, + "resample buffer"); + if (!c->buffer) + goto error; + + av_log(avr, AV_LOG_DEBUG, "resample: %s from %d Hz to %d Hz\n", + av_get_sample_fmt_name(avr->internal_sample_fmt), + avr->in_sample_rate, avr->out_sample_rate); + + return c; + +error: + ff_audio_data_free(&c->buffer); + av_free(c->filter_bank); + av_free(c); + return NULL; +} + +void ff_audio_resample_free(ResampleContext **c) +{ + if (!*c) + return; + ff_audio_data_free(&(*c)->buffer); + av_free((*c)->filter_bank); + av_freep(c); +} + +int avresample_set_compensation(AVAudioResampleContext *avr, int sample_delta, + int compensation_distance) +{ + ResampleContext *c; + AudioData *fifo_buf = NULL; + int ret = 0; + + if (compensation_distance < 0) + return AVERROR(EINVAL); + if (!compensation_distance && sample_delta) + return AVERROR(EINVAL); + + /* if resampling was not enabled previously, re-initialize the + AVAudioResampleContext and force resampling */ + if (!avr->resample_needed) { + int fifo_samples; + double matrix[AVRESAMPLE_MAX_CHANNELS * AVRESAMPLE_MAX_CHANNELS] = { 0 }; + + /* buffer any remaining samples in the output FIFO before closing */ + fifo_samples = av_audio_fifo_size(avr->out_fifo); + if (fifo_samples > 0) { + fifo_buf = ff_audio_data_alloc(avr->out_channels, fifo_samples, + avr->out_sample_fmt, NULL); + if (!fifo_buf) + return AVERROR(EINVAL); + ret = ff_audio_data_read_from_fifo(avr->out_fifo, fifo_buf, + fifo_samples); + if (ret < 0) + goto reinit_fail; + } + /* save the channel mixing matrix */ + ret = avresample_get_matrix(avr, matrix, AVRESAMPLE_MAX_CHANNELS); + if (ret < 0) + goto reinit_fail; + + /* close the AVAudioResampleContext */ + avresample_close(avr); + + avr->force_resampling = 1; + + /* restore the channel mixing matrix */ + ret = avresample_set_matrix(avr, matrix, AVRESAMPLE_MAX_CHANNELS); + if (ret < 0) + goto reinit_fail; + + /* re-open the AVAudioResampleContext */ + ret = avresample_open(avr); + if (ret < 0) + goto reinit_fail; + + /* restore buffered samples to the output FIFO */ + if (fifo_samples > 0) { + ret = ff_audio_data_add_to_fifo(avr->out_fifo, fifo_buf, 0, + fifo_samples); + if (ret < 0) + goto reinit_fail; + ff_audio_data_free(&fifo_buf); + } + } + c = avr->resample; + c->compensation_distance = compensation_distance; + if (compensation_distance) { + c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr * + (int64_t)sample_delta / compensation_distance; + } else { + c->dst_incr = c->ideal_dst_incr; + } + return 0; + +reinit_fail: + ff_audio_data_free(&fifo_buf); + return ret; +} + +static int resample(ResampleContext *c, int16_t *dst, const int16_t *src, + int *consumed, int src_size, int dst_size, int update_ctx) +{ + int dst_index, i; + int index = c->index; + int frac = c->frac; + int dst_incr_frac = c->dst_incr % c->src_incr; + int dst_incr = c->dst_incr / c->src_incr; + int compensation_distance = c->compensation_distance; + + if (!dst != !src) + return AVERROR(EINVAL); + + if (compensation_distance == 0 && c->filter_length == 1 && + c->phase_shift == 0) { + int64_t index2 = ((int64_t)index) << 32; + int64_t incr = (1LL << 32) * c->dst_incr / c->src_incr; + dst_size = FFMIN(dst_size, + (src_size-1-index) * (int64_t)c->src_incr / + c->dst_incr); + + if (dst) { + for(dst_index = 0; dst_index < dst_size; dst_index++) { + dst[dst_index] = src[index2 >> 32]; + index2 += incr; + } + } else { + dst_index = dst_size; + } + index += dst_index * dst_incr; + index += (frac + dst_index * (int64_t)dst_incr_frac) / c->src_incr; + frac = (frac + dst_index * (int64_t)dst_incr_frac) % c->src_incr; + } else { + for (dst_index = 0; dst_index < dst_size; dst_index++) { + FELEM *filter = c->filter_bank + + c->filter_length * (index & c->phase_mask); + int sample_index = index >> c->phase_shift; + + if (!dst && (sample_index + c->filter_length > src_size || + -sample_index >= src_size)) + break; + + if (dst) { + FELEM2 val = 0; + + if (sample_index < 0) { + for (i = 0; i < c->filter_length; i++) + val += src[FFABS(sample_index + i) % src_size] * + (FELEM2)filter[i]; + } else if (sample_index + c->filter_length > src_size) { + break; + } else if (c->linear) { + FELEM2 v2 = 0; + for (i = 0; i < c->filter_length; i++) { + val += src[abs(sample_index + i)] * (FELEM2)filter[i]; + v2 += src[abs(sample_index + i)] * (FELEM2)filter[i + c->filter_length]; + } + val += (v2 - val) * (FELEML)frac / c->src_incr; + } else { + for (i = 0; i < c->filter_length; i++) + val += src[sample_index + i] * (FELEM2)filter[i]; + } + +#ifdef CONFIG_RESAMPLE_FLT + dst[dst_index] = av_clip_int16(lrintf(val)); +#else + val = (val + (1<<(FILTER_SHIFT-1)))>>FILTER_SHIFT; + dst[dst_index] = av_clip_int16(val); +#endif + } + + frac += dst_incr_frac; + index += dst_incr; + if (frac >= c->src_incr) { + frac -= c->src_incr; + index++; + } + if (dst_index + 1 == compensation_distance) { + compensation_distance = 0; + dst_incr_frac = c->ideal_dst_incr % c->src_incr; + dst_incr = c->ideal_dst_incr / c->src_incr; + } + } + } + if (consumed) + *consumed = FFMAX(index, 0) >> c->phase_shift; + + if (update_ctx) { + if (index >= 0) + index &= c->phase_mask; + + if (compensation_distance) { + compensation_distance -= dst_index; + if (compensation_distance <= 0) + return AVERROR_BUG; + } + c->frac = frac; + c->index = index; + c->dst_incr = dst_incr_frac + c->src_incr*dst_incr; + c->compensation_distance = compensation_distance; + } + + return dst_index; +} + +int ff_audio_resample(ResampleContext *c, AudioData *dst, AudioData *src, + int *consumed) +{ + int ch, in_samples, in_leftover, out_samples = 0; + int ret = AVERROR(EINVAL); + + in_samples = src ? src->nb_samples : 0; + in_leftover = c->buffer->nb_samples; + + /* add input samples to the internal buffer */ + if (src) { + ret = ff_audio_data_combine(c->buffer, in_leftover, src, 0, in_samples); + if (ret < 0) + return ret; + } else if (!in_leftover) { + /* no remaining samples to flush */ + return 0; + } else { + /* TODO: pad buffer to flush completely */ + } + + /* calculate output size and reallocate output buffer if needed */ + /* TODO: try to calculate this without the dummy resample() run */ + if (!dst->read_only && dst->allow_realloc) { + out_samples = resample(c, NULL, NULL, NULL, c->buffer->nb_samples, + INT_MAX, 0); + ret = ff_audio_data_realloc(dst, out_samples); + if (ret < 0) { + av_log(c->avr, AV_LOG_ERROR, "error reallocating output\n"); + return ret; + } + } + + /* resample each channel plane */ + for (ch = 0; ch < c->buffer->channels; ch++) { + out_samples = resample(c, (int16_t *)dst->data[ch], + (const int16_t *)c->buffer->data[ch], consumed, + c->buffer->nb_samples, dst->allocated_samples, + ch + 1 == c->buffer->channels); + } + if (out_samples < 0) { + av_log(c->avr, AV_LOG_ERROR, "error during resampling\n"); + return out_samples; + } + + /* drain consumed samples from the internal buffer */ + ff_audio_data_drain(c->buffer, *consumed); + + av_dlog(c->avr, "resampled %d in + %d leftover to %d out + %d leftover\n", + in_samples, in_leftover, out_samples, c->buffer->nb_samples); + + dst->nb_samples = out_samples; + return 0; +} + +int avresample_get_delay(AVAudioResampleContext *avr) +{ + if (!avr->resample_needed || !avr->resample) + return 0; + + return avr->resample->buffer->nb_samples; +} diff --git a/libavresample/resample.h b/libavresample/resample.h new file mode 100644 index 0000000000..b42fdbbaac --- /dev/null +++ b/libavresample/resample.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * + * 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 + */ + +#ifndef AVRESAMPLE_RESAMPLE_H +#define AVRESAMPLE_RESAMPLE_H + +#include "avresample.h" +#include "audio_data.h" + +typedef struct ResampleContext ResampleContext; + +/** + * Allocate and initialize a ResampleContext. + * + * The parameters in the AVAudioResampleContext are used to initialize the + * ResampleContext. + * + * @param avr AVAudioResampleContext + * @return newly-allocated ResampleContext + */ +ResampleContext *ff_audio_resample_init(AVAudioResampleContext *avr); + +/** + * Free a ResampleContext. + * + * @param c ResampleContext + */ +void ff_audio_resample_free(ResampleContext **c); + +/** + * Resample audio data. + * + * Changes the sample rate. + * + * @par + * All samples in the source data may not be consumed depending on the + * resampling parameters and the size of the output buffer. The unconsumed + * samples are automatically added to the start of the source in the next call. + * If the destination data can be reallocated, that may be done in this function + * in order to fit all available output. If it cannot be reallocated, fewer + * input samples will be consumed in order to have the output fit in the + * destination data buffers. + * + * @param c ResampleContext + * @param dst destination audio data + * @param src source audio data + * @param consumed number of samples consumed from the source + * @return number of samples written to the destination + */ +int ff_audio_resample(ResampleContext *c, AudioData *dst, AudioData *src, + int *consumed); + +#endif /* AVRESAMPLE_RESAMPLE_H */ diff --git a/libavresample/utils.c b/libavresample/utils.c new file mode 100644 index 0000000000..f54dcc6ae6 --- /dev/null +++ b/libavresample/utils.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include "libavutil/dict.h" +#include "libavutil/error.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" + +#include "avresample.h" +#include "audio_data.h" +#include "internal.h" + +int avresample_open(AVAudioResampleContext *avr) +{ + int ret; + + /* set channel mixing parameters */ + avr->in_channels = av_get_channel_layout_nb_channels(avr->in_channel_layout); + if (avr->in_channels <= 0 || avr->in_channels > AVRESAMPLE_MAX_CHANNELS) { + av_log(avr, AV_LOG_ERROR, "Invalid input channel layout: %"PRIu64"\n", + avr->in_channel_layout); + return AVERROR(EINVAL); + } + avr->out_channels = av_get_channel_layout_nb_channels(avr->out_channel_layout); + if (avr->out_channels <= 0 || avr->out_channels > AVRESAMPLE_MAX_CHANNELS) { + av_log(avr, AV_LOG_ERROR, "Invalid output channel layout: %"PRIu64"\n", + avr->out_channel_layout); + return AVERROR(EINVAL); + } + avr->resample_channels = FFMIN(avr->in_channels, avr->out_channels); + avr->downmix_needed = avr->in_channels > avr->out_channels; + avr->upmix_needed = avr->out_channels > avr->in_channels || + avr->am->matrix || + (avr->out_channels == avr->in_channels && + avr->in_channel_layout != avr->out_channel_layout); + avr->mixing_needed = avr->downmix_needed || avr->upmix_needed; + + /* set resampling parameters */ + avr->resample_needed = avr->in_sample_rate != avr->out_sample_rate || + avr->force_resampling; + + /* set sample format conversion parameters */ + /* override user-requested internal format to avoid unexpected failures + TODO: support more internal formats */ + if (avr->resample_needed && avr->internal_sample_fmt != AV_SAMPLE_FMT_S16P) { + av_log(avr, AV_LOG_WARNING, "Using s16p as internal sample format\n"); + avr->internal_sample_fmt = AV_SAMPLE_FMT_S16P; + } else if (avr->mixing_needed && + avr->internal_sample_fmt != AV_SAMPLE_FMT_S16P && + avr->internal_sample_fmt != AV_SAMPLE_FMT_FLTP) { + av_log(avr, AV_LOG_WARNING, "Using fltp as internal sample format\n"); + avr->internal_sample_fmt = AV_SAMPLE_FMT_FLTP; + } + if (avr->in_channels == 1) + avr->in_sample_fmt = av_get_planar_sample_fmt(avr->in_sample_fmt); + if (avr->out_channels == 1) + avr->out_sample_fmt = av_get_planar_sample_fmt(avr->out_sample_fmt); + avr->in_convert_needed = (avr->resample_needed || avr->mixing_needed) && + avr->in_sample_fmt != avr->internal_sample_fmt; + if (avr->resample_needed || avr->mixing_needed) + avr->out_convert_needed = avr->internal_sample_fmt != avr->out_sample_fmt; + else + avr->out_convert_needed = avr->in_sample_fmt != avr->out_sample_fmt; + + /* allocate buffers */ + if (avr->mixing_needed || avr->in_convert_needed) { + avr->in_buffer = ff_audio_data_alloc(FFMAX(avr->in_channels, avr->out_channels), + 0, avr->internal_sample_fmt, + "in_buffer"); + if (!avr->in_buffer) { + ret = AVERROR(EINVAL); + goto error; + } + } + if (avr->resample_needed) { + avr->resample_out_buffer = ff_audio_data_alloc(avr->out_channels, + 0, avr->internal_sample_fmt, + "resample_out_buffer"); + if (!avr->resample_out_buffer) { + ret = AVERROR(EINVAL); + goto error; + } + } + if (avr->out_convert_needed) { + avr->out_buffer = ff_audio_data_alloc(avr->out_channels, 0, + avr->out_sample_fmt, "out_buffer"); + if (!avr->out_buffer) { + ret = AVERROR(EINVAL); + goto error; + } + } + avr->out_fifo = av_audio_fifo_alloc(avr->out_sample_fmt, avr->out_channels, + 1024); + if (!avr->out_fifo) { + ret = AVERROR(ENOMEM); + goto error; + } + + /* setup contexts */ + if (avr->in_convert_needed) { + avr->ac_in = ff_audio_convert_alloc(avr, avr->internal_sample_fmt, + avr->in_sample_fmt, avr->in_channels); + if (!avr->ac_in) { + ret = AVERROR(ENOMEM); + goto error; + } + } + if (avr->out_convert_needed) { + enum AVSampleFormat src_fmt; + if (avr->in_convert_needed) + src_fmt = avr->internal_sample_fmt; + else + src_fmt = avr->in_sample_fmt; + avr->ac_out = ff_audio_convert_alloc(avr, avr->out_sample_fmt, src_fmt, + avr->out_channels); + if (!avr->ac_out) { + ret = AVERROR(ENOMEM); + goto error; + } + } + if (avr->resample_needed) { + avr->resample = ff_audio_resample_init(avr); + if (!avr->resample) { + ret = AVERROR(ENOMEM); + goto error; + } + } + if (avr->mixing_needed) { + ret = ff_audio_mix_init(avr); + if (ret < 0) + goto error; + } + + return 0; + +error: + avresample_close(avr); + return ret; +} + +void avresample_close(AVAudioResampleContext *avr) +{ + ff_audio_data_free(&avr->in_buffer); + ff_audio_data_free(&avr->resample_out_buffer); + ff_audio_data_free(&avr->out_buffer); + av_audio_fifo_free(avr->out_fifo); + avr->out_fifo = NULL; + av_freep(&avr->ac_in); + av_freep(&avr->ac_out); + ff_audio_resample_free(&avr->resample); + ff_audio_mix_close(avr->am); + return; +} + +void avresample_free(AVAudioResampleContext **avr) +{ + if (!*avr) + return; + avresample_close(*avr); + av_freep(&(*avr)->am); + av_opt_free(*avr); + av_freep(avr); +} + +static int handle_buffered_output(AVAudioResampleContext *avr, + AudioData *output, AudioData *converted) +{ + int ret; + + if (!output || av_audio_fifo_size(avr->out_fifo) > 0 || + (converted && output->allocated_samples < converted->nb_samples)) { + if (converted) { + /* if there are any samples in the output FIFO or if the + user-supplied output buffer is not large enough for all samples, + we add to the output FIFO */ + av_dlog(avr, "[FIFO] add %s to out_fifo\n", converted->name); + ret = ff_audio_data_add_to_fifo(avr->out_fifo, converted, 0, + converted->nb_samples); + if (ret < 0) + return ret; + } + + /* if the user specified an output buffer, read samples from the output + FIFO to the user output */ + if (output && output->allocated_samples > 0) { + av_dlog(avr, "[FIFO] read from out_fifo to output\n"); + av_dlog(avr, "[end conversion]\n"); + return ff_audio_data_read_from_fifo(avr->out_fifo, output, + output->allocated_samples); + } + } else if (converted) { + /* copy directly to output if it is large enough or there is not any + data in the output FIFO */ + av_dlog(avr, "[copy] %s to output\n", converted->name); + output->nb_samples = 0; + ret = ff_audio_data_copy(output, converted); + if (ret < 0) + return ret; + av_dlog(avr, "[end conversion]\n"); + return output->nb_samples; + } + av_dlog(avr, "[end conversion]\n"); + return 0; +} + +int avresample_convert(AVAudioResampleContext *avr, void **output, + int out_plane_size, int out_samples, void **input, + int in_plane_size, int in_samples) +{ + AudioData input_buffer; + AudioData output_buffer; + AudioData *current_buffer; + int ret; + + /* reset internal buffers */ + if (avr->in_buffer) { + avr->in_buffer->nb_samples = 0; + ff_audio_data_set_channels(avr->in_buffer, + avr->in_buffer->allocated_channels); + } + if (avr->resample_out_buffer) { + avr->resample_out_buffer->nb_samples = 0; + ff_audio_data_set_channels(avr->resample_out_buffer, + avr->resample_out_buffer->allocated_channels); + } + if (avr->out_buffer) { + avr->out_buffer->nb_samples = 0; + ff_audio_data_set_channels(avr->out_buffer, + avr->out_buffer->allocated_channels); + } + + av_dlog(avr, "[start conversion]\n"); + + /* initialize output_buffer with output data */ + if (output) { + ret = ff_audio_data_init(&output_buffer, output, out_plane_size, + avr->out_channels, out_samples, + avr->out_sample_fmt, 0, "output"); + if (ret < 0) + return ret; + output_buffer.nb_samples = 0; + } + + if (input) { + /* initialize input_buffer with input data */ + ret = ff_audio_data_init(&input_buffer, input, in_plane_size, + avr->in_channels, in_samples, + avr->in_sample_fmt, 1, "input"); + if (ret < 0) + return ret; + current_buffer = &input_buffer; + + if (avr->upmix_needed && !avr->in_convert_needed && !avr->resample_needed && + !avr->out_convert_needed && output && out_samples >= in_samples) { + /* in some rare cases we can copy input to output and upmix + directly in the output buffer */ + av_dlog(avr, "[copy] %s to output\n", current_buffer->name); + ret = ff_audio_data_copy(&output_buffer, current_buffer); + if (ret < 0) + return ret; + current_buffer = &output_buffer; + } else if (avr->mixing_needed || avr->in_convert_needed) { + /* if needed, copy or convert input to in_buffer, and downmix if + applicable */ + if (avr->in_convert_needed) { + ret = ff_audio_data_realloc(avr->in_buffer, + current_buffer->nb_samples); + if (ret < 0) + return ret; + av_dlog(avr, "[convert] %s to in_buffer\n", current_buffer->name); + ret = ff_audio_convert(avr->ac_in, avr->in_buffer, current_buffer, + current_buffer->nb_samples); + if (ret < 0) + return ret; + } else { + av_dlog(avr, "[copy] %s to in_buffer\n", current_buffer->name); + ret = ff_audio_data_copy(avr->in_buffer, current_buffer); + if (ret < 0) + return ret; + } + ff_audio_data_set_channels(avr->in_buffer, avr->in_channels); + if (avr->downmix_needed) { + av_dlog(avr, "[downmix] in_buffer\n"); + ret = ff_audio_mix(avr->am, avr->in_buffer); + if (ret < 0) + return ret; + } + current_buffer = avr->in_buffer; + } + } else { + /* flush resampling buffer and/or output FIFO if input is NULL */ + if (!avr->resample_needed) + return handle_buffered_output(avr, output ? &output_buffer : NULL, + NULL); + current_buffer = NULL; + } + + if (avr->resample_needed) { + AudioData *resample_out; + int consumed = 0; + + if (!avr->out_convert_needed && output && out_samples > 0) + resample_out = &output_buffer; + else + resample_out = avr->resample_out_buffer; + av_dlog(avr, "[resample] %s to %s\n", current_buffer->name, + resample_out->name); + ret = ff_audio_resample(avr->resample, resample_out, + current_buffer, &consumed); + if (ret < 0) + return ret; + + /* if resampling did not produce any samples, just return 0 */ + if (resample_out->nb_samples == 0) { + av_dlog(avr, "[end conversion]\n"); + return 0; + } + + current_buffer = resample_out; + } + + if (avr->upmix_needed) { + av_dlog(avr, "[upmix] %s\n", current_buffer->name); + ret = ff_audio_mix(avr->am, current_buffer); + if (ret < 0) + return ret; + } + + /* if we resampled or upmixed directly to output, return here */ + if (current_buffer == &output_buffer) { + av_dlog(avr, "[end conversion]\n"); + return current_buffer->nb_samples; + } + + if (avr->out_convert_needed) { + if (output && out_samples >= current_buffer->nb_samples) { + /* convert directly to output */ + av_dlog(avr, "[convert] %s to output\n", current_buffer->name); + ret = ff_audio_convert(avr->ac_out, &output_buffer, current_buffer, + current_buffer->nb_samples); + if (ret < 0) + return ret; + + av_dlog(avr, "[end conversion]\n"); + return output_buffer.nb_samples; + } else { + ret = ff_audio_data_realloc(avr->out_buffer, + current_buffer->nb_samples); + if (ret < 0) + return ret; + av_dlog(avr, "[convert] %s to out_buffer\n", current_buffer->name); + ret = ff_audio_convert(avr->ac_out, avr->out_buffer, + current_buffer, current_buffer->nb_samples); + if (ret < 0) + return ret; + current_buffer = avr->out_buffer; + } + } + + return handle_buffered_output(avr, &output_buffer, current_buffer); +} + +int avresample_available(AVAudioResampleContext *avr) +{ + return av_audio_fifo_size(avr->out_fifo); +} + +int avresample_read(AVAudioResampleContext *avr, void **output, int nb_samples) +{ + return av_audio_fifo_read(avr->out_fifo, output, nb_samples); +} + +unsigned avresample_version(void) +{ + return LIBAVRESAMPLE_VERSION_INT; +} + +const char *avresample_license(void) +{ +#define LICENSE_PREFIX "libavresample license: " + return LICENSE_PREFIX LIBAV_LICENSE + sizeof(LICENSE_PREFIX) - 1; +} + +const char *avresample_configuration(void) +{ + return LIBAV_CONFIGURATION; +} diff --git a/libavresample/version.h b/libavresample/version.h new file mode 100644 index 0000000000..4113edc21d --- /dev/null +++ b/libavresample/version.h @@ -0,0 +1,41 @@ +/* + * 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 + */ + +#ifndef AVRESAMPLE_VERSION_H +#define AVRESAMPLE_VERSION_H + +#define LIBAVRESAMPLE_VERSION_MAJOR 0 +#define LIBAVRESAMPLE_VERSION_MINOR 0 +#define LIBAVRESAMPLE_VERSION_MICRO 0 + +#define LIBAVRESAMPLE_VERSION_INT AV_VERSION_INT(LIBAVRESAMPLE_VERSION_MAJOR, \ + LIBAVRESAMPLE_VERSION_MINOR, \ + LIBAVRESAMPLE_VERSION_MICRO) +#define LIBAVRESAMPLE_VERSION AV_VERSION(LIBAVRESAMPLE_VERSION_MAJOR, \ + LIBAVRESAMPLE_VERSION_MINOR, \ + LIBAVRESAMPLE_VERSION_MICRO) +#define LIBAVRESAMPLE_BUILD LIBAVRESAMPLE_VERSION_INT + +#define LIBAVRESAMPLE_IDENT "Lavr" AV_STRINGIFY(LIBAVRESAMPLE_VERSION) + +/** + * These FF_API_* defines are not part of public API. + * They may change, break or disappear at any time. + */ + +#endif /* AVRESAMPLE_VERSION_H */ diff --git a/libavresample/x86/Makefile b/libavresample/x86/Makefile new file mode 100644 index 0000000000..63697faee7 --- /dev/null +++ b/libavresample/x86/Makefile @@ -0,0 +1,5 @@ +OBJS += x86/audio_convert_init.o \ + x86/audio_mix_init.o + +YASM-OBJS += x86/audio_convert.o \ + x86/audio_mix.o diff --git a/libavresample/x86/audio_convert.asm b/libavresample/x86/audio_convert.asm new file mode 100644 index 0000000000..809c5d1378 --- /dev/null +++ b/libavresample/x86/audio_convert.asm @@ -0,0 +1,104 @@ +;****************************************************************************** +;* x86 optimized Format Conversion Utils +;* Copyright (c) 2008 Loren Merritt +;* +;* 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 +;****************************************************************************** + +%include "x86inc.asm" +%include "x86util.asm" + +SECTION_TEXT + +;----------------------------------------------------------------------------- +; void ff_conv_fltp_to_flt_6ch(float *dst, float *const *src, int len, +; int channels); +;----------------------------------------------------------------------------- + +%macro CONV_FLTP_TO_FLT_6CH 0 +cglobal conv_fltp_to_flt_6ch, 2,8,7, dst, src, src1, src2, src3, src4, src5, len +%if ARCH_X86_64 + mov lend, r2d +%else + %define lend dword r2m +%endif + mov src1q, [srcq+1*gprsize] + mov src2q, [srcq+2*gprsize] + mov src3q, [srcq+3*gprsize] + mov src4q, [srcq+4*gprsize] + mov src5q, [srcq+5*gprsize] + mov srcq, [srcq] + sub src1q, srcq + sub src2q, srcq + sub src3q, srcq + sub src4q, srcq + sub src5q, srcq +.loop: + mova m0, [srcq ] + mova m1, [srcq+src1q] + mova m2, [srcq+src2q] + mova m3, [srcq+src3q] + mova m4, [srcq+src4q] + mova m5, [srcq+src5q] +%if cpuflag(sse) + SBUTTERFLYPS 0, 1, 6 + SBUTTERFLYPS 2, 3, 6 + SBUTTERFLYPS 4, 5, 6 + + movaps m6, m4 + shufps m4, m0, q3210 + movlhps m0, m2 + movhlps m6, m2 + movaps [dstq ], m0 + movaps [dstq+16], m4 + movaps [dstq+32], m6 + + movaps m6, m5 + shufps m5, m1, q3210 + movlhps m1, m3 + movhlps m6, m3 + movaps [dstq+48], m1 + movaps [dstq+64], m5 + movaps [dstq+80], m6 +%else ; mmx + SBUTTERFLY dq, 0, 1, 6 + SBUTTERFLY dq, 2, 3, 6 + SBUTTERFLY dq, 4, 5, 6 + + movq [dstq ], m0 + movq [dstq+ 8], m2 + movq [dstq+16], m4 + movq [dstq+24], m1 + movq [dstq+32], m3 + movq [dstq+40], m5 +%endif + add srcq, mmsize + add dstq, mmsize*6 + sub lend, mmsize/4 + jg .loop +%if mmsize == 8 + emms + RET +%else + REP_RET +%endif +%endmacro + +INIT_MMX mmx +CONV_FLTP_TO_FLT_6CH +INIT_XMM sse +CONV_FLTP_TO_FLT_6CH diff --git a/libavresample/x86/audio_convert_init.c b/libavresample/x86/audio_convert_init.c new file mode 100644 index 0000000000..6883f10a21 --- /dev/null +++ b/libavresample/x86/audio_convert_init.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include "config.h" +#include "libavutil/cpu.h" +#include "libavresample/audio_convert.h" + +extern void ff_conv_fltp_to_flt_6ch_mmx(float *dst, float *const *src, int len); +extern void ff_conv_fltp_to_flt_6ch_sse(float *dst, float *const *src, int len); + +av_cold void ff_audio_convert_init_x86(AudioConvert *ac) +{ +#if HAVE_YASM + int mm_flags = av_get_cpu_flags(); + + if (mm_flags & AV_CPU_FLAG_MMX && HAVE_MMX) { + ff_audio_convert_set_func(ac, AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP, + 6, 1, 4, "MMX", ff_conv_fltp_to_flt_6ch_mmx); + } + if (mm_flags & AV_CPU_FLAG_SSE && HAVE_SSE) { + ff_audio_convert_set_func(ac, AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP, + 6, 16, 4, "SSE", ff_conv_fltp_to_flt_6ch_sse); + } +#endif +} diff --git a/libavresample/x86/audio_mix.asm b/libavresample/x86/audio_mix.asm new file mode 100644 index 0000000000..ef30f02486 --- /dev/null +++ b/libavresample/x86/audio_mix.asm @@ -0,0 +1,64 @@ +;****************************************************************************** +;* x86 optimized channel mixing +;* Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> +;* +;* 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 +;****************************************************************************** + +%include "x86inc.asm" +%include "x86util.asm" + +SECTION_TEXT + +;----------------------------------------------------------------------------- +; void ff_mix_2_to_1_fltp_flt(float **src, float **matrix, int len, +; int out_ch, int in_ch); +;----------------------------------------------------------------------------- + +%macro MIX_2_TO_1_FLTP_FLT 0 +cglobal mix_2_to_1_fltp_flt, 3,4,6, src, matrix, len, src1 + mov src1q, [srcq+gprsize] + mov srcq, [srcq ] + sub src1q, srcq + mov matrixq, [matrixq ] + VBROADCASTSS m4, [matrixq ] + VBROADCASTSS m5, [matrixq+4] + ALIGN 16 +.loop: + mulps m0, m4, [srcq ] + mulps m1, m5, [srcq+src1q ] + mulps m2, m4, [srcq+ mmsize] + mulps m3, m5, [srcq+src1q+mmsize] + addps m0, m0, m1 + addps m2, m2, m3 + mova [srcq ], m0 + mova [srcq+mmsize], m2 + add srcq, mmsize*2 + sub lend, mmsize*2/4 + jg .loop +%if mmsize == 32 + vzeroupper + RET +%else + REP_RET +%endif +%endmacro + +INIT_XMM sse +MIX_2_TO_1_FLTP_FLT +INIT_YMM avx +MIX_2_TO_1_FLTP_FLT diff --git a/libavresample/x86/audio_mix_init.c b/libavresample/x86/audio_mix_init.c new file mode 100644 index 0000000000..8f8930f836 --- /dev/null +++ b/libavresample/x86/audio_mix_init.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> + * + * 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 + */ + +#include "config.h" +#include "libavutil/cpu.h" +#include "libavresample/audio_mix.h" + +extern void ff_mix_2_to_1_fltp_flt_sse(float **src, float **matrix, int len, + int out_ch, int in_ch); +extern void ff_mix_2_to_1_fltp_flt_avx(float **src, float **matrix, int len, + int out_ch, int in_ch); + +av_cold void ff_audio_mix_init_x86(AudioMix *am) +{ +#if HAVE_YASM + int mm_flags = av_get_cpu_flags(); + + if (mm_flags & AV_CPU_FLAG_SSE && HAVE_SSE) { + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 2, 1, 16, 8, "SSE", ff_mix_2_to_1_fltp_flt_sse); + } + if (mm_flags & AV_CPU_FLAG_AVX && HAVE_AVX) { + ff_audio_mix_set_func(am, AV_SAMPLE_FMT_FLTP, AV_MIX_COEFF_TYPE_FLT, + 2, 1, 32, 16, "AVX", ff_mix_2_to_1_fltp_flt_avx); + } +#endif +} diff --git a/libavutil/x86/x86util.asm b/libavutil/x86/x86util.asm index a9c75645c0..55f4a936e2 100644 --- a/libavutil/x86/x86util.asm +++ b/libavutil/x86/x86util.asm @@ -585,3 +585,12 @@ pminsd %1, %3 pmaxsd %1, %2 %endmacro + +%macro VBROADCASTSS 2 ; dst xmm/ymm, src m32 +%if cpuflag(avx) + vbroadcastss %1, %2 +%else ; sse + movss %1, %2 + shufps %1, %1, 0 +%endif +%endmacro |