From 40a4b8afb14519cb536f2ecc1138834c9624497c Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Sun, 24 Mar 2013 17:31:27 +0000 Subject: [PATCH] Add support for resampling audio with libavresample This currently has a bunch of limitations. The resampling can't be reconfigured after the first call to GetAudio, and sample rate conversions aren't supported. Originally committed to SVN as r748. --- Makefile.am | 24 ++--- Makefile.in | 7 +- aclocal.m4 | 6 +- build-msvc/config.targets | 4 + build-msvc/config.xml | 6 ++ build-msvc/ffms2.vcxproj | 1 + config.sub | 8 +- configure | 135 +++++++++++++++++++++++--- configure.ac | 19 ++++ ffms2.pc.in | 2 +- include/ffms.h | 53 +++++++++- src/config/config.h.in | 3 + src/config/libs.cpp | 3 + src/core/audiosource.cpp | 199 +++++++++++++++++++++++++++++--------- src/core/audiosource.h | 12 ++- src/core/ffms.cpp | 18 ++++ src/core/utils.h | 102 ++++++++++++++++--- 17 files changed, 511 insertions(+), 91 deletions(-) diff --git a/Makefile.am b/Makefile.am index 87742f49e3..9e85852d0e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ dist_doc_DATA = doc/ffms2-api.html doc/ffms2-changelog.html doc/style.css INCLUDES = -I. -I$(top_srcdir)/include -I$(top_srcdir)/src/config @LIBAV_CFLAGS@ @ZLIB_CPPFLAGS@ -include config.h lib_LTLIBRARIES = src/core/libffms2.la -src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ +src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @AVRESAMPLE_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ src_core_libffms2_la_SOURCES = \ src/core/audiosource.h \ src/core/audiosource.cpp \ @@ -39,17 +39,17 @@ src_core_libffms2_la_SOURCES = \ src/core/utils.cpp \ src/core/videosource.h \ src/core/videosource.cpp \ - src/core/videoutils.h \ - src/core/videoutils.cpp \ - src/core/wave64writer.h \ - src/core/wave64writer.cpp \ - src/vapoursynth/VapourSynth.h \ - src/vapoursynth/vapoursource.h \ - src/vapoursynth/vapoursource.cpp \ - src/vapoursynth/vapoursynth.cpp - -include_HEADERS = $(top_srcdir)/include/ffms.h $(top_srcdir)/include/ffmscompat.h - + src/core/videoutils.h \ + src/core/videoutils.cpp \ + src/core/wave64writer.h \ + src/core/wave64writer.cpp \ + src/vapoursynth/VapourSynth.h \ + src/vapoursynth/vapoursource.h \ + src/vapoursynth/vapoursource.cpp \ + src/vapoursynth/vapoursynth.cpp + +include_HEADERS = $(top_srcdir)/include/ffms.h $(top_srcdir)/include/ffmscompat.h + bin_PROGRAMS = src/index/ffmsindex src_index_ffmsindex_SOURCES = src/index/ffmsindex.cpp src_index_ffmsindex_LDADD = src/core/libffms2.la diff --git a/Makefile.in b/Makefile.in index 9db1b1bb7d..1b08a7ae25 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.12.5 from Makefile.am. +# Makefile.in generated by automake 1.12.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2012 Free Software Foundation, Inc. @@ -218,6 +218,8 @@ AR = @AR@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ +AVRESAMPLE_CFLAGS = @AVRESAMPLE_CFLAGS@ +AVRESAMPLE_LIBS = @AVRESAMPLE_LIBS@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ @@ -321,6 +323,7 @@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ +libavresample = @libavresample@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ @@ -347,7 +350,7 @@ pkgconfig_DATA = ffms2.pc dist_doc_DATA = doc/ffms2-api.html doc/ffms2-changelog.html doc/style.css INCLUDES = -I. -I$(top_srcdir)/include -I$(top_srcdir)/src/config @LIBAV_CFLAGS@ @ZLIB_CPPFLAGS@ -include config.h lib_LTLIBRARIES = src/core/libffms2.la -src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ +src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @AVRESAMPLE_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ src_core_libffms2_la_SOURCES = \ src/core/audiosource.h \ src/core/audiosource.cpp \ diff --git a/aclocal.m4 b/aclocal.m4 index bc85fce595..50c5aa9093 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.12.5 -*- Autoconf -*- +# generated automatically by aclocal 1.12.6 -*- Autoconf -*- # Copyright (C) 1996-2012 Free Software Foundation, Inc. @@ -234,7 +234,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.12' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.12.5], [], +m4_if([$1], [1.12.6], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -250,7 +250,7 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.12.5])dnl +[AM_AUTOMAKE_VERSION([1.12.6])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) diff --git a/build-msvc/config.targets b/build-msvc/config.targets index 814a0a599e..8d2ae26127 100644 --- a/build-msvc/config.targets +++ b/build-msvc/config.targets @@ -17,6 +17,10 @@ WITH_PTHREAD_GC2;%(PreprocessorDefinitions) + + WITH_AVRESAMPLE;%(PreprocessorDefinitions) + + FFMS_USE_FFMPEG_COMPAT;%(PreprocessorDefinitions) diff --git a/build-msvc/config.xml b/build-msvc/config.xml index bce93186f1..05aaa806be 100644 --- a/build-msvc/config.xml +++ b/build-msvc/config.xml @@ -42,4 +42,10 @@ Description="Was FFmpeg/libav built with pthreads rather than w32threads?" /> + + diff --git a/build-msvc/ffms2.vcxproj b/build-msvc/ffms2.vcxproj index f0114744ab..8ba001d218 100644 --- a/build-msvc/ffms2.vcxproj +++ b/build-msvc/ffms2.vcxproj @@ -29,6 +29,7 @@ false false false + true libav diff --git a/config.sub b/config.sub index 89b1286300..8df5511094 100755 --- a/config.sub +++ b/config.sub @@ -4,7 +4,7 @@ # 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # 2011, 2012 Free Software Foundation, Inc. -timestamp='2012-10-10' +timestamp='2012-12-06' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software @@ -1026,7 +1026,11 @@ case $basic_machine in basic_machine=i586-unknown os=-pw32 ;; - rdos) + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) basic_machine=i386-pc os=-rdos ;; diff --git a/configure b/configure index 28e6dfd032..c237a48fe4 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for ffms2 2.17.3. +# Generated by GNU Autoconf 2.69 for ffms2 2.17.4. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -587,8 +587,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='ffms2' PACKAGE_TARNAME='ffms2' -PACKAGE_VERSION='2.17.3' -PACKAGE_STRING='ffms2 2.17.3' +PACKAGE_VERSION='2.17.4' +PACKAGE_STRING='ffms2 2.17.4' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -634,6 +634,9 @@ am__EXEEXT_TRUE LTLIBOBJS LIBOBJS LTUNDEF +libavresample +AVRESAMPLE_LIBS +AVRESAMPLE_CFLAGS LIBAV_LIBS LIBAV_CFLAGS pkgconfigdir @@ -781,6 +784,7 @@ with_gnu_ld with_sysroot enable_libtool_lock with_zlib +enable_avresample ' ac_precious_vars='build_alias host_alias @@ -799,7 +803,9 @@ PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR LIBAV_CFLAGS -LIBAV_LIBS' +LIBAV_LIBS +AVRESAMPLE_CFLAGS +AVRESAMPLE_LIBS' # Initialize some variables set by options. @@ -1340,7 +1346,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures ffms2 2.17.3 to adapt to many kinds of systems. +\`configure' configures ffms2 2.17.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1410,7 +1416,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of ffms2 2.17.3:";; + short | recursive ) echo "Configuration of ffms2 2.17.4:";; esac cat <<\_ACEOF @@ -1433,6 +1439,7 @@ Optional Features: --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) + --enable-avresample use libavresample for audio resampling Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1464,6 +1471,10 @@ Some influential environment variables: LIBAV_CFLAGS C compiler flags for LIBAV, overriding pkg-config LIBAV_LIBS linker flags for LIBAV, overriding pkg-config + AVRESAMPLE_CFLAGS + C compiler flags for AVRESAMPLE, overriding pkg-config + AVRESAMPLE_LIBS + linker flags for AVRESAMPLE, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -1531,7 +1542,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -ffms2 configure 2.17.3 +ffms2 configure 2.17.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2071,7 +2082,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by ffms2 $as_me 2.17.3, which was +It was created by ffms2 $as_me 2.17.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2897,7 +2908,7 @@ fi # Define the identity of the package. PACKAGE='ffms2' - VERSION='2.17.3' + VERSION='2.17.4' cat >>confdefs.h <<_ACEOF @@ -15987,6 +15998,108 @@ fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext +# Check whether --enable-avresample was given. +if test "${enable_avresample+set}" = set; then : + enableval=$enable_avresample; +fi + +if test x$enable_avresample != xno; then : + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for AVRESAMPLE" >&5 +$as_echo_n "checking for AVRESAMPLE... " >&6; } + +if test -n "$AVRESAMPLE_CFLAGS"; then + pkg_cv_AVRESAMPLE_CFLAGS="$AVRESAMPLE_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavresample >= 1.0.0\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libavresample >= 1.0.0") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_AVRESAMPLE_CFLAGS=`$PKG_CONFIG --cflags "libavresample >= 1.0.0" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$AVRESAMPLE_LIBS"; then + pkg_cv_AVRESAMPLE_LIBS="$AVRESAMPLE_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavresample >= 1.0.0\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libavresample >= 1.0.0") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_AVRESAMPLE_LIBS=`$PKG_CONFIG --libs "libavresample >= 1.0.0" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + AVRESAMPLE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libavresample >= 1.0.0" 2>&1` + else + AVRESAMPLE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libavresample >= 1.0.0" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$AVRESAMPLE_PKG_ERRORS" >&5 + + + if test x$enable_avresample = xyes; then : + as_fn_error $? "--enable-avresample was specified, but avresample 1.0.0+ could not be found." "$LINENO" 5 +fi + enable_avresample=no + +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + if test x$enable_avresample = xyes; then : + as_fn_error $? "--enable-avresample was specified, but avresample 1.0.0+ could not be found." "$LINENO" 5 +fi + enable_avresample=no + +else + AVRESAMPLE_CFLAGS=$pkg_cv_AVRESAMPLE_CFLAGS + AVRESAMPLE_LIBS=$pkg_cv_AVRESAMPLE_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + enable_avresample=yes +fi + +fi + +if test x$enable_avresample; then : + libavresample="libavresample" + +$as_echo "#define WITH_AVRESAMPLE 1" >>confdefs.h + +fi + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -Wl,-Bsymbolic is needed" >&5 $as_echo_n "checking whether -Wl,-Bsymbolic is needed... " >&6; } if test "$enable_shared" = yes; then @@ -16621,7 +16734,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by ffms2 $as_me 2.17.3, which was +This file was extended by ffms2 $as_me 2.17.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -16687,7 +16800,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -ffms2 config.status 2.17.3 +ffms2 config.status 2.17.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index ae1cc0b338..a1abaf09a1 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,25 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ AC_MSG_RESULT([no]) ]) +AC_ARG_ENABLE(avresample, + AS_HELP_STRING([--enable-avresample], + [use libavresample for audio resampling])) +AS_IF([test x$enable_avresample != xno], [ + PKG_CHECK_MODULES(AVRESAMPLE, [libavresample >= 1.0.0], [enable_avresample=yes], [ + AS_IF([test x$enable_avresample = xyes], + [AC_MSG_ERROR([--enable-avresample was specified, but avresample 1.0.0+ could not be found.])]) + enable_avresample=no + ]) +]) + +AS_IF([test x$enable_avresample], + [libavresample="libavresample" + AC_DEFINE([WITH_AVRESAMPLE], [1], [Use avresample])]) + +AC_SUBST([AVRESAMPLE_CFLAGS]) +AC_SUBST([AVRESAMPLE_LIBS]) +AC_SUBST([libavresample]) + AC_MSG_CHECKING([whether -Wl,-Bsymbolic is needed]) if test "$enable_shared" = yes; then _LDFLAGS="$LDFLAGS" diff --git a/ffms2.pc.in b/ffms2.pc.in index 7e316ead9e..2568b61423 100644 --- a/ffms2.pc.in +++ b/ffms2.pc.in @@ -7,7 +7,7 @@ includedir=@includedir@ Name: ffms2 Description: The Fabulous FM Library 2 -Requires.private: libavformat libavcodec libswscale libavutil +Requires.private: libavformat libavcodec libswscale libavutil @libavresample@ Version: @FFMS_VERSION@ Libs.private: @ZLIB_LDFLAGS@ -lz Libs: -L${libdir} -lffms2 diff --git a/include/ffms.h b/include/ffms.h index ce6869b8c6..70aed7bb0d 100644 --- a/include/ffms.h +++ b/include/ffms.h @@ -22,7 +22,7 @@ #define FFMS_H // Version format: major - minor - micro - bump -#define FFMS_VERSION ((2 << 24) | (17 << 16) | (3 << 8) | 0) +#define FFMS_VERSION ((2 << 24) | (17 << 16) | (4 << 8) | 0) #include @@ -113,6 +113,7 @@ typedef enum FFMS_Errors { FFMS_ERROR_TRACK, // track handling FFMS_ERROR_WAVE_WRITER, // WAVE64 file writer FFMS_ERROR_CANCELLED, // operation aborted + FFMS_ERROR_RESAMPLING, // audio resampling (libavresample) // Subtypes - what caused the error FFMS_ERROR_UNKNOWN = 20, // unknown error @@ -237,6 +238,53 @@ typedef enum FFMS_ColorRanges { FFMS_CR_JPEG = 2 // 2^n-1, or "fullrange" } FFMS_ColorRanges; +typedef enum FFMS_MixingCoefficientType { + FFMS_MIXING_COEFFICIENT_Q8 = 0, + FFMS_MIXING_COEFFICIENT_Q15 = 1, + FFMS_MIXING_COEFFICIENT_FLT = 2 +} FFMS_MixingCoefficientType; + +typedef enum FFMS_MatrixEncoding { + FFMS_MATRIX_ENCODING_NONE = 0, + FFMS_MATRIX_ENCODING_DOBLY = 1, + FFMS_MATRIX_ENCODING_PRO_LOGIC_II = 2 +} FFMS_MatrixEncoding; + +typedef enum FFMS_ResampleFilterType { + FFMS_RESAMPLE_FILTER_CUBIC = 0, + FFMS_RESAMPLE_FILTER_SINC = 1, + FFMS_RESAMPLE_FILTER_KAISER = 2 +} FFMS_ResampleFilterType; + +typedef enum FFMS_AudioDitherMethod { + FFMS_RESAMPLE_DITHER_NONE = 0, + FFMS_RESAMPLE_DITHER_RECTANGULAR = 1, + FFMS_RESAMPLE_DITHER_TRIANGULAR = 2, + FFMS_RESAMPLE_DITHER_TRIANGULAR_HIGHPASS = 3, + FFMS_RESAMPLE_DITHER_TRIANGULAR_NOISESHAPING = 4 +} FFMS_AudioDitherMethod; + +typedef struct FFMS_ResampleOptions { + int64_t ChannelLayout; + FFMS_SampleFormat SampleFormat; + int SampleRate; + FFMS_MixingCoefficientType MixingCoefficientType; + double CenterMixLevel; + double SurroundMixLevel; + double LFEMixLevel; + int Normalize; + int ForceResample; + int ResampleFilterSize; + int ResamplePhaseShift; + int LinearInterpolation; + double CutoffFrequencyRatio; + FFMS_MatrixEncoding MatrixedStereoEncoding; + FFMS_ResampleFilterType FilterType; + int KaiserBeta; + FFMS_AudioDitherMethod DitherMethod; +} FFMS_ResampleOptions; + + typedef struct FFMS_Frame { uint8_t *Data[4]; int Linesize[4]; @@ -319,6 +367,9 @@ FFMS_API(int) FFMS_SetOutputFormatV2(FFMS_VideoSource *V, const int *TargetForma FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V); FFMS_API(int) FFMS_SetInputFormatV(FFMS_VideoSource *V, int ColorSpace, int ColorRange, int Format, FFMS_ErrorInfo *ErrorInfo); /* Introduced in FFMS_VERSION ((2 << 24) | (17 << 16) | (1 << 8) | 0) */ FFMS_API(void) FFMS_ResetInputFormatV(FFMS_VideoSource *V); +FFMS_API(FFMS_ResampleOptions *) FFMS_CreateResampleOptions(FFMS_AudioSource *A); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ +FFMS_API(int) FFMS_SetOutputFormatA(FFMS_AudioSource *A, const FFMS_ResampleOptions*options, FFMS_ErrorInfo *ErrorInfo); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ +FFMS_API(void) FFMS_DestroyResampleOptions(FFMS_ResampleOptions *options); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index); FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index); FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer); diff --git a/src/config/config.h.in b/src/config/config.h.in index ac67875f70..0dd3087101 100644 --- a/src/config/config.h.in +++ b/src/config/config.h.in @@ -90,5 +90,8 @@ /* Version number of package */ #undef VERSION +/* Use avresample */ +#undef WITH_AVRESAMPLE + /* Define to `unsigned int' if does not define. */ #undef size_t diff --git a/src/config/libs.cpp b/src/config/libs.cpp index 752ea2f0f7..7cb011cc48 100644 --- a/src/config/libs.cpp +++ b/src/config/libs.cpp @@ -45,6 +45,9 @@ extern "C" { #pragma comment(lib, "libavcodec.a") #pragma comment(lib, "libavformat.a") #pragma comment(lib, "libswscale.a") +#ifdef WITH_AVRESAMPLE +#pragma comment(lib, "libavresample.a") +#endif #ifdef WITH_OPENCORE_AMR_NB #ifdef WITH_GCC_LIBAV diff --git a/src/core/audiosource.cpp b/src/core/audiosource.cpp index 8d7e965b1c..f839f3825e 100644 --- a/src/core/audiosource.cpp +++ b/src/core/audiosource.cpp @@ -23,10 +23,40 @@ #include #include +namespace { + + int64_t ChannelLayout; + FFMS_SampleFormat SampleFormat; + int SampleRate; +#define MAPPER(m, n) OptionMapper(n, &FFMS_ResampleOptions::m) +OptionMapper resample_options[] = { + MAPPER(ChannelLayout, "out_channel_layout"), + MAPPER(SampleFormat, "out_sample_fmt"), + MAPPER(SampleRate, "out_sample_rate"), + MAPPER(MixingCoefficientType, "mix_coeff_type"), + MAPPER(CenterMixLevel, "center_mix_level"), + MAPPER(SurroundMixLevel, "surround_mix_level"), + MAPPER(LFEMixLevel, "lfe_mix_level"), + MAPPER(Normalize, "normalize_mix_level"), + MAPPER(ForceResample, "force_resampling"), + MAPPER(ResampleFilterSize, "filter_size"), + MAPPER(ResamplePhaseShift, "phase_shift"), + MAPPER(LinearInterpolation, "linear_interp"), + MAPPER(CutoffFrequencyRatio, "cutoff"), + MAPPER(MatrixedStereoEncoding, "matrix_encoding"), + MAPPER(FilterType, "filter_type"), + MAPPER(KaiserBeta, "kaiser_beta"), + MAPPER(DitherMethod, "dither_method") +}; +#undef MAPPER + +} + FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track) : Delay(0) , MaxCacheBlocks(50) , BytesPerSample(0) +, NeedsResample(false) , CurrentSample(-1) , PacketNumber(0) , CurrentFrame(NULL) @@ -55,46 +85,14 @@ FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, in Index.AddRef(); } - #define EXCESSIVE_CACHE_SIZE 400 void FFMS_AudioSource::Init(const FFMS_Index &Index, int DelayMode) { - // The first packet after a seek is often decoded incorrectly, which - // makes it impossible to ever correctly seek back to the beginning, so - // store the first block now - - // In addition, anything with the same PTS as the first packet can't be - // distinguished from the first packet and so can't be seeked to, so - // store those as well - - // Some of LAVF's splitters don't like to seek to the beginning of the - // file (ts and?), so cache a few blocks even if PTSes are unique - // Packet 7 is the last packet I've had be unseekable to, so cache up to - // 10 for a bit of an extra buffer - CacheIterator end = Cache.end(); - while (PacketNumber < Frames.size() && - ((Frames[0].PTS != ffms_av_nopts_value && Frames[PacketNumber].PTS == Frames[0].PTS) || - Cache.size() < 10)) { - - // Vorbis in particular seems to like having 60+ packets at the start - // of the file with a PTS of 0, so we might need to expand the search - // range to account for that. - // Expanding slightly before it's strictly needed to ensure there's a - // bit of space for an actual cache - if (Cache.size() >= MaxCacheBlocks - 5) { - if (MaxCacheBlocks >= EXCESSIVE_CACHE_SIZE) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_ALLOCATION_FAILED, - "Exceeded the search range for an initial valid audio PTS"); - MaxCacheBlocks *= 2; - } - - DecodeNextBlock(&end); - } - // Store the iterator to the last element of the cache which is used for - // correctness rather than speed, so that when looking for one to delete - // we know how much to skip - CacheNoDelete = Cache.end(); - --CacheNoDelete; + // Decode the first packet to ensure all properties are initialized + // Don't cache it since it might be in the wrong format + // Instead, leave it in DecodeFrame and it'll get cached later + while (DecodeFrame->nb_samples == 0) + DecodeNextBlock(); // Read properties of the audio which may not be available until the first // frame has been decoded @@ -104,6 +102,11 @@ void FFMS_AudioSource::Init(const FFMS_Index &Index, int DelayMode) { throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Codec returned zero size audio"); + if (av_sample_fmt_is_planar(CodecContext->sample_fmt)) { + std::auto_ptr opt(CreateResampleOptions()); + SetOutputFormat(opt.get()); + } + if (DelayMode < FFMS_DELAY_NO_SHIFT) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Bad audio delay compensation mode"); @@ -146,10 +149,118 @@ void FFMS_AudioSource::Init(const FFMS_Index &Index, int DelayMode) { AP.NumSamples += Delay; } -void FFMS_AudioSource::InsertInterleaved(CacheIterator pos) { - AudioBlock& block = *Cache.insert(pos, AudioBlock(CurrentSample, DecodeFrame->nb_samples)); +void FFMS_AudioSource::CacheBeginning() { + // Nothing to do if the cache is already populated + if (!Cache.empty()) return; + + // The first frame is already decoded, so add it to the cache + CacheBlock(Cache.end()); + + // The first packet after a seek is often decoded incorrectly, which + // makes it impossible to ever correctly seek back to the beginning, so + // store the first block now + + // In addition, anything with the same PTS as the first packet can't be + // distinguished from the first packet and so can't be seeked to, so + // store those as well + + // Some of LAVF's splitters don't like to seek to the beginning of the + // file (ts and?), so cache a few blocks even if PTSes are unique + // Packet 7 is the last packet I've had be unseekable to, so cache up to + // 10 for a bit of an extra buffer + CacheIterator end = Cache.end(); + while (PacketNumber < Frames.size() && + ((Frames[0].PTS != ffms_av_nopts_value && Frames[PacketNumber].PTS == Frames[0].PTS) || + Cache.size() < 10)) { + + // Vorbis in particular seems to like having 60+ packets at the start + // of the file with a PTS of 0, so we might need to expand the search + // range to account for that. + // Expanding slightly before it's strictly needed to ensure there's a + // bit of space for an actual cache + if (Cache.size() >= MaxCacheBlocks - 5) { + if (MaxCacheBlocks >= EXCESSIVE_CACHE_SIZE) + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_ALLOCATION_FAILED, + "Exceeded the search range for an initial valid audio PTS"); + MaxCacheBlocks *= 2; + } + DecodeNextBlock(&end); + } + // Store the iterator to the last element of the cache which is used for + // correctness rather than speed, so that when looking for one to delete + // we know how much to skip + CacheNoDelete = Cache.end(); + --CacheNoDelete; +} + +void FFMS_AudioSource::SetOutputFormat(const FFMS_ResampleOptions *opt) { + if (!Cache.empty()) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_USER, + "Cannot change the output format after audio decoding has begun"); + + BytesPerSample = av_get_bytes_per_sample(static_cast(opt->SampleFormat)) * av_get_channel_layout_nb_channels(opt->ChannelLayout); + + NeedsResample = + opt->SampleFormat != (int)CodecContext->sample_fmt || + opt->SampleRate != AP.SampleRate || + opt->ChannelLayout != AP.ChannelLayout || + opt->ForceResample; + if (!NeedsResample) return; + + if (opt->SampleRate != AP.SampleRate) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "Sample rate changes are currently unsupported."); + +#ifdef WITH_AVRESAMPLE + if (opt->SampleRate != AP.SampleRate) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "Changing the audio sample rate is currently not supported"); + + std::auto_ptr oldOptions(ReadOptions(ResampleContext, resample_options)); + SetOptions(opt, ResampleContext, resample_options); + av_opt_set_int(ResampleContext, "in_sample_rate", AP.SampleRate, 0); + av_opt_set_int(ResampleContext, "in_sample_fmt", CodecContext->sample_fmt, 0); + av_opt_set_int(ResampleContext, "in_channel_layout", AP.ChannelLayout, 0); + + if (avresample_open(ResampleContext)) { + SetOptions(oldOptions.get(), ResampleContext, resample_options); + avresample_open(ResampleContext); + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNKNOWN, + "Could not open avresample context"); + } +#else + if (opt->SampleFormat != AP.SampleFormat || opt->SampleRate != AP.SampleRate || opt->ChannelLayout != AP.ChannelLayout) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "FFMS was not built with resampling enabled. The only supported conversion is interleaving planar audio."); +#endif +} + +FFMS_ResampleOptions *FFMS_AudioSource::CreateResampleOptions() const { +#ifdef WITH_AVRESAMPLE + FFMS_ResampleOptions *ret = ReadOptions(ResampleContext, resample_options); +#else + FFMS_ResampleOptions *ret = new FFMS_ResampleOptions; + memset(ret, 0, sizeof(FFMS_ResampleOptions)); +#endif + ret->SampleRate = AP.SampleRate; + ret->SampleFormat = static_cast(AP.SampleFormat); + ret->ChannelLayout = AP.ChannelLayout; + return ret; +} + +void FFMS_AudioSource::ResampleAndCache(CacheIterator pos) { + AudioBlock& block = *Cache.insert(pos, AudioBlock(CurrentSample, DecodeFrame->nb_samples)); block.Data.reserve(DecodeFrame->nb_samples * BytesPerSample); + +#ifdef WITH_AVRESAMPLE + block.Data.resize(block.Data.capacity()); + + uint8_t *OutPlanes[1] = { static_cast(&block.Data[0]) }; + avresample_convert(ResampleContext, + OutPlanes, block.Data.size(), DecodeFrame->nb_samples, + DecodeFrame->extended_data, DecodeFrame->nb_samples * av_get_bytes_per_sample(CodecContext->sample_fmt), DecodeFrame->nb_samples); +#else int width = av_get_bytes_per_sample(CodecContext->sample_fmt); uint8_t **Data = DecodeFrame->extended_data; @@ -157,11 +268,12 @@ void FFMS_AudioSource::InsertInterleaved(CacheIterator pos) { for (int c = 0; c < CodecContext->channels; ++c) block.Data.insert(block.Data.end(), &Data[c][s * width], &Data[c][(s + 1) * width]); } +#endif } void FFMS_AudioSource::CacheBlock(CacheIterator pos) { - if (CodecContext->channels > 1 && av_sample_fmt_is_planar(CodecContext->sample_fmt)) - InsertInterleaved(pos); + if (NeedsResample) + ResampleAndCache(pos); else Cache.insert(pos, AudioBlock(CurrentSample, DecodeFrame->nb_samples, DecodeFrame->extended_data[0], DecodeFrame->nb_samples * BytesPerSample)); @@ -179,9 +291,6 @@ void FFMS_AudioSource::CacheBlock(CacheIterator pos) { } void FFMS_AudioSource::DecodeNextBlock(CacheIterator *pos) { - if (BytesPerSample == 0) - BytesPerSample = av_get_bytes_per_sample(CodecContext->sample_fmt) * CodecContext->channels; - CurrentFrame = &Frames[PacketNumber]; AVPacket Packet; @@ -235,6 +344,8 @@ void FFMS_AudioSource::GetAudio(void *Buf, int64_t Start, int64_t Count) { throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds audio samples requested"); + CacheBeginning(); + uint8_t *Dst = static_cast(Buf); // Apply audio delay (if any) and fill any samples before the start time with zero diff --git a/src/core/audiosource.h b/src/core/audiosource.h index 3814a44965..23a6a46fb2 100644 --- a/src/core/audiosource.h +++ b/src/core/audiosource.h @@ -46,7 +46,6 @@ extern "C" { #endif struct FFMS_AudioSource { -private: struct AudioBlock { int64_t Age; int64_t Start; @@ -83,11 +82,17 @@ struct FFMS_AudioSource { // bytes per sample * number of channels size_t BytesPerSample; + bool NeedsResample; + FFResampleContext ResampleContext; + // Insert the current audio frame into the cache void CacheBlock(CacheIterator pos); // Interleave the current audio frame and insert it into the cache - void InsertInterleaved(CacheIterator pos); + void ResampleAndCache(CacheIterator pos); + + // Cache the unseekable beginning of the file once the output format is set + void CacheBeginning(); // Called after seeking virtual void Seek() { }; @@ -125,6 +130,9 @@ struct FFMS_AudioSource { FFMS_Track *GetTrack() { return &Frames; } const FFMS_AudioProperties& GetAudioProperties() const { return AP; } void GetAudio(void *Buf, int64_t Start, int64_t Count); + + FFMS_ResampleOptions *CreateResampleOptions() const; + void SetOutputFormat(const FFMS_ResampleOptions *opt); }; class FFLAVFAudio : public FFMS_AudioSource { diff --git a/src/core/ffms.cpp b/src/core/ffms.cpp index c703225c52..49c06bd10b 100644 --- a/src/core/ffms.cpp +++ b/src/core/ffms.cpp @@ -256,6 +256,24 @@ FFMS_API(void) FFMS_ResetInputFormatV(FFMS_VideoSource *V) { V->ResetInputFormat(); } +FFMS_API(FFMS_ResampleOptions *) FFMS_CreateResampleOptions(FFMS_AudioSource *A) { + return A->CreateResampleOptions(); +} + +FFMS_API(void) FFMS_DestroyResampleOptions(FFMS_ResampleOptions *options) { + delete options; +} + +FFMS_API(int) FFMS_SetOutputFormatA(FFMS_AudioSource *A, const FFMS_ResampleOptions *options, FFMS_ErrorInfo *ErrorInfo) { + ClearErrorInfo(ErrorInfo); + try { + A->SetOutputFormat(options); + } catch (FFMS_Exception &e) { + return e.CopyOut(ErrorInfo); + } + return FFMS_ERROR_SUCCESS; +} + FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) { assert(Index != NULL); if (Index == NULL) diff --git a/src/core/utils.h b/src/core/utils.h index f421ef07d7..85833ee2b9 100644 --- a/src/core/utils.h +++ b/src/core/utils.h @@ -31,9 +31,13 @@ extern "C" { #include "stdiostream.h" #include +#include #include #include #include +#ifdef WITH_AVRESAMPLE +#include +#endif } // must be included after ffmpeg headers @@ -133,26 +137,34 @@ class FFCodecContext { } }; -class ScopedFrame { - AVFrame *frame; +template +class unknown_size { + T *ptr; - ScopedFrame(ScopedFrame const&); - ScopedFrame& operator=(ScopedFrame const&); + unknown_size(unknown_size const&); + unknown_size& operator=(unknown_size const&); public: - operator AVFrame*() const { return frame; } - AVFrame *operator->() const { return frame; } + operator T*() const { return ptr; } + operator void*() const { return ptr; } + T *operator->() const { return ptr; } + + unknown_size() : ptr(Alloc()) { } + ~unknown_size() { Del(&ptr); } +}; +class ScopedFrame : public unknown_size { +public: void reset() { - if (frame) - avcodec_get_frame_defaults(frame); - else - frame = avcodec_alloc_frame(); + avcodec_get_frame_defaults(*this); } - - ScopedFrame() : frame(0) { } - ~ScopedFrame() { if (frame) avcodec_free_frame(&frame); } }; +#ifdef WITH_AVRESAMPLE +typedef unknown_size FFResampleContext; +#else +typedef struct {} FFResampleContext; +#endif + inline void DeleteHaaliCodecContext(AVCodecContext *CodecContext) { av_freep(&CodecContext->extradata); av_freep(&CodecContext); @@ -228,4 +240,68 @@ void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext); void FlushBuffers(AVCodecContext *CodecContext); +namespace optdetail { + template + T get_av_opt(void *v, const char *name) { + return static_cast(av_get_int(v, name, 0)); + } + + template<> + inline double get_av_opt(void *v, const char *name) { + return av_get_double(v, name, 0); + } + + template + void set_av_opt(void *v, const char *name, T value) { + av_opt_set_int(v, name, value, 0); + } + + template<> + inline void set_av_opt(void *v, const char *name, double value) { + av_opt_set_double(v, name, value, 0); + } +} + +template +class OptionMapper { + struct OptionMapperBase { + virtual void ToOpt(const FFMS_Struct *src, void *dst) const=0; + virtual void FromOpt(FFMS_Struct *dst, void *src) const=0; + }; + + template + class OptionMapperImpl : public OptionMapperBase { + T (FFMS_Struct::*ptr); + const char *name; + + public: + OptionMapperImpl(T (FFMS_Struct::*ptr), const char *name) : ptr(ptr), name(name) { } + void ToOpt(const FFMS_Struct *src, void *dst) const { optdetail::set_av_opt(dst, name, src->*ptr); } + void FromOpt(FFMS_Struct *dst, void *src) const { dst->*ptr = optdetail::get_av_opt(src, name); } + }; + + OptionMapperBase *impl; + +public: + template + OptionMapper(const char *opt_name, T (FFMS_Struct::*member)) : impl(new OptionMapperImpl(member, opt_name)) { } + + void ToOpt(const FFMS_Struct *src, void *dst) const { impl->ToOpt(src, dst); } + void FromOpt(FFMS_Struct *dst, void *src) const { impl->FromOpt(dst, src); } +}; + +template +T *ReadOptions(void *opt, OptionMapper (&options)[N]) { + T *ret = new T; + for (int i = 0; i < N; ++i) + options[i].FromOpt(ret, opt); + return ret; +} + +template +void SetOptions(const T* src, void *opt, OptionMapper (&options)[N]) { + for (int i = 0; i < N; ++i) + options[i].ToOpt(src, opt); +} + #endif