diff options
author | jk7744.park <jk7744.park@samsung.com> | 2015-10-24 16:49:08 +0900 |
---|---|---|
committer | jk7744.park <jk7744.park@samsung.com> | 2015-10-24 16:49:08 +0900 |
commit | 32e864f0f32b7628d995e84d07646b01dc5bf2d5 (patch) | |
tree | 27f97eaa1ca7e171815e4f88b348448b05f02748 /gst/replaygain | |
parent | 9093d777f57b3f1dc62010bb8e0c7ed3d72a76d2 (diff) | |
download | gst-plugins-good-32e864f0f32b7628d995e84d07646b01dc5bf2d5.tar.gz gst-plugins-good-32e864f0f32b7628d995e84d07646b01dc5bf2d5.tar.bz2 gst-plugins-good-32e864f0f32b7628d995e84d07646b01dc5bf2d5.zip |
tizen 2.4 releasetizen_2.4_mobile_releasesubmit/tizen_2.4/20151028.064339accepted/tizen/2.4/mobile/20151029.032704accepted/tizen_2.4_mobile
Diffstat (limited to 'gst/replaygain')
-rw-r--r-- | gst/replaygain/Makefile.am | 38 | ||||
-rwxr-xr-x | gst/replaygain/Makefile.in | 961 | ||||
-rwxr-xr-x | gst/replaygain/gstrganalysis.c | 711 | ||||
-rw-r--r-- | gst/replaygain/gstrganalysis.h | 86 | ||||
-rw-r--r-- | gst/replaygain/gstrglimiter.c | 201 | ||||
-rw-r--r-- | gst/replaygain/gstrglimiter.h | 64 | ||||
-rwxr-xr-x | gst/replaygain/gstrgvolume.c | 689 | ||||
-rw-r--r-- | gst/replaygain/gstrgvolume.h | 88 | ||||
-rw-r--r-- | gst/replaygain/replaygain.c | 53 | ||||
-rw-r--r-- | gst/replaygain/replaygain.h | 36 | ||||
-rw-r--r-- | gst/replaygain/rganalysis.c | 824 | ||||
-rw-r--r-- | gst/replaygain/rganalysis.h | 63 |
12 files changed, 3814 insertions, 0 deletions
diff --git a/gst/replaygain/Makefile.am b/gst/replaygain/Makefile.am new file mode 100644 index 0000000..d352f18 --- /dev/null +++ b/gst/replaygain/Makefile.am @@ -0,0 +1,38 @@ +plugin_LTLIBRARIES = libgstreplaygain.la + +libgstreplaygain_la_SOURCES = \ + gstrganalysis.c \ + gstrglimiter.c \ + gstrgvolume.c \ + replaygain.c \ + rganalysis.c +libgstreplaygain_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstreplaygain_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstpbutils-$(GST_API_VERSION) -lgstaudio-$(GST_API_VERSION)\ + $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgstreplaygain_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstreplaygain_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +# headers we need but don't want installed +noinst_HEADERS = \ + gstrganalysis.h \ + gstrglimiter.h \ + gstrgvolume.h \ + replaygain.h \ + rganalysis.h + + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstreplaygain -:SHARED libgstreplaygain \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstreplaygain_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstreplaygain_la_CFLAGS) \ + -:LDFLAGS $(libgstreplaygain_la_LDFLAGS) \ + $(libgstreplaygain_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/gst/replaygain/Makefile.in b/gst/replaygain/Makefile.in new file mode 100755 index 0000000..11f7d05 --- /dev/null +++ b/gst/replaygain/Makefile.in @@ -0,0 +1,961 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = gst/replaygain +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/depcomp $(noinst_HEADERS) +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/common/m4/as-ac-expand.m4 \ + $(top_srcdir)/common/m4/as-auto-alt.m4 \ + $(top_srcdir)/common/m4/as-compiler-flag.m4 \ + $(top_srcdir)/common/m4/as-gcc-inline-assembly.m4 \ + $(top_srcdir)/common/m4/as-libtool.m4 \ + $(top_srcdir)/common/m4/as-version.m4 \ + $(top_srcdir)/common/m4/ax_create_stdint_h.m4 \ + $(top_srcdir)/common/m4/gst-arch.m4 \ + $(top_srcdir)/common/m4/gst-args.m4 \ + $(top_srcdir)/common/m4/gst-check.m4 \ + $(top_srcdir)/common/m4/gst-default.m4 \ + $(top_srcdir)/common/m4/gst-dowhile.m4 \ + $(top_srcdir)/common/m4/gst-error.m4 \ + $(top_srcdir)/common/m4/gst-feature.m4 \ + $(top_srcdir)/common/m4/gst-gettext.m4 \ + $(top_srcdir)/common/m4/gst-glib2.m4 \ + $(top_srcdir)/common/m4/gst-package-release-datetime.m4 \ + $(top_srcdir)/common/m4/gst-platform.m4 \ + $(top_srcdir)/common/m4/gst-plugin-docs.m4 \ + $(top_srcdir)/common/m4/gst-plugindir.m4 \ + $(top_srcdir)/common/m4/gst-x11.m4 \ + $(top_srcdir)/common/m4/gst.m4 \ + $(top_srcdir)/common/m4/gtk-doc.m4 \ + $(top_srcdir)/common/m4/orc.m4 $(top_srcdir)/common/m4/pkg.m4 \ + $(top_srcdir)/m4/aalib.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gst-fionread.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(plugindir)" +LTLIBRARIES = $(plugin_LTLIBRARIES) +am__DEPENDENCIES_1 = +libgstreplaygain_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libgstreplaygain_la_OBJECTS = libgstreplaygain_la-gstrganalysis.lo \ + libgstreplaygain_la-gstrglimiter.lo \ + libgstreplaygain_la-gstrgvolume.lo \ + libgstreplaygain_la-replaygain.lo \ + libgstreplaygain_la-rganalysis.lo +libgstreplaygain_la_OBJECTS = $(am_libgstreplaygain_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libgstreplaygain_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) \ + $(libgstreplaygain_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libgstreplaygain_la_SOURCES) +DIST_SOURCES = $(libgstreplaygain_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +AALIB_CFLAGS = @AALIB_CFLAGS@ +AALIB_CONFIG = @AALIB_CONFIG@ +AALIB_LIBS = @AALIB_LIBS@ +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BZ2_LIBS = @BZ2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFAULT_AUDIOSINK = @DEFAULT_AUDIOSINK@ +DEFAULT_AUDIOSRC = @DEFAULT_AUDIOSRC@ +DEFAULT_VIDEOSINK = @DEFAULT_VIDEOSINK@ +DEFAULT_VIDEOSRC = @DEFAULT_VIDEOSRC@ +DEFAULT_VISUALIZER = @DEFAULT_VISUALIZER@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEPRECATED_CFLAGS = @DEPRECATED_CFLAGS@ +DIRECTSOUND_CFLAGS = @DIRECTSOUND_CFLAGS@ +DIRECTSOUND_LDFLAGS = @DIRECTSOUND_LDFLAGS@ +DIRECTSOUND_LIBS = @DIRECTSOUND_LIBS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DV1394_CFLAGS = @DV1394_CFLAGS@ +DV1394_LIBS = @DV1394_LIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ERROR_CFLAGS = @ERROR_CFLAGS@ +ERROR_CXXFLAGS = @ERROR_CXXFLAGS@ +ERROR_OBJCFLAGS = @ERROR_OBJCFLAGS@ +EXEEXT = @EXEEXT@ +FFLAGS = @FFLAGS@ +FGREP = @FGREP@ +FLAC_CFLAGS = @FLAC_CFLAGS@ +FLAC_LIBS = @FLAC_LIBS@ +GCOV = @GCOV@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_LIBS = @GCOV_LIBS@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LDFLAGS = @GIO_LDFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_EXTRA_CFLAGS = @GLIB_EXTRA_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_PREFIX = @GLIB_PREFIX@ +GLIB_REQ = @GLIB_REQ@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GSTPB_PLUGINS_DIR = @GSTPB_PLUGINS_DIR@ +GSTPB_PREFIX = @GSTPB_PREFIX@ +GST_AGE = @GST_AGE@ +GST_ALL_LDFLAGS = @GST_ALL_LDFLAGS@ +GST_API_VERSION = @GST_API_VERSION@ +GST_BASE_CFLAGS = @GST_BASE_CFLAGS@ +GST_BASE_LIBS = @GST_BASE_LIBS@ +GST_CFLAGS = @GST_CFLAGS@ +GST_CHECK_CFLAGS = @GST_CHECK_CFLAGS@ +GST_CHECK_LIBS = @GST_CHECK_LIBS@ +GST_CONTROLLER_CFLAGS = @GST_CONTROLLER_CFLAGS@ +GST_CONTROLLER_LIBS = @GST_CONTROLLER_LIBS@ +GST_CURRENT = @GST_CURRENT@ +GST_CXXFLAGS = @GST_CXXFLAGS@ +GST_LEVEL_DEFAULT = @GST_LEVEL_DEFAULT@ +GST_LIBS = @GST_LIBS@ +GST_LIBVERSION = @GST_LIBVERSION@ +GST_LICENSE = @GST_LICENSE@ +GST_LT_LDFLAGS = @GST_LT_LDFLAGS@ +GST_NET_CFLAGS = @GST_NET_CFLAGS@ +GST_NET_LIBS = @GST_NET_LIBS@ +GST_OBJCFLAGS = @GST_OBJCFLAGS@ +GST_OPTION_CFLAGS = @GST_OPTION_CFLAGS@ +GST_OPTION_CXXFLAGS = @GST_OPTION_CXXFLAGS@ +GST_OPTION_OBJCFLAGS = @GST_OPTION_OBJCFLAGS@ +GST_PACKAGE_NAME = @GST_PACKAGE_NAME@ +GST_PACKAGE_ORIGIN = @GST_PACKAGE_ORIGIN@ +GST_PLUGINS_ALL = @GST_PLUGINS_ALL@ +GST_PLUGINS_BASE_CFLAGS = @GST_PLUGINS_BASE_CFLAGS@ +GST_PLUGINS_BASE_DIR = @GST_PLUGINS_BASE_DIR@ +GST_PLUGINS_BASE_LIBS = @GST_PLUGINS_BASE_LIBS@ +GST_PLUGINS_DIR = @GST_PLUGINS_DIR@ +GST_PLUGINS_NONPORTED = @GST_PLUGINS_NONPORTED@ +GST_PLUGINS_SELECTED = @GST_PLUGINS_SELECTED@ +GST_PLUGIN_LDFLAGS = @GST_PLUGIN_LDFLAGS@ +GST_PLUGIN_LIBTOOLFLAGS = @GST_PLUGIN_LIBTOOLFLAGS@ +GST_PREFIX = @GST_PREFIX@ +GST_REVISION = @GST_REVISION@ +GST_TOOLS_DIR = @GST_TOOLS_DIR@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_X11_CFLAGS = @GTK_X11_CFLAGS@ +GTK_X11_LIBS = @GTK_X11_LIBS@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HAVE_AVC1394 = @HAVE_AVC1394@ +HAVE_CXX = @HAVE_CXX@ +HAVE_DIRECTSOUND = @HAVE_DIRECTSOUND@ +HAVE_ROM1394 = @HAVE_ROM1394@ +HAVE_SPEEX = @HAVE_SPEEX@ +HAVE_X = @HAVE_X@ +HAVE_XSHM = @HAVE_XSHM@ +HAVE_ZLIB = @HAVE_ZLIB@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +JACK_0_120_1_CFLAGS = @JACK_0_120_1_CFLAGS@ +JACK_0_120_1_LIBS = @JACK_0_120_1_LIBS@ +JACK_1_9_7_CFLAGS = @JACK_1_9_7_CFLAGS@ +JACK_1_9_7_LIBS = @JACK_1_9_7_LIBS@ +JACK_CFLAGS = @JACK_CFLAGS@ +JACK_LIBS = @JACK_LIBS@ +JPEG_LIBS = @JPEG_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCACA_CFLAGS = @LIBCACA_CFLAGS@ +LIBCACA_LIBS = @LIBCACA_LIBS@ +LIBDV_CFLAGS = @LIBDV_CFLAGS@ +LIBDV_LIBS = @LIBDV_LIBS@ +LIBICONV = @LIBICONV@ +LIBIEC61883_CFLAGS = @LIBIEC61883_CFLAGS@ +LIBIEC61883_LIBS = @LIBIEC61883_LIBS@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBOBJS = @LIBOBJS@ +LIBPNG_CFLAGS = @LIBPNG_CFLAGS@ +LIBPNG_LIBS = @LIBPNG_LIBS@ +LIBRT = @LIBRT@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBV4L2_CFLAGS = @LIBV4L2_CFLAGS@ +LIBV4L2_LIBS = @LIBV4L2_LIBS@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALEDIR = @LOCALEDIR@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJC = @OBJC@ +OBJCDEPMODE = @OBJCDEPMODE@ +OBJCFLAGS = @OBJCFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +ORCC = @ORCC@ +ORCC_FLAGS = @ORCC_FLAGS@ +ORC_CFLAGS = @ORC_CFLAGS@ +ORC_LIBS = @ORC_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@ +PACKAGE_VERSION_MICRO = @PACKAGE_VERSION_MICRO@ +PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@ +PACKAGE_VERSION_NANO = @PACKAGE_VERSION_NANO@ +PACKAGE_VERSION_RELEASE = @PACKAGE_VERSION_RELEASE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PLUGINDIR = @PLUGINDIR@ +POSUB = @POSUB@ +PROFILE_CFLAGS = @PROFILE_CFLAGS@ +PULSE_CFLAGS = @PULSE_CFLAGS@ +PULSE_LIBS = @PULSE_LIBS@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RAW1394_CFLAGS = @RAW1394_CFLAGS@ +RAW1394_LIBS = @RAW1394_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHOUT2_CFLAGS = @SHOUT2_CFLAGS@ +SHOUT2_LIBS = @SHOUT2_LIBS@ +SOUP_CFLAGS = @SOUP_CFLAGS@ +SOUP_LIBS = @SOUP_LIBS@ +SPEEX_CFLAGS = @SPEEX_CFLAGS@ +SPEEX_LIBS = @SPEEX_LIBS@ +STRIP = @STRIP@ +TAGLIB_CFLAGS = @TAGLIB_CFLAGS@ +TAGLIB_CXXFLAGS = @TAGLIB_CXXFLAGS@ +TAGLIB_LIBS = @TAGLIB_LIBS@ +USE_NLS = @USE_NLS@ +VALGRIND_CFLAGS = @VALGRIND_CFLAGS@ +VALGRIND_LIBS = @VALGRIND_LIBS@ +VALGRIND_PATH = @VALGRIND_PATH@ +VERSION = @VERSION@ +VPX_130_CFLAGS = @VPX_130_CFLAGS@ +VPX_130_LIBS = @VPX_130_LIBS@ +VPX_CFLAGS = @VPX_CFLAGS@ +VPX_LIBS = @VPX_LIBS@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +WARNING_CXXFLAGS = @WARNING_CXXFLAGS@ +WARNING_OBJCFLAGS = @WARNING_OBJCFLAGS@ +WAVPACK_CFLAGS = @WAVPACK_CFLAGS@ +WAVPACK_LIBS = @WAVPACK_LIBS@ +XDAMAGE_CFLAGS = @XDAMAGE_CFLAGS@ +XDAMAGE_LIBS = @XDAMAGE_LIBS@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XMKMF = @XMKMF@ +XSHM_LIBS = @XSHM_LIBS@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +ac_ct_OBJC = @ac_ct_OBJC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +plugindir = @plugindir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +plugin_LTLIBRARIES = libgstreplaygain.la +libgstreplaygain_la_SOURCES = \ + gstrganalysis.c \ + gstrglimiter.c \ + gstrgvolume.c \ + replaygain.c \ + rganalysis.c + +libgstreplaygain_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) + +libgstreplaygain_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstpbutils-$(GST_API_VERSION) -lgstaudio-$(GST_API_VERSION)\ + $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) + +libgstreplaygain_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstreplaygain_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +# headers we need but don't want installed +noinst_HEADERS = \ + gstrganalysis.h \ + gstrglimiter.h \ + gstrgvolume.h \ + replaygain.h \ + rganalysis.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu gst/replaygain/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu gst/replaygain/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(plugindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(plugindir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(plugindir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ + } + +uninstall-pluginLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ + done + +clean-pluginLTLIBRARIES: + -test -z "$(plugin_LTLIBRARIES)" || rm -f $(plugin_LTLIBRARIES) + @list='$(plugin_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libgstreplaygain.la: $(libgstreplaygain_la_OBJECTS) $(libgstreplaygain_la_DEPENDENCIES) $(EXTRA_libgstreplaygain_la_DEPENDENCIES) + $(AM_V_CCLD)$(libgstreplaygain_la_LINK) -rpath $(plugindir) $(libgstreplaygain_la_OBJECTS) $(libgstreplaygain_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreplaygain_la-gstrganalysis.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreplaygain_la-gstrglimiter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreplaygain_la-gstrgvolume.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreplaygain_la-replaygain.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgstreplaygain_la-rganalysis.Plo@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +libgstreplaygain_la-gstrganalysis.lo: gstrganalysis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -MT libgstreplaygain_la-gstrganalysis.lo -MD -MP -MF $(DEPDIR)/libgstreplaygain_la-gstrganalysis.Tpo -c -o libgstreplaygain_la-gstrganalysis.lo `test -f 'gstrganalysis.c' || echo '$(srcdir)/'`gstrganalysis.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreplaygain_la-gstrganalysis.Tpo $(DEPDIR)/libgstreplaygain_la-gstrganalysis.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrganalysis.c' object='libgstreplaygain_la-gstrganalysis.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -c -o libgstreplaygain_la-gstrganalysis.lo `test -f 'gstrganalysis.c' || echo '$(srcdir)/'`gstrganalysis.c + +libgstreplaygain_la-gstrglimiter.lo: gstrglimiter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -MT libgstreplaygain_la-gstrglimiter.lo -MD -MP -MF $(DEPDIR)/libgstreplaygain_la-gstrglimiter.Tpo -c -o libgstreplaygain_la-gstrglimiter.lo `test -f 'gstrglimiter.c' || echo '$(srcdir)/'`gstrglimiter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreplaygain_la-gstrglimiter.Tpo $(DEPDIR)/libgstreplaygain_la-gstrglimiter.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrglimiter.c' object='libgstreplaygain_la-gstrglimiter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -c -o libgstreplaygain_la-gstrglimiter.lo `test -f 'gstrglimiter.c' || echo '$(srcdir)/'`gstrglimiter.c + +libgstreplaygain_la-gstrgvolume.lo: gstrgvolume.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -MT libgstreplaygain_la-gstrgvolume.lo -MD -MP -MF $(DEPDIR)/libgstreplaygain_la-gstrgvolume.Tpo -c -o libgstreplaygain_la-gstrgvolume.lo `test -f 'gstrgvolume.c' || echo '$(srcdir)/'`gstrgvolume.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreplaygain_la-gstrgvolume.Tpo $(DEPDIR)/libgstreplaygain_la-gstrgvolume.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gstrgvolume.c' object='libgstreplaygain_la-gstrgvolume.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -c -o libgstreplaygain_la-gstrgvolume.lo `test -f 'gstrgvolume.c' || echo '$(srcdir)/'`gstrgvolume.c + +libgstreplaygain_la-replaygain.lo: replaygain.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -MT libgstreplaygain_la-replaygain.lo -MD -MP -MF $(DEPDIR)/libgstreplaygain_la-replaygain.Tpo -c -o libgstreplaygain_la-replaygain.lo `test -f 'replaygain.c' || echo '$(srcdir)/'`replaygain.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreplaygain_la-replaygain.Tpo $(DEPDIR)/libgstreplaygain_la-replaygain.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='replaygain.c' object='libgstreplaygain_la-replaygain.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -c -o libgstreplaygain_la-replaygain.lo `test -f 'replaygain.c' || echo '$(srcdir)/'`replaygain.c + +libgstreplaygain_la-rganalysis.lo: rganalysis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -MT libgstreplaygain_la-rganalysis.lo -MD -MP -MF $(DEPDIR)/libgstreplaygain_la-rganalysis.Tpo -c -o libgstreplaygain_la-rganalysis.lo `test -f 'rganalysis.c' || echo '$(srcdir)/'`rganalysis.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgstreplaygain_la-rganalysis.Tpo $(DEPDIR)/libgstreplaygain_la-rganalysis.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rganalysis.c' object='libgstreplaygain_la-rganalysis.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(libgstreplaygain_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libgstreplaygain_la_CFLAGS) $(CFLAGS) -c -o libgstreplaygain_la-rganalysis.lo `test -f 'rganalysis.c' || echo '$(srcdir)/'`rganalysis.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(plugindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-pluginLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pluginLTLIBRARIES + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pluginLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-pluginLTLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-pluginLTLIBRARIES install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-pluginLTLIBRARIES + + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstreplaygain -:SHARED libgstreplaygain \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstreplaygain_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstreplaygain_la_CFLAGS) \ + -:LDFLAGS $(libgstreplaygain_la_LDFLAGS) \ + $(libgstreplaygain_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/gst/replaygain/gstrganalysis.c b/gst/replaygain/gstrganalysis.c new file mode 100755 index 0000000..98c3ca6 --- /dev/null +++ b/gst/replaygain/gstrganalysis.c @@ -0,0 +1,711 @@ +/* GStreamer ReplayGain analysis + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * + * gstrganalysis.c: Element that performs the ReplayGain analysis + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/** + * SECTION:element-rganalysis + * @see_also: #GstRgVolume + * + * This element analyzes raw audio sample data in accordance with the proposed + * <ulink url="http://replaygain.org">ReplayGain standard</ulink> for + * calculating the ideal replay gain for music tracks and albums. The element + * is designed as a pass-through filter that never modifies any data. As it + * receives an EOS event, it finalizes the ongoing analysis and generates a tag + * list containing the results. It is sent downstream with a tag event and + * posted on the message bus with a tag message. The EOS event is forwarded as + * normal afterwards. Result tag lists at least contain the tags + * #GST_TAG_TRACK_GAIN, #GST_TAG_TRACK_PEAK and #GST_TAG_REFERENCE_LEVEL. + * + * Because the generated metadata tags become available at the end of streams, + * downstream muxer and encoder elements are normally unable to save them in + * their output since they generally save metadata in the file header. + * Therefore, it is often necessary that applications read the results in a bus + * event handler for the tag message. Obtaining the values this way is always + * needed for album processing (see #GstRgAnalysis:num-tracks property) since + * the album gain and peak values need to be associated with all tracks of an + * album, not just the last one. + * + * <refsect2> + * <title>Example launch lines</title> + * |[ + * gst-launch-1.0 -t audiotestsrc wave=sine num-buffers=512 ! rganalysis ! fakesink + * ]| Analyze a simple test waveform + * |[ + * gst-launch-1.0 -t filesrc location=filename.ext ! decodebin \ + * ! audioconvert ! audioresample ! rganalysis ! fakesink + * ]| Analyze a given file + * |[ + * gst-launch-1.0 -t gnomevfssrc location=http://replaygain.hydrogenaudio.org/ref_pink.wav \ + * ! wavparse ! rganalysis ! fakesink + * ]| Analyze the pink noise reference file + * <para> + * The above launch line yields a result gain of +6 dB (instead of the expected + * +0 dB). This is not in error, refer to the #GstRgAnalysis:reference-level + * property documentation for more information. + * </para> + * </refsect2> + * <refsect2> + * <title>Acknowledgements</title> + * <para> + * This element is based on code used in the <ulink + * url="http://sjeng.org/vorbisgain.html">vorbisgain</ulink> program and many + * others. The relevant parts are copyrighted by David Robinson, Glen Sawyer + * and Frank Klemm. + * </para> + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gst/gst.h> +#include <gst/base/gstbasetransform.h> +#include <gst/audio/audio.h> + +#include "gstrganalysis.h" +#include "replaygain.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rg_analysis_debug); +#define GST_CAT_DEFAULT gst_rg_analysis_debug + +/* Default property value. */ +#define FORCED_DEFAULT TRUE +#define DEFAULT_MESSAGE FALSE + +enum +{ + PROP_0, + PROP_NUM_TRACKS, + PROP_FORCED, + PROP_REFERENCE_LEVEL, + PROP_MESSAGE +}; + +/* The ReplayGain algorithm is intended for use with mono and stereo + * audio. The used implementation has filter coefficients for the + * "usual" sample rates in the 8000 to 48000 Hz range. */ +#define REPLAY_GAIN_CAPS "audio/x-raw," \ + "format = (string) { "GST_AUDIO_NE(F32)","GST_AUDIO_NE(S16)" }, " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, " \ + "44100, 48000 }; " \ + "audio/x-raw," \ + "format = (string) { "GST_AUDIO_NE(F32)","GST_AUDIO_NE(S16)" }, " \ + "layout = (string) interleaved, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 0x3, " \ + "rate = (int) { 8000, 11025, 12000, 16000, 22050, 24000, 32000, " \ + "44100, 48000 }" + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (REPLAY_GAIN_CAPS)); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (REPLAY_GAIN_CAPS)); + +#define gst_rg_analysis_parent_class parent_class +G_DEFINE_TYPE (GstRgAnalysis, gst_rg_analysis, GST_TYPE_BASE_TRANSFORM); + +static void gst_rg_analysis_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rg_analysis_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_rg_analysis_start (GstBaseTransform * base); +static gboolean gst_rg_analysis_set_caps (GstBaseTransform * base, + GstCaps * incaps, GstCaps * outcaps); +static GstFlowReturn gst_rg_analysis_transform_ip (GstBaseTransform * base, + GstBuffer * buf); +static gboolean gst_rg_analysis_sink_event (GstBaseTransform * base, + GstEvent * event); +static gboolean gst_rg_analysis_stop (GstBaseTransform * base); + +static void gst_rg_analysis_handle_tags (GstRgAnalysis * filter, + const GstTagList * tag_list); +static void gst_rg_analysis_handle_eos (GstRgAnalysis * filter); +static gboolean gst_rg_analysis_track_result (GstRgAnalysis * filter, + GstTagList ** tag_list); +static gboolean gst_rg_analysis_album_result (GstRgAnalysis * filter, + GstTagList ** tag_list); + +static void +gst_rg_analysis_class_init (GstRgAnalysisClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstBaseTransformClass *trans_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_rg_analysis_set_property; + gobject_class->get_property = gst_rg_analysis_get_property; + + /** + * GstRgAnalysis:num-tracks: + * + * Number of remaining album tracks. + * + * Analyzing several streams sequentially and assigning them a common result + * gain is known as "album processing". If this gain is used during playback + * (by switching to "album mode"), all tracks of an album receive the same + * amplification. This keeps the relative volume levels between the tracks + * intact. To enable this, set this property to the number of streams that + * will be processed as album tracks. + * + * Every time an EOS event is received, the value of this property is + * decremented by one. As it reaches zero, it is assumed that the last track + * of the album finished. The tag list for the final stream will contain the + * additional tags #GST_TAG_ALBUM_GAIN and #GST_TAG_ALBUM_PEAK. All other + * streams just get the two track tags posted because the values for the album + * tags are not known before all tracks are analyzed. Applications need to + * ensure that the album gain and peak values are also associated with the + * other tracks when storing the results. + * + * If the total number of album tracks is unknown beforehand, just ensure that + * the value is greater than 1 before each track starts. Then before the end + * of the last track, set it to the value 1. + * + * To perform album processing, the element has to preserve data between + * streams. This cannot survive a state change to the NULL or READY state. + * If you change your pipeline's state to NULL or READY between tracks, lock + * the element's state using gst_element_set_locked_state() when it is in + * PAUSED or PLAYING. + */ + g_object_class_install_property (gobject_class, PROP_NUM_TRACKS, + g_param_spec_int ("num-tracks", "Number of album tracks", + "Number of remaining album tracks", 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgAnalysis:forced: + * + * Whether to analyze streams even when ReplayGain tags exist. + * + * For assisting transcoder/converter applications, the element can silently + * skip the processing of streams that already contain the necessary tags. + * Data will flow as usual but the element will not consume CPU time and will + * not generate result tags. To enable possible skipping, set this property + * to #FALSE. + * + * If used in conjunction with <link linkend="GstRgAnalysis--num-tracks">album + * processing</link>, the element will skip the number of remaining album + * tracks if a full set of tags is found for the first track. If a subsequent + * track of the album is missing tags, processing cannot start again. If this + * is undesired, the application has to scan all files beforehand and enable + * forcing of processing if needed. + */ + g_object_class_install_property (gobject_class, PROP_FORCED, + g_param_spec_boolean ("forced", "Forced", + "Analyze even if ReplayGain tags exist", + FORCED_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgAnalysis:reference-level: + * + * Reference level [dB]. + * + * Analyzing the ReplayGain pink noise reference waveform computes a result of + * +6 dB instead of the expected 0 dB. This is because the default reference + * level is 89 dB. To obtain values as lined out in the original proposal of + * ReplayGain, set this property to 83. + * + * Almost all software uses 89 dB as a reference however, and this value has + * become the new official value. That is to say, while the change has been + * acclaimed by the author of the ReplayGain proposal, the <ulink + * url="http://replaygain.org">webpage</ulink> is still outdated at the time + * of this writing. + * + * The value was changed because the original proposal recommends a default + * pre-amp value of +6 dB for playback. This seemed a bit odd, as it means + * that the algorithm has the general tendency to produce adjustment values + * that are 6 dB too low. Bumping the reference level by 6 dB compensated for + * this. + * + * The problem of the reference level being ambiguous for lack of concise + * standardization is to be solved by adopting the #GST_TAG_REFERENCE_LEVEL + * tag, which allows to store the used value alongside the gain values. + */ + g_object_class_install_property (gobject_class, PROP_REFERENCE_LEVEL, + g_param_spec_double ("reference-level", "Reference level", + "Reference level [dB]", 0.0, 150., RG_REFERENCE_LEVEL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MESSAGE, + g_param_spec_boolean ("message", "Message", + "Post statics messages", + DEFAULT_MESSAGE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + + trans_class = (GstBaseTransformClass *) klass; + trans_class->start = GST_DEBUG_FUNCPTR (gst_rg_analysis_start); + trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_rg_analysis_set_caps); + trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_rg_analysis_transform_ip); + trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_rg_analysis_sink_event); + trans_class->stop = GST_DEBUG_FUNCPTR (gst_rg_analysis_stop); + trans_class->passthrough_on_same_caps = TRUE; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_static_metadata (element_class, "ReplayGain analysis", + "Filter/Analyzer/Audio", + "Perform the ReplayGain analysis", + "Ren\xc3\xa9 Stadler <mail@renestadler.de>"); + + GST_DEBUG_CATEGORY_INIT (gst_rg_analysis_debug, "rganalysis", 0, + "ReplayGain analysis element"); +} + +static void +gst_rg_analysis_init (GstRgAnalysis * filter) +{ + GstBaseTransform *base = GST_BASE_TRANSFORM (filter); + + gst_base_transform_set_gap_aware (base, TRUE); + + filter->num_tracks = 0; + filter->forced = FORCED_DEFAULT; + filter->message = DEFAULT_MESSAGE; + filter->reference_level = RG_REFERENCE_LEVEL; + + filter->ctx = NULL; + filter->analyze = NULL; +} + +static void +gst_rg_analysis_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (object); + + GST_OBJECT_LOCK (filter); + switch (prop_id) { + case PROP_NUM_TRACKS: + filter->num_tracks = g_value_get_int (value); + break; + case PROP_FORCED: + filter->forced = g_value_get_boolean (value); + break; + case PROP_REFERENCE_LEVEL: + filter->reference_level = g_value_get_double (value); + break; + case PROP_MESSAGE: + filter->message = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (filter); +} + +static void +gst_rg_analysis_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (object); + + GST_OBJECT_LOCK (filter); + switch (prop_id) { + case PROP_NUM_TRACKS: + g_value_set_int (value, filter->num_tracks); + break; + case PROP_FORCED: + g_value_set_boolean (value, filter->forced); + break; + case PROP_REFERENCE_LEVEL: + g_value_set_double (value, filter->reference_level); + break; + case PROP_MESSAGE: + g_value_set_boolean (value, filter->message); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (filter); +} + +static void +gst_rg_analysis_post_message (gpointer rganalysis, GstClockTime timestamp, + GstClockTime duration, gdouble rglevel) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (rganalysis); + if (filter->message) { + GstMessage *m; + + m = gst_message_new_element (GST_OBJECT_CAST (rganalysis), + gst_structure_new ("rganalysis", + "timestamp", G_TYPE_UINT64, timestamp, + "duration", G_TYPE_UINT64, duration, + "rglevel", G_TYPE_DOUBLE, rglevel, NULL)); + + gst_element_post_message (GST_ELEMENT_CAST (rganalysis), m); + } +} + + +static gboolean +gst_rg_analysis_start (GstBaseTransform * base) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (base); + + filter->ignore_tags = FALSE; + filter->skip = FALSE; + filter->has_track_gain = FALSE; + filter->has_track_peak = FALSE; + filter->has_album_gain = FALSE; + filter->has_album_peak = FALSE; + + filter->ctx = rg_analysis_new (); + GST_OBJECT_LOCK (filter); + rg_analysis_init_silence_detection (filter->ctx, gst_rg_analysis_post_message, + filter); + GST_OBJECT_UNLOCK (filter); + filter->analyze = NULL; + + GST_LOG_OBJECT (filter, "started"); + + return TRUE; +} + +static gboolean +gst_rg_analysis_set_caps (GstBaseTransform * base, GstCaps * in_caps, + GstCaps * out_caps) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (base); + GstAudioInfo info; + gint rate, channels; + + g_return_val_if_fail (filter->ctx != NULL, FALSE); + + GST_DEBUG_OBJECT (filter, + "set_caps in %" GST_PTR_FORMAT " out %" GST_PTR_FORMAT, + in_caps, out_caps); + + if (!gst_audio_info_from_caps (&info, in_caps)) + goto invalid_format; + + rate = GST_AUDIO_INFO_RATE (&info); + + if (!rg_analysis_set_sample_rate (filter->ctx, rate)) + goto invalid_format; + + channels = GST_AUDIO_INFO_CHANNELS (&info); + + if (channels < 1 || channels > 2) + goto invalid_format; + + switch (GST_AUDIO_INFO_FORMAT (&info)) { + case GST_AUDIO_FORMAT_F32: + /* The depth is not variable for float formats of course. It just + * makes the transform function nice and simple if the + * rg_analysis_analyze_* functions have a common signature. */ + filter->depth = sizeof (gfloat) * 8; + + if (channels == 1) + filter->analyze = rg_analysis_analyze_mono_float; + else + filter->analyze = rg_analysis_analyze_stereo_float; + + break; + case GST_AUDIO_FORMAT_S16: + filter->depth = sizeof (gint16) * 8; + + if (channels == 1) + filter->analyze = rg_analysis_analyze_mono_int16; + else + filter->analyze = rg_analysis_analyze_stereo_int16; + break; + default: + goto invalid_format; + } + + return TRUE; + + /* Errors. */ +invalid_format: + { + filter->analyze = NULL; + GST_ELEMENT_ERROR (filter, CORE, NEGOTIATION, + ("Invalid incoming caps: %" GST_PTR_FORMAT, in_caps), (NULL)); + return FALSE; + } +} + +static GstFlowReturn +gst_rg_analysis_transform_ip (GstBaseTransform * base, GstBuffer * buf) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (base); + GstMapInfo map; + + g_return_val_if_fail (filter->ctx != NULL, GST_FLOW_FLUSHING); + g_return_val_if_fail (filter->analyze != NULL, GST_FLOW_NOT_NEGOTIATED); + + if (filter->skip) + return GST_FLOW_OK; + + gst_buffer_map (buf, &map, GST_MAP_READ); + GST_LOG_OBJECT (filter, "processing buffer of size %" G_GSIZE_FORMAT, + map.size); + + rg_analysis_start_buffer (filter->ctx, GST_BUFFER_TIMESTAMP (buf)); + filter->analyze (filter->ctx, map.data, map.size, filter->depth); + + gst_buffer_unmap (buf, &map); + + return GST_FLOW_OK; +} + +static gboolean +gst_rg_analysis_sink_event (GstBaseTransform * base, GstEvent * event) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (base); + + g_return_val_if_fail (filter->ctx != NULL, TRUE); + + switch (GST_EVENT_TYPE (event)) { + + case GST_EVENT_EOS: + { + GST_LOG_OBJECT (filter, "received EOS event"); + + gst_rg_analysis_handle_eos (filter); + + GST_LOG_OBJECT (filter, "passing on EOS event"); + + break; + } + case GST_EVENT_TAG: + { + GstTagList *tag_list; + + /* The reference to the tag list is borrowed. */ + gst_event_parse_tag (event, &tag_list); + gst_rg_analysis_handle_tags (filter, tag_list); + + break; + } + default: + break; + } + + return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (base, event); +} + +static gboolean +gst_rg_analysis_stop (GstBaseTransform * base) +{ + GstRgAnalysis *filter = GST_RG_ANALYSIS (base); + + g_return_val_if_fail (filter->ctx != NULL, FALSE); + + rg_analysis_destroy (filter->ctx); + filter->ctx = NULL; + + GST_LOG_OBJECT (filter, "stopped"); + + return TRUE; +} + +/* FIXME: handle global vs. stream-tags? */ +static void +gst_rg_analysis_handle_tags (GstRgAnalysis * filter, + const GstTagList * tag_list) +{ + gboolean album_processing = (filter->num_tracks > 0); + gdouble dummy; + + if (!album_processing) + filter->ignore_tags = FALSE; + + if (filter->skip && album_processing) { + GST_DEBUG_OBJECT (filter, "ignoring tag event: skipping album"); + return; + } else if (filter->skip) { + GST_DEBUG_OBJECT (filter, "ignoring tag event: skipping track"); + return; + } else if (filter->ignore_tags) { + GST_DEBUG_OBJECT (filter, "ignoring tag event: cannot skip anyways"); + return; + } + + filter->has_track_gain |= gst_tag_list_get_double (tag_list, + GST_TAG_TRACK_GAIN, &dummy); + filter->has_track_peak |= gst_tag_list_get_double (tag_list, + GST_TAG_TRACK_PEAK, &dummy); + filter->has_album_gain |= gst_tag_list_get_double (tag_list, + GST_TAG_ALBUM_GAIN, &dummy); + filter->has_album_peak |= gst_tag_list_get_double (tag_list, + GST_TAG_ALBUM_PEAK, &dummy); + + if (!(filter->has_track_gain && filter->has_track_peak)) { + GST_DEBUG_OBJECT (filter, "track tags not complete yet"); + return; + } + + if (album_processing && !(filter->has_album_gain && filter->has_album_peak)) { + GST_DEBUG_OBJECT (filter, "album tags not complete yet"); + return; + } + + if (filter->forced) { + GST_DEBUG_OBJECT (filter, + "existing tags are sufficient, but processing anyway (forced)"); + return; + } + + filter->skip = TRUE; + rg_analysis_reset (filter->ctx); + + if (!album_processing) { + GST_DEBUG_OBJECT (filter, + "existing tags are sufficient, will not process this track"); + } else { + GST_DEBUG_OBJECT (filter, + "existing tags are sufficient, will not process this album"); + } +} + +static void +gst_rg_analysis_handle_eos (GstRgAnalysis * filter) +{ + gboolean album_processing = (filter->num_tracks > 0); + gboolean album_finished = (filter->num_tracks == 1); + gboolean album_skipping = album_processing && filter->skip; + + filter->has_track_gain = FALSE; + filter->has_track_peak = FALSE; + + if (album_finished) { + filter->ignore_tags = FALSE; + filter->skip = FALSE; + filter->has_album_gain = FALSE; + filter->has_album_peak = FALSE; + } else if (!album_skipping) { + filter->skip = FALSE; + } + + /* We might have just fully processed a track because it has + * incomplete tags. If we do album processing and allow skipping + * (not forced), prevent switching to skipping if a later track with + * full tags comes along: */ + if (!filter->forced && album_processing && !album_finished) + filter->ignore_tags = TRUE; + + if (!filter->skip) { + GstTagList *tag_list = NULL; + gboolean track_success; + gboolean album_success = FALSE; + + track_success = gst_rg_analysis_track_result (filter, &tag_list); + + if (album_finished) + album_success = gst_rg_analysis_album_result (filter, &tag_list); + else if (!album_processing) + rg_analysis_reset_album (filter->ctx); + + if (track_success || album_success) { + GST_LOG_OBJECT (filter, "posting tag list with results"); + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_REFERENCE_LEVEL, filter->reference_level, NULL); + /* This takes ownership of our reference to the list */ + gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (filter), + gst_event_new_tag (tag_list)); + tag_list = NULL; + } + } + + if (album_processing) { + filter->num_tracks--; + + if (!album_finished) { + GST_DEBUG_OBJECT (filter, "album not finished yet (num-tracks is now %u)", + filter->num_tracks); + } else { + GST_DEBUG_OBJECT (filter, "album finished (num-tracks is now 0)"); + } + } + + if (album_processing) + g_object_notify (G_OBJECT (filter), "num-tracks"); +} + +/* FIXME: return tag list (lists?) based on input tags.. */ +static gboolean +gst_rg_analysis_track_result (GstRgAnalysis * filter, GstTagList ** tag_list) +{ + gboolean track_success; + gdouble track_gain, track_peak; + + track_success = rg_analysis_track_result (filter->ctx, &track_gain, + &track_peak); + + if (track_success) { + track_gain += filter->reference_level - RG_REFERENCE_LEVEL; + GST_INFO_OBJECT (filter, "track gain is %+.2f dB, peak %.6f", track_gain, + track_peak); + } else { + GST_INFO_OBJECT (filter, "track was too short to analyze"); + } + + if (track_success) { + if (*tag_list == NULL) + *tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (*tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, track_peak, GST_TAG_TRACK_GAIN, track_gain, NULL); + } + + return track_success; +} + +static gboolean +gst_rg_analysis_album_result (GstRgAnalysis * filter, GstTagList ** tag_list) +{ + gboolean album_success; + gdouble album_gain, album_peak; + + album_success = rg_analysis_album_result (filter->ctx, &album_gain, + &album_peak); + + if (album_success) { + album_gain += filter->reference_level - RG_REFERENCE_LEVEL; + GST_INFO_OBJECT (filter, "album gain is %+.2f dB, peak %.6f", album_gain, + album_peak); + } else { + GST_INFO_OBJECT (filter, "album was too short to analyze"); + } + + if (album_success) { + if (*tag_list == NULL) + *tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (*tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_ALBUM_PEAK, album_peak, GST_TAG_ALBUM_GAIN, album_gain, NULL); + } + + return album_success; +} diff --git a/gst/replaygain/gstrganalysis.h b/gst/replaygain/gstrganalysis.h new file mode 100644 index 0000000..0d68e63 --- /dev/null +++ b/gst/replaygain/gstrganalysis.h @@ -0,0 +1,86 @@ +/* GStreamer ReplayGain analysis + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * + * gstrganalysis.h: Element that performs the ReplayGain analysis + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __GST_RG_ANALYSIS_H__ +#define __GST_RG_ANALYSIS_H__ + +#include <gst/gst.h> +#include <gst/base/gstbasetransform.h> + +#include "rganalysis.h" + +G_BEGIN_DECLS + +#define GST_TYPE_RG_ANALYSIS \ + (gst_rg_analysis_get_type()) +#define GST_RG_ANALYSIS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RG_ANALYSIS,GstRgAnalysis)) +#define GST_RG_ANALYSIS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RG_ANALYSIS,GstRgAnalysisClass)) +#define GST_IS_RG_ANALYSIS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RG_ANALYSIS)) +#define GST_IS_RG_ANALYSIS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RG_ANALYSIS)) +typedef struct _GstRgAnalysis GstRgAnalysis; +typedef struct _GstRgAnalysisClass GstRgAnalysisClass; + +/** + * GstRgAnalysis: + * + * Opaque data structure. + */ +struct _GstRgAnalysis +{ + GstBaseTransform element; + + /*< private >*/ + + RgAnalysisCtx *ctx; + void (*analyze) (RgAnalysisCtx * ctx, gconstpointer data, gsize size, + guint depth); + gint depth; + + /* Property values. */ + guint num_tracks; + gdouble reference_level; + gboolean forced; + gboolean message; + + /* State machinery for skipping. */ + gboolean ignore_tags; + gboolean skip; + gboolean has_track_gain; + gboolean has_track_peak; + gboolean has_album_gain; + gboolean has_album_peak; +}; + +struct _GstRgAnalysisClass +{ + GstBaseTransformClass parent_class; +}; + +GType gst_rg_analysis_get_type (void); + +G_END_DECLS + +#endif /* __GST_RG_ANALYSIS_H__ */ diff --git a/gst/replaygain/gstrglimiter.c b/gst/replaygain/gstrglimiter.c new file mode 100644 index 0000000..95363be --- /dev/null +++ b/gst/replaygain/gstrglimiter.c @@ -0,0 +1,201 @@ +/* GStreamer ReplayGain limiter + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * gstrglimiter.c: Element to apply signal compression to raw audio data + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/** + * SECTION:element-rglimiter + * @see_also: #GstRgVolume + * + * This element applies signal compression/limiting to raw audio data. It + * performs strict hard limiting with soft-knee characteristics, using a + * threshold of -6 dB. This type of filter is mentioned in the proposed <ulink + * url="http://replaygain.org">ReplayGain standard</ulink>. + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 filesrc location=filename.ext ! decodebin ! audioconvert \ + * ! rgvolume pre-amp=6.0 headroom=10.0 ! rglimiter \ + * ! audioconvert ! audioresample ! alsasink + * ]|Playback of a file + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gst/gst.h> +#include <math.h> +#include <gst/audio/audio.h> + +#include "gstrglimiter.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rg_limiter_debug); +#define GST_CAT_DEFAULT gst_rg_limiter_debug + +enum +{ + PROP_0, + PROP_ENABLED, +}; + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "layout = (string) { interleaved, non-interleaved }, " + "channels = (int) [1, MAX], " "rate = (int) [1, MAX]")); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "layout = (string) { interleaved, non-interleaved}, " + "channels = (int) [1, MAX], " "rate = (int) [1, MAX]")); + +#define gst_rg_limiter_parent_class parent_class +G_DEFINE_TYPE (GstRgLimiter, gst_rg_limiter, GST_TYPE_BASE_TRANSFORM); + +static void gst_rg_limiter_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rg_limiter_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstFlowReturn gst_rg_limiter_transform_ip (GstBaseTransform * base, + GstBuffer * buf); + +static void +gst_rg_limiter_class_init (GstRgLimiterClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstBaseTransformClass *trans_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_rg_limiter_set_property; + gobject_class->get_property = gst_rg_limiter_get_property; + + g_object_class_install_property (gobject_class, PROP_ENABLED, + g_param_spec_boolean ("enabled", "Enabled", "Enable processing", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + trans_class = GST_BASE_TRANSFORM_CLASS (klass); + trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_rg_limiter_transform_ip); + trans_class->passthrough_on_same_caps = FALSE; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_static_metadata (element_class, "ReplayGain limiter", + "Filter/Effect/Audio", + "Apply signal compression to raw audio data", + "Ren\xc3\xa9 Stadler <mail@renestadler.de>"); + + GST_DEBUG_CATEGORY_INIT (gst_rg_limiter_debug, "rglimiter", 0, + "ReplayGain limiter element"); +} + +static void +gst_rg_limiter_init (GstRgLimiter * filter) +{ + GstBaseTransform *base = GST_BASE_TRANSFORM (filter); + + gst_base_transform_set_passthrough (base, FALSE); + gst_base_transform_set_gap_aware (base, TRUE); + + filter->enabled = TRUE; +} + +static void +gst_rg_limiter_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRgLimiter *filter = GST_RG_LIMITER (object); + + switch (prop_id) { + case PROP_ENABLED: + filter->enabled = g_value_get_boolean (value); + gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (filter), + !filter->enabled); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rg_limiter_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRgLimiter *filter = GST_RG_LIMITER (object); + + switch (prop_id) { + case PROP_ENABLED: + g_value_set_boolean (value, filter->enabled); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#define LIMIT 1.0 +#define THRES 0.5 /* ca. -6 dB */ +#define COMPL 0.5 /* LIMIT - THRESH */ + +static GstFlowReturn +gst_rg_limiter_transform_ip (GstBaseTransform * base, GstBuffer * buf) +{ + GstRgLimiter *filter = GST_RG_LIMITER (base); + gfloat *input; + GstMapInfo map; + guint count; + guint i; + + if (!filter->enabled) + return GST_FLOW_OK; + + if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)) + return GST_FLOW_OK; + + gst_buffer_map (buf, &map, GST_MAP_READ); + input = (gfloat *) map.data; + count = gst_buffer_get_size (buf) / sizeof (gfloat); + + for (i = count; i--;) { + if (*input > THRES) + *input = tanhf ((*input - THRES) / COMPL) * COMPL + THRES; + else if (*input < -THRES) + *input = tanhf ((*input + THRES) / COMPL) * COMPL - THRES; + input++; + } + + gst_buffer_unmap (buf, &map); + + return GST_FLOW_OK; +} diff --git a/gst/replaygain/gstrglimiter.h b/gst/replaygain/gstrglimiter.h new file mode 100644 index 0000000..63bd804 --- /dev/null +++ b/gst/replaygain/gstrglimiter.h @@ -0,0 +1,64 @@ +/* GStreamer ReplayGain limiter + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * gstrglimiter.h: Element to apply signal compression to raw audio data + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __GST_RG_LIMITER_H__ +#define __GST_RG_LIMITER_H__ + +#include <gst/gst.h> +#include <gst/base/gstbasetransform.h> + +#define GST_TYPE_RG_LIMITER \ + (gst_rg_limiter_get_type()) +#define GST_RG_LIMITER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RG_LIMITER,GstRgLimiter)) +#define GST_RG_LIMITER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RG_LIMITER,GstRgLimiterClass)) +#define GST_IS_RG_LIMITER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RG_LIMITER)) +#define GST_IS_RG_LIMITER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RG_LIMITER)) + +typedef struct _GstRgLimiter GstRgLimiter; +typedef struct _GstRgLimiterClass GstRgLimiterClass; + +/** + * GstRgLimiter: + * + * Opaque data structure. + */ +struct _GstRgLimiter +{ + GstBaseTransform element; + + /*< private >*/ + + gboolean enabled; +}; + +struct _GstRgLimiterClass +{ + GstBaseTransformClass parent_class; +}; + +GType gst_rg_limiter_get_type (void); + +#endif /* __GST_RG_LIMITER_H__ */ diff --git a/gst/replaygain/gstrgvolume.c b/gst/replaygain/gstrgvolume.c new file mode 100755 index 0000000..beb8fda --- /dev/null +++ b/gst/replaygain/gstrgvolume.c @@ -0,0 +1,689 @@ +/* GStreamer ReplayGain volume adjustment + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * gstrgvolume.c: Element to apply ReplayGain volume adjustment + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/** + * SECTION:element-rgvolume + * @see_also: #GstRgLimiter, #GstRgAnalysis + * + * This element applies volume changes to streams as lined out in the proposed + * <ulink url="http://replaygain.org">ReplayGain standard</ulink>. It + * interprets the ReplayGain meta data tags and carries out the adjustment (by + * using a volume element internally). The relevant tags are: + * <itemizedlist> + * <listitem>#GST_TAG_TRACK_GAIN</listitem> + * <listitem>#GST_TAG_TRACK_PEAK</listitem> + * <listitem>#GST_TAG_ALBUM_GAIN</listitem> + * <listitem>#GST_TAG_ALBUM_PEAK</listitem> + * <listitem>#GST_TAG_REFERENCE_LEVEL</listitem> + * </itemizedlist> + * The information carried by these tags must have been calculated beforehand by + * performing the ReplayGain analysis. This is implemented by the <link + * linkend="GstRgAnalysis">rganalysis</link> element. + * + * The signal compression/limiting recommendations outlined in the proposed + * standard are not implemented by this element. This has to be handled by + * separate elements because applications might want to have additional filters + * between the volume adjustment and the limiting stage. A basic limiter is + * included with this plugin: The <link linkend="GstRgLimiter">rglimiter</link> + * element applies -6 dB hard limiting as mentioned in the ReplayGain standard. + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 filesrc location=filename.ext ! decodebin ! audioconvert \ + * ! rgvolume ! audioconvert ! audioresample ! alsasink + * ]| Playback of a file + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> +#include <gst/audio/audio.h> +#include <math.h> + +#include "gstrgvolume.h" +#include "replaygain.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rg_volume_debug); +#define GST_CAT_DEFAULT gst_rg_volume_debug + +enum +{ + PROP_0, + PROP_ALBUM_MODE, + PROP_HEADROOM, + PROP_PRE_AMP, + PROP_FALLBACK_GAIN, + PROP_TARGET_GAIN, + PROP_RESULT_GAIN +}; + +#define DEFAULT_ALBUM_MODE TRUE +#define DEFAULT_HEADROOM 0.0 +#define DEFAULT_PRE_AMP 0.0 +#define DEFAULT_FALLBACK_GAIN 0.0 + +#define DB_TO_LINEAR(x) pow (10., (x) / 20.) +#define LINEAR_TO_DB(x) (20. * log10 (x)) + +#define GAIN_FORMAT "+.02f dB" +#define PEAK_FORMAT ".06f" + +#define VALID_GAIN(x) ((x) > -60.00 && (x) < 60.00) +#define VALID_PEAK(x) ((x) > 0.) + +/* Same template caps as GstVolume, for I don't like having just ANY caps. */ + +#define FORMAT "{ "GST_AUDIO_NE(F32)","GST_AUDIO_NE(S16)" }" + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMAT ", " + "layout = (string) { interleaved, non-interleaved }, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]")); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMAT ", " + "layout = (string) { interleaved, non-interleaved }, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]")); + +#define gst_rg_volume_parent_class parent_class +G_DEFINE_TYPE (GstRgVolume, gst_rg_volume, GST_TYPE_BIN); + +static void gst_rg_volume_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rg_volume_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_rg_volume_dispose (GObject * object); + +static GstStateChangeReturn gst_rg_volume_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_rg_volume_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event); + +static GstEvent *gst_rg_volume_tag_event (GstRgVolume * self, GstEvent * event); +static void gst_rg_volume_reset (GstRgVolume * self); +static void gst_rg_volume_update_gain (GstRgVolume * self); +static inline void gst_rg_volume_determine_gain (GstRgVolume * self, + gdouble * target_gain, gdouble * result_gain); + +static void +gst_rg_volume_class_init (GstRgVolumeClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstBinClass *bin_class; + + gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_rg_volume_set_property; + gobject_class->get_property = gst_rg_volume_get_property; + gobject_class->dispose = gst_rg_volume_dispose; + + /** + * GstRgVolume:album-mode: + * + * Whether to prefer album gain over track gain. + * + * If set to %TRUE, use album gain instead of track gain if both are + * available. This keeps the relative loudness levels of tracks from the same + * album intact. + * + * If set to %FALSE, track mode is used instead. This effectively leads to + * more extensive normalization. + * + * If album mode is enabled but the album gain tag is absent in the stream, + * the track gain is used instead. If both gain tags are missing, the value + * of the #GstRgVolume:fallback-gain property is used instead. + */ + g_object_class_install_property (gobject_class, PROP_ALBUM_MODE, + g_param_spec_boolean ("album-mode", "Album mode", + "Prefer album over track gain", DEFAULT_ALBUM_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgVolume:headroom: + * + * Extra headroom [dB]. This controls the amount by which the output can + * exceed digital full scale. + * + * Only set this to a value greater than 0.0 if signal compression/limiting of + * a suitable form is applied to the output (or output is brought into the + * correct range by some other transformation). + * + * This element internally uses a volume element, which also supports + * operating on integer audio formats. These formats do not allow exceeding + * digital full scale. If extra headroom is used, make sure that the raw + * audio data format is floating point (F32). Otherwise, + * clipping distortion might be introduced as part of the volume adjustment + * itself. + */ + g_object_class_install_property (gobject_class, PROP_HEADROOM, + g_param_spec_double ("headroom", "Headroom", "Extra headroom [dB]", + 0., 60., DEFAULT_HEADROOM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgVolume:pre-amp: + * + * Additional gain to apply globally [dB]. This controls the trade-off + * between uniformity of normalization and utilization of available dynamic + * range. + * + * Note that the default value is 0 dB because the ReplayGain reference value + * was adjusted by +6 dB (from 83 to 89 dB). At the time of this writing, the + * <ulink url="http://replaygain.org">webpage</ulink> is still outdated and + * does not reflect this change however. Where the original proposal states + * that a proper default pre-amp value is +6 dB, this translates to the used 0 + * dB. + */ + g_object_class_install_property (gobject_class, PROP_PRE_AMP, + g_param_spec_double ("pre-amp", "Pre-amp", "Extra gain [dB]", + -60., 60., DEFAULT_PRE_AMP, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgVolume:fallback-gain: + * + * Fallback gain [dB] for streams missing ReplayGain tags. + */ + g_object_class_install_property (gobject_class, PROP_FALLBACK_GAIN, + g_param_spec_double ("fallback-gain", "Fallback gain", + "Gain for streams missing tags [dB]", + -60., 60., DEFAULT_FALLBACK_GAIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgVolume:result-gain: + * + * Applied gain [dB]. This gain is applied to processed buffer data. + * + * This is set to the #GstRgVolume:target-gain if amplification by that amount + * can be applied safely. "Safely" means that the volume adjustment does not + * inflict clipping distortion. Should this not be the case, the result gain + * is set to an appropriately reduced value (by applying peak normalization). + * The proposed standard calls this "clipping prevention". + * + * The difference between target and result gain reflects the necessary amount + * of reduction. Applications can make use of this information to temporarily + * reduce the #GstRgVolume:pre-amp for subsequent streams, as recommended by + * the ReplayGain standard. + * + * Note that target and result gain differing for a great majority of streams + * indicates a problem: What happens in this case is that most streams receive + * peak normalization instead of amplification by the ideal replay gain. To + * prevent this, the #GstRgVolume:pre-amp has to be lowered and/or a limiter + * has to be used which facilitates the use of #GstRgVolume:headroom. + */ + g_object_class_install_property (gobject_class, PROP_RESULT_GAIN, + g_param_spec_double ("result-gain", "Result-gain", "Applied gain [dB]", + -120., 120., 0., G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstRgVolume:target-gain: + * + * Applicable gain [dB]. This gain is supposed to be applied. + * + * Depending on the value of the #GstRgVolume:album-mode property and the + * presence of ReplayGain tags in the stream, this is set according to one of + * these simple formulas: + * + * <itemizedlist> + * <listitem>#GstRgVolume:pre-amp + album gain of the stream</listitem> + * <listitem>#GstRgVolume:pre-amp + track gain of the stream</listitem> + * <listitem>#GstRgVolume:pre-amp + #GstRgVolume:fallback-gain</listitem> + * </itemizedlist> + */ + g_object_class_install_property (gobject_class, PROP_TARGET_GAIN, + g_param_spec_double ("target-gain", "Target-gain", + "Applicable gain [dB]", -120., 120., 0., + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + element_class = (GstElementClass *) klass; + element_class->change_state = GST_DEBUG_FUNCPTR (gst_rg_volume_change_state); + + bin_class = (GstBinClass *) klass; + /* Setting these to NULL makes gst_bin_add and _remove refuse to let anyone + * mess with our internals. */ + bin_class->add_element = NULL; + bin_class->remove_element = NULL; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_set_static_metadata (element_class, "ReplayGain volume", + "Filter/Effect/Audio", + "Apply ReplayGain volume adjustment", + "Ren\xc3\xa9 Stadler <mail@renestadler.de>"); + + GST_DEBUG_CATEGORY_INIT (gst_rg_volume_debug, "rgvolume", 0, + "ReplayGain volume element"); +} + +static void +gst_rg_volume_init (GstRgVolume * self) +{ + GObjectClass *volume_class; + GstPad *volume_pad, *ghost_pad; + + self->album_mode = DEFAULT_ALBUM_MODE; + self->headroom = DEFAULT_HEADROOM; + self->pre_amp = DEFAULT_PRE_AMP; + self->fallback_gain = DEFAULT_FALLBACK_GAIN; + self->target_gain = 0.0; + self->result_gain = 0.0; + + self->volume_element = gst_element_factory_make ("volume", "rgvolume-volume"); + if (G_UNLIKELY (self->volume_element == NULL)) { + GstMessage *msg; + + GST_WARNING_OBJECT (self, "could not create volume element"); + msg = gst_missing_element_message_new (GST_ELEMENT_CAST (self), "volume"); + gst_element_post_message (GST_ELEMENT_CAST (self), msg); + + /* Nothing else to do, we will refuse the state change from NULL to READY to + * indicate that something went very wrong. It is doubtful that someone + * attempts changing our state though, since we end up having no pads! */ + return; + } + + volume_class = G_OBJECT_GET_CLASS (G_OBJECT (self->volume_element)); + self->max_volume = G_PARAM_SPEC_DOUBLE + (g_object_class_find_property (volume_class, "volume"))->maximum; + + GST_BIN_CLASS (parent_class)->add_element (GST_BIN_CAST (self), + self->volume_element); + + volume_pad = gst_element_get_static_pad (self->volume_element, "sink"); + ghost_pad = gst_ghost_pad_new_from_template ("sink", volume_pad, + GST_PAD_PAD_TEMPLATE (volume_pad)); + gst_object_unref (volume_pad); + gst_pad_set_event_function (ghost_pad, gst_rg_volume_sink_event); + gst_element_add_pad (GST_ELEMENT_CAST (self), ghost_pad); + + volume_pad = gst_element_get_static_pad (self->volume_element, "src"); + ghost_pad = gst_ghost_pad_new_from_template ("src", volume_pad, + GST_PAD_PAD_TEMPLATE (volume_pad)); + gst_object_unref (volume_pad); + gst_element_add_pad (GST_ELEMENT_CAST (self), ghost_pad); +} + +static void +gst_rg_volume_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRgVolume *self = GST_RG_VOLUME (object); + + switch (prop_id) { + case PROP_ALBUM_MODE: + self->album_mode = g_value_get_boolean (value); + break; + case PROP_HEADROOM: + self->headroom = g_value_get_double (value); + break; + case PROP_PRE_AMP: + self->pre_amp = g_value_get_double (value); + break; + case PROP_FALLBACK_GAIN: + self->fallback_gain = g_value_get_double (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + gst_rg_volume_update_gain (self); +} + +static void +gst_rg_volume_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRgVolume *self = GST_RG_VOLUME (object); + + switch (prop_id) { + case PROP_ALBUM_MODE: + g_value_set_boolean (value, self->album_mode); + break; + case PROP_HEADROOM: + g_value_set_double (value, self->headroom); + break; + case PROP_PRE_AMP: + g_value_set_double (value, self->pre_amp); + break; + case PROP_FALLBACK_GAIN: + g_value_set_double (value, self->fallback_gain); + break; + case PROP_TARGET_GAIN: + g_value_set_double (value, self->target_gain); + break; + case PROP_RESULT_GAIN: + g_value_set_double (value, self->result_gain); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rg_volume_dispose (GObject * object) +{ + GstRgVolume *self = GST_RG_VOLUME (object); + + if (self->volume_element != NULL) { + /* Manually remove our child using the bin implementation of remove_element. + * This is needed because we prevent gst_bin_remove from working, which the + * parent dispose handler would use if we had any children left. */ + GST_BIN_CLASS (parent_class)->remove_element (GST_BIN_CAST (self), + self->volume_element); + self->volume_element = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static GstStateChangeReturn +gst_rg_volume_change_state (GstElement * element, GstStateChange transition) +{ + GstRgVolume *self = GST_RG_VOLUME (element); + GstStateChangeReturn res; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + + if (G_UNLIKELY (self->volume_element == NULL)) { + /* Creating our child volume element in _init failed. */ + return GST_STATE_CHANGE_FAILURE; + } + break; + + case GST_STATE_CHANGE_READY_TO_PAUSED: + + gst_rg_volume_reset (self); + break; + + default: + break; + } + + res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + return res; +} + +/* Event function for the ghost sink pad. */ +static gboolean +gst_rg_volume_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstRgVolume *self; + GstEvent *send_event = event; + gboolean res; + + self = GST_RG_VOLUME (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG: + + GST_LOG_OBJECT (self, "received tag event"); + + send_event = gst_rg_volume_tag_event (self, event); + + if (send_event == NULL) + GST_LOG_OBJECT (self, "all tags handled, dropping event"); + + break; + + case GST_EVENT_EOS: + + gst_rg_volume_reset (self); + break; + + default: + break; + } + + if (G_LIKELY (send_event != NULL)) + res = gst_pad_event_default (pad, parent, send_event); + else + res = TRUE; + + return res; +} + +static GstEvent * +gst_rg_volume_tag_event (GstRgVolume * self, GstEvent * event) +{ + GstTagList *tag_list; + gboolean has_track_gain, has_track_peak, has_album_gain, has_album_peak; + gboolean has_ref_level; + + g_return_val_if_fail (event != NULL, NULL); + g_return_val_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_TAG, event); + + gst_event_parse_tag (event, &tag_list); + + if (gst_tag_list_is_empty (tag_list)) + return event; + + has_track_gain = gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, + &self->track_gain); + has_track_peak = gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, + &self->track_peak); + has_album_gain = gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, + &self->album_gain); + has_album_peak = gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, + &self->album_peak); + has_ref_level = gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL, + &self->reference_level); + + if (!has_track_gain && !has_track_peak && !has_album_gain && !has_album_peak) + return event; + + if (has_ref_level && (has_track_gain || has_album_gain) + && (ABS (self->reference_level - RG_REFERENCE_LEVEL) > 1.e-6)) { + /* Log a message stating the amount of adjustment that is applied below. */ + GST_DEBUG_OBJECT (self, + "compensating for reference level difference by %" GAIN_FORMAT, + RG_REFERENCE_LEVEL - self->reference_level); + } + if (has_track_gain) { + self->track_gain += RG_REFERENCE_LEVEL - self->reference_level; + } + if (has_album_gain) { + self->album_gain += RG_REFERENCE_LEVEL - self->reference_level; + } + + /* Ignore values that are obviously invalid. */ + if (G_UNLIKELY (has_track_gain && !VALID_GAIN (self->track_gain))) { + GST_DEBUG_OBJECT (self, + "ignoring bogus track gain value %" GAIN_FORMAT, self->track_gain); + has_track_gain = FALSE; + } + if (G_UNLIKELY (has_track_peak && !VALID_PEAK (self->track_peak))) { + GST_DEBUG_OBJECT (self, + "ignoring bogus track peak value %" PEAK_FORMAT, self->track_peak); + has_track_peak = FALSE; + } + if (G_UNLIKELY (has_album_gain && !VALID_GAIN (self->album_gain))) { + GST_DEBUG_OBJECT (self, + "ignoring bogus album gain value %" GAIN_FORMAT, self->album_gain); + has_album_gain = FALSE; + } + if (G_UNLIKELY (has_album_peak && !VALID_PEAK (self->album_peak))) { + GST_DEBUG_OBJECT (self, + "ignoring bogus album peak value %" PEAK_FORMAT, self->album_peak); + has_album_peak = FALSE; + } + + /* Clamp peaks >1.0. Float based decoders can produce spurious samples >1.0, + * cutting these files back to 1.0 should not cause any audible distortion. + * This is most often seen with Vorbis files. */ + if (has_track_peak && self->track_peak > 1.) { + GST_DEBUG_OBJECT (self, + "clamping track peak %" PEAK_FORMAT " to 1.0", self->track_peak); + self->track_peak = 1.0; + } + if (has_album_peak && self->album_peak > 1.) { + GST_DEBUG_OBJECT (self, + "clamping album peak %" PEAK_FORMAT " to 1.0", self->album_peak); + self->album_peak = 1.0; + } + + self->has_track_gain |= has_track_gain; + self->has_track_peak |= has_track_peak; + self->has_album_gain |= has_album_gain; + self->has_album_peak |= has_album_peak; + + event = (GstEvent *) gst_mini_object_make_writable (GST_MINI_OBJECT (event)); + gst_event_parse_tag (event, &tag_list); + + gst_tag_list_remove_tag (tag_list, GST_TAG_TRACK_GAIN); + gst_tag_list_remove_tag (tag_list, GST_TAG_TRACK_PEAK); + gst_tag_list_remove_tag (tag_list, GST_TAG_ALBUM_GAIN); + gst_tag_list_remove_tag (tag_list, GST_TAG_ALBUM_PEAK); + gst_tag_list_remove_tag (tag_list, GST_TAG_REFERENCE_LEVEL); + + gst_rg_volume_update_gain (self); + + if (gst_tag_list_is_empty (tag_list)) { + gst_event_unref (event); + event = NULL; + } + + return event; +} + +static void +gst_rg_volume_reset (GstRgVolume * self) +{ + self->has_track_gain = FALSE; + self->has_track_peak = FALSE; + self->has_album_gain = FALSE; + self->has_album_peak = FALSE; + + self->reference_level = RG_REFERENCE_LEVEL; + + gst_rg_volume_update_gain (self); +} + +static void +gst_rg_volume_update_gain (GstRgVolume * self) +{ + gdouble target_gain, result_gain, result_volume; + gboolean target_gain_changed, result_gain_changed; + + gst_rg_volume_determine_gain (self, &target_gain, &result_gain); + + result_volume = DB_TO_LINEAR (result_gain); + + /* Ensure that the result volume is within the range that the volume element + * can handle. Currently, the limit is 10. (+20 dB), which should not be + * restrictive. */ + if (G_UNLIKELY (result_volume > self->max_volume)) { + GST_INFO_OBJECT (self, + "cannot handle result gain of %" GAIN_FORMAT " (%0.6f), adjusting", + result_gain, result_volume); + + result_volume = self->max_volume; + result_gain = LINEAR_TO_DB (result_volume); + } + + /* Direct comparison is OK in this case. */ + if (target_gain == result_gain) { + GST_INFO_OBJECT (self, + "result gain is %" GAIN_FORMAT " (%0.6f), matching target", + result_gain, result_volume); + } else { + GST_INFO_OBJECT (self, + "result gain is %" GAIN_FORMAT " (%0.6f), target is %" GAIN_FORMAT, + result_gain, result_volume, target_gain); + } + + target_gain_changed = (self->target_gain != target_gain); + result_gain_changed = (self->result_gain != result_gain); + + self->target_gain = target_gain; + self->result_gain = result_gain; + + g_object_set (self->volume_element, "volume", result_volume, NULL); + + if (target_gain_changed) + g_object_notify ((GObject *) self, "target-gain"); + if (result_gain_changed) + g_object_notify ((GObject *) self, "result-gain"); +} + +static inline void +gst_rg_volume_determine_gain (GstRgVolume * self, gdouble * target_gain, + gdouble * result_gain) +{ + gdouble gain, peak; + + if (!self->has_track_gain && !self->has_album_gain) { + + GST_DEBUG_OBJECT (self, "using fallback gain"); + gain = self->fallback_gain; + peak = 1.0; + + } else if ((self->album_mode && self->has_album_gain) + || (!self->album_mode && !self->has_track_gain)) { + + gain = self->album_gain; + if (G_LIKELY (self->has_album_peak)) { + peak = self->album_peak; + } else { + GST_DEBUG_OBJECT (self, "album peak missing, assuming 1.0"); + peak = 1.0; + } + /* Falling back from track to album gain shouldn't really happen. */ + if (G_UNLIKELY (!self->album_mode)) + GST_INFO_OBJECT (self, "falling back to album gain"); + + } else { + /* !album_mode && !has_album_gain || album_mode && has_track_gain */ + + gain = self->track_gain; + if (G_LIKELY (self->has_track_peak)) { + peak = self->track_peak; + } else { + GST_DEBUG_OBJECT (self, "track peak missing, assuming 1.0"); + peak = 1.0; + } + if (self->album_mode) + GST_INFO_OBJECT (self, "falling back to track gain"); + } + + gain += self->pre_amp; + + *target_gain = gain; + *result_gain = gain; + + if (LINEAR_TO_DB (peak) + gain > self->headroom) { + *result_gain = LINEAR_TO_DB (1. / peak) + self->headroom; + } +} diff --git a/gst/replaygain/gstrgvolume.h b/gst/replaygain/gstrgvolume.h new file mode 100644 index 0000000..a0a5a8c --- /dev/null +++ b/gst/replaygain/gstrgvolume.h @@ -0,0 +1,88 @@ +/* GStreamer ReplayGain volume adjustment + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * gstrgvolume.h: Element to apply ReplayGain volume adjustment + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __GST_RG_VOLUME_H__ +#define __GST_RG_VOLUME_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_RG_VOLUME \ + (gst_rg_volume_get_type()) +#define GST_RG_VOLUME(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RG_VOLUME,GstRgVolume)) +#define GST_RG_VOLUME_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RG_VOLUME,GstRgVolumeClass)) +#define GST_IS_RG_VOLUME(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RG_VOLUME)) +#define GST_IS_RG_VOLUME_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RG_VOLUME)) + +typedef struct _GstRgVolume GstRgVolume; +typedef struct _GstRgVolumeClass GstRgVolumeClass; + +/** + * GstRgVolume: + * + * Opaque data structure. + */ +struct _GstRgVolume +{ + GstBin bin; + + /*< private >*/ + + GstElement *volume_element; + gdouble max_volume; + + gboolean album_mode; + gdouble headroom; + gdouble pre_amp; + gdouble fallback_gain; + + gdouble target_gain; + gdouble result_gain; + + gdouble track_gain; + gdouble track_peak; + gdouble album_gain; + gdouble album_peak; + + gboolean has_track_gain; + gboolean has_track_peak; + gboolean has_album_gain; + gboolean has_album_peak; + + gdouble reference_level; +}; + +struct _GstRgVolumeClass +{ + GstBinClass parent_class; +}; + +GType gst_rg_volume_get_type (void); + +G_END_DECLS + +#endif /* __GST_RG_VOLUME_H__ */ diff --git a/gst/replaygain/replaygain.c b/gst/replaygain/replaygain.c new file mode 100644 index 0000000..cb499ed --- /dev/null +++ b/gst/replaygain/replaygain.c @@ -0,0 +1,53 @@ +/* GStreamer ReplayGain plugin + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * + * replaygain.c: Plugin providing ReplayGain related elements + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gst/gst.h> + +#include "gstrganalysis.h" +#include "gstrglimiter.h" +#include "gstrgvolume.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "rganalysis", GST_RANK_NONE, + GST_TYPE_RG_ANALYSIS)) + return FALSE; + + if (!gst_element_register (plugin, "rglimiter", GST_RANK_NONE, + GST_TYPE_RG_LIMITER)) + return FALSE; + + if (!gst_element_register (plugin, "rgvolume", GST_RANK_NONE, + GST_TYPE_RG_VOLUME)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, replaygain, + "ReplayGain volume normalization", plugin_init, VERSION, GST_LICENSE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/replaygain/replaygain.h b/gst/replaygain/replaygain.h new file mode 100644 index 0000000..15be888 --- /dev/null +++ b/gst/replaygain/replaygain.h @@ -0,0 +1,36 @@ +/* GStreamer ReplayGain plugin + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * + * replaygain.h: Plugin providing ReplayGain related elements + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __REPLAYGAIN_H__ +#define __REPLAYGAIN_H__ + +G_BEGIN_DECLS + +/* Reference level (in dBSPL). The 2001 proposal specifies 83. This was + * changed later in all implementations to 89, which is the new, offical value: + * David Robinson acknowledged the change but didn't update the website yet. */ + +#define RG_REFERENCE_LEVEL 89. + +G_END_DECLS + +#endif /* __REPLAYGAIN_H__ */ diff --git a/gst/replaygain/rganalysis.c b/gst/replaygain/rganalysis.c new file mode 100644 index 0000000..2553536 --- /dev/null +++ b/gst/replaygain/rganalysis.c @@ -0,0 +1,824 @@ +/* GStreamer ReplayGain analysis + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * Copyright (C) 2001 David Robinson <David@Robinson.org> + * Glen Sawyer <glensawyer@hotmail.com> + * + * rganalysis.c: Analyze raw audio data in accordance with ReplayGain + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* Based on code with Copyright (C) 2001 David Robinson + * <David@Robinson.org> and Glen Sawyer <glensawyer@hotmail.com>, + * which is distributed under the LGPL as part of the vorbisgain + * program. The original code also mentions Frank Klemm + * (http://www.uni-jena.de/~pfk/mpp/) for having contributed lots of + * good code. Specifically, this is based on the file + * "gain_analysis.c" from vorbisgain version 0.34. + */ + +/* Room for future improvement: Mono data is currently in fact copied + * to two channels which get processed normally. This means that mono + * input data is processed twice. + */ + +/* Helpful information for understanding this code: The two IIR + * filters depend on previous input _and_ previous output samples (up + * to the filter's order number of samples). This explains the whole + * lot of memcpy'ing done in rg_analysis_analyze and why the context + * holds so many buffers. + */ + +#include <math.h> +#include <string.h> +#include <glib.h> + +#include "rganalysis.h" + +#define YULE_ORDER 10 +#define BUTTER_ORDER 2 +/* Percentile which is louder than the proposed level: */ +#define RMS_PERCENTILE 95 +/* Duration of RMS window in milliseconds: */ +#define RMS_WINDOW_MSECS 50 +/* Histogram array elements per dB: */ +#define STEPS_PER_DB 100 +/* Histogram upper bound in dB (normal max. values in the wild are + * assumed to be around 70, 80 dB): */ +#define MAX_DB 120 +/* Calibration value: */ +#define PINK_REF 64.82 /* 298640883795 */ + +#define MAX_ORDER MAX (BUTTER_ORDER, YULE_ORDER) +#define MAX_SAMPLE_RATE 48000 +/* The + 999 has the effect of ceil()ing: */ +#define MAX_SAMPLE_WINDOW (guint) \ + ((MAX_SAMPLE_RATE * RMS_WINDOW_MSECS + 999) / 1000) + +/* Analysis result accumulator. */ + +struct _RgAnalysisAcc +{ + guint32 histogram[STEPS_PER_DB * MAX_DB]; + gdouble peak; +}; + +typedef struct _RgAnalysisAcc RgAnalysisAcc; + +/* Analysis context. */ + +struct _RgAnalysisCtx +{ + /* Filter buffers for left channel. */ + gfloat inprebuf_l[MAX_ORDER * 2]; + gfloat *inpre_l; + gfloat stepbuf_l[MAX_SAMPLE_WINDOW + MAX_ORDER]; + gfloat *step_l; + gfloat outbuf_l[MAX_SAMPLE_WINDOW + MAX_ORDER]; + gfloat *out_l; + /* Filter buffers for right channel. */ + gfloat inprebuf_r[MAX_ORDER * 2]; + gfloat *inpre_r; + gfloat stepbuf_r[MAX_SAMPLE_WINDOW + MAX_ORDER]; + gfloat *step_r; + gfloat outbuf_r[MAX_SAMPLE_WINDOW + MAX_ORDER]; + gfloat *out_r; + + /* Number of samples to reach duration of the RMS window: */ + guint window_n_samples; + /* Progress of the running window: */ + guint window_n_samples_done; + gdouble window_square_sum; + + gint sample_rate; + gint sample_rate_index; + + RgAnalysisAcc track; + RgAnalysisAcc album; + void (*post_message) (gpointer analysis, + GstClockTime timestamp, GstClockTime duration, gdouble rglevel); + gpointer analysis; + /* The timestamp of the current incoming buffer. */ + GstClockTime buffer_timestamp; + /* Number of samples processed in current buffer, during emit_signal, + this will always be on an RMS window boundary. */ + guint buffer_n_samples_done; +}; + +/* Filter coefficients for the IIR filters that form the equal + * loudness filter. XFilter[ctx->sample_rate_index] gives the array + * of the X coefficients (A or B) for the configured sample rate. */ + +#ifdef _MSC_VER +/* Disable double-to-float warning: */ +/* A better solution would be to append 'f' to each constant, but that + * makes the code ugly. */ +#pragma warning ( disable : 4305 ) +#endif + +static const gfloat AYule[9][11] = { + {1., -3.84664617118067, 7.81501653005538, -11.34170355132042, + 13.05504219327545, -12.28759895145294, 9.48293806319790, + -5.87257861775999, 2.75465861874613, -0.86984376593551, + 0.13919314567432}, + {1., -3.47845948550071, 6.36317777566148, -8.54751527471874, 9.47693607801280, + -8.81498681370155, 6.85401540936998, -4.39470996079559, + 2.19611684890774, -0.75104302451432, 0.13149317958808}, + {1., -2.37898834973084, 2.84868151156327, -2.64577170229825, 2.23697657451713, + -1.67148153367602, 1.00595954808547, -0.45953458054983, + 0.16378164858596, -0.05032077717131, 0.02347897407020}, + {1., -1.61273165137247, 1.07977492259970, -0.25656257754070, + -0.16276719120440, -0.22638893773906, 0.39120800788284, + -0.22138138954925, 0.04500235387352, 0.02005851806501, + 0.00302439095741}, + {1., -1.49858979367799, 0.87350271418188, 0.12205022308084, -0.80774944671438, + 0.47854794562326, -0.12453458140019, -0.04067510197014, + 0.08333755284107, -0.04237348025746, 0.02977207319925}, + {1., -0.62820619233671, 0.29661783706366, -0.37256372942400, 0.00213767857124, + -0.42029820170918, 0.22199650564824, 0.00613424350682, 0.06747620744683, + 0.05784820375801, 0.03222754072173}, + {1., -1.04800335126349, 0.29156311971249, -0.26806001042947, 0.00819999645858, + 0.45054734505008, -0.33032403314006, 0.06739368333110, + -0.04784254229033, 0.01639907836189, 0.01807364323573}, + {1., -0.51035327095184, -0.31863563325245, -0.20256413484477, + 0.14728154134330, 0.38952639978999, -0.23313271880868, + -0.05246019024463, -0.02505961724053, 0.02442357316099, + 0.01818801111503}, + {1., -0.25049871956020, -0.43193942311114, -0.03424681017675, + -0.04678328784242, 0.26408300200955, 0.15113130533216, + -0.17556493366449, -0.18823009262115, 0.05477720428674, + 0.04704409688120} +}; + +static const gfloat BYule[9][11] = { + {0.03857599435200, -0.02160367184185, -0.00123395316851, -0.00009291677959, + -0.01655260341619, 0.02161526843274, -0.02074045215285, + 0.00594298065125, 0.00306428023191, 0.00012025322027, 0.00288463683916}, + {0.05418656406430, -0.02911007808948, -0.00848709379851, -0.00851165645469, + -0.00834990904936, 0.02245293253339, -0.02596338512915, + 0.01624864962975, -0.00240879051584, 0.00674613682247, + -0.00187763777362}, + {0.15457299681924, -0.09331049056315, -0.06247880153653, 0.02163541888798, + -0.05588393329856, 0.04781476674921, 0.00222312597743, 0.03174092540049, + -0.01390589421898, 0.00651420667831, -0.00881362733839}, + {0.30296907319327, -0.22613988682123, -0.08587323730772, 0.03282930172664, + -0.00915702933434, -0.02364141202522, -0.00584456039913, + 0.06276101321749, -0.00000828086748, 0.00205861885564, + -0.02950134983287}, + {0.33642304856132, -0.25572241425570, -0.11828570177555, 0.11921148675203, + -0.07834489609479, -0.00469977914380, -0.00589500224440, + 0.05724228140351, 0.00832043980773, -0.01635381384540, + -0.01760176568150}, + {0.44915256608450, -0.14351757464547, -0.22784394429749, -0.01419140100551, + 0.04078262797139, -0.12398163381748, 0.04097565135648, 0.10478503600251, + -0.01863887810927, -0.03193428438915, 0.00541907748707}, + {0.56619470757641, -0.75464456939302, 0.16242137742230, 0.16744243493672, + -0.18901604199609, 0.30931782841830, -0.27562961986224, + 0.00647310677246, 0.08647503780351, -0.03788984554840, + -0.00588215443421}, + {0.58100494960553, -0.53174909058578, -0.14289799034253, 0.17520704835522, + 0.02377945217615, 0.15558449135573, -0.25344790059353, 0.01628462406333, + 0.06920467763959, -0.03721611395801, -0.00749618797172}, + {0.53648789255105, -0.42163034350696, -0.00275953611929, 0.04267842219415, + -0.10214864179676, 0.14590772289388, -0.02459864859345, + -0.11202315195388, -0.04060034127000, 0.04788665548180, + -0.02217936801134} +}; + +static const gfloat AButter[9][3] = { + {1., -1.97223372919527, 0.97261396931306}, + {1., -1.96977855582618, 0.97022847566350}, + {1., -1.95835380975398, 0.95920349965459}, + {1., -1.95002759149878, 0.95124613669835}, + {1., -1.94561023566527, 0.94705070426118}, + {1., -1.92783286977036, 0.93034775234268}, + {1., -1.91858953033784, 0.92177618768381}, + {1., -1.91542108074780, 0.91885558323625}, + {1., -1.88903307939452, 0.89487434461664} +}; + +static const gfloat BButter[9][3] = { + {0.98621192462708, -1.97242384925416, 0.98621192462708}, + {0.98500175787242, -1.97000351574484, 0.98500175787242}, + {0.97938932735214, -1.95877865470428, 0.97938932735214}, + {0.97531843204928, -1.95063686409857, 0.97531843204928}, + {0.97316523498161, -1.94633046996323, 0.97316523498161}, + {0.96454515552826, -1.92909031105652, 0.96454515552826}, + {0.96009142950541, -1.92018285901082, 0.96009142950541}, + {0.95856916599601, -1.91713833199203, 0.95856916599601}, + {0.94597685600279, -1.89195371200558, 0.94597685600279} +}; + +#ifdef _MSC_VER +#pragma warning ( default : 4305 ) +#endif + +/* Filter functions. These access elements with negative indices of + * the input and output arrays (up to the filter's order). */ + +/* For much better performance, the function below has been + * implemented by unrolling the inner loop for our two use cases. */ + +/* + * static inline void + * apply_filter (const gfloat * input, gfloat * output, guint n_samples, + * const gfloat * a, const gfloat * b, guint order) + * { + * gfloat y; + * gint i, k; + * + * for (i = 0; i < n_samples; i++) { + * y = input[i] * b[0]; + * for (k = 1; k <= order; k++) + * y += input[i - k] * b[k] - output[i - k] * a[k]; + * output[i] = y; + * } + * } + */ + +static inline void +yule_filter (const gfloat * input, gfloat * output, + const gfloat * a, const gfloat * b) +{ + /* 1e-10 is added below to avoid running into denormals when operating on + * near silence. */ + + output[0] = 1e-10 + input[0] * b[0] + + input[-1] * b[1] - output[-1] * a[1] + + input[-2] * b[2] - output[-2] * a[2] + + input[-3] * b[3] - output[-3] * a[3] + + input[-4] * b[4] - output[-4] * a[4] + + input[-5] * b[5] - output[-5] * a[5] + + input[-6] * b[6] - output[-6] * a[6] + + input[-7] * b[7] - output[-7] * a[7] + + input[-8] * b[8] - output[-8] * a[8] + + input[-9] * b[9] - output[-9] * a[9] + + input[-10] * b[10] - output[-10] * a[10]; +} + +static inline void +butter_filter (const gfloat * input, gfloat * output, + const gfloat * a, const gfloat * b) +{ + output[0] = input[0] * b[0] + + input[-1] * b[1] - output[-1] * a[1] + + input[-2] * b[2] - output[-2] * a[2]; +} + +/* Because butter_filter and yule_filter are inlined, this function is + * a bit blown-up (code-size wise), but not inlining gives a ca. 40% + * performance penalty. */ + +static inline void +apply_filters (const RgAnalysisCtx * ctx, const gfloat * input_l, + const gfloat * input_r, guint n_samples) +{ + const gfloat *ayule = AYule[ctx->sample_rate_index]; + const gfloat *byule = BYule[ctx->sample_rate_index]; + const gfloat *abutter = AButter[ctx->sample_rate_index]; + const gfloat *bbutter = BButter[ctx->sample_rate_index]; + gint pos = ctx->window_n_samples_done; + gint i; + + for (i = 0; i < n_samples; i++, pos++) { + yule_filter (input_l + i, ctx->step_l + pos, ayule, byule); + butter_filter (ctx->step_l + pos, ctx->out_l + pos, abutter, bbutter); + + yule_filter (input_r + i, ctx->step_r + pos, ayule, byule); + butter_filter (ctx->step_r + pos, ctx->out_r + pos, abutter, bbutter); + } +} + +/* Clear filter buffer state and current RMS window. */ + +static void +reset_filters (RgAnalysisCtx * ctx) +{ + gint i; + + for (i = 0; i < MAX_ORDER; i++) { + + ctx->inprebuf_l[i] = 0.; + ctx->stepbuf_l[i] = 0.; + ctx->outbuf_l[i] = 0.; + + ctx->inprebuf_r[i] = 0.; + ctx->stepbuf_r[i] = 0.; + ctx->outbuf_r[i] = 0.; + } + + ctx->window_square_sum = 0.; + ctx->window_n_samples_done = 0; +} + +/* Accumulator functions. */ + +/* Add two accumulators in-place. The sum is defined as the result of + * the vector sum of the histogram array and the maximum value of the + * peak field. Thus "adding" the accumulators for all tracks yields + * the correct result for obtaining the album gain and peak. */ + +static void +accumulator_add (RgAnalysisAcc * acc, const RgAnalysisAcc * acc_other) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (acc->histogram); i++) + acc->histogram[i] += acc_other->histogram[i]; + + acc->peak = MAX (acc->peak, acc_other->peak); +} + +/* Reset an accumulator to zero. */ + +static void +accumulator_clear (RgAnalysisAcc * acc) +{ + memset (acc->histogram, 0, sizeof (acc->histogram)); + acc->peak = 0.; +} + +/* Obtain final analysis result from an accumulator. Returns TRUE on + * success, FALSE on error (if accumulator is still zero). */ + +static gboolean +accumulator_result (const RgAnalysisAcc * acc, gdouble * result_gain, + gdouble * result_peak) +{ + guint32 sum = 0; + guint32 upper; + guint i; + + for (i = 0; i < G_N_ELEMENTS (acc->histogram); i++) + sum += acc->histogram[i]; + + if (sum == 0) + /* All entries are 0: We got less than 50ms of data. */ + return FALSE; + + upper = (guint32) ceil (sum * (1. - (gdouble) (RMS_PERCENTILE / 100.))); + + for (i = G_N_ELEMENTS (acc->histogram); i--;) { + if (upper <= acc->histogram[i]) + break; + upper -= acc->histogram[i]; + } + + if (result_peak != NULL) + *result_peak = acc->peak; + if (result_gain != NULL) + *result_gain = PINK_REF - (gdouble) i / STEPS_PER_DB; + + return TRUE; +} + +/* Functions that operate on contexts, for external usage. */ + +/* Create a new context. Before it can be used, a sample rate must be + * configured using rg_analysis_set_sample_rate. */ + +RgAnalysisCtx * +rg_analysis_new (void) +{ + RgAnalysisCtx *ctx; + + ctx = g_new (RgAnalysisCtx, 1); + + ctx->inpre_l = ctx->inprebuf_l + MAX_ORDER; + ctx->step_l = ctx->stepbuf_l + MAX_ORDER; + ctx->out_l = ctx->outbuf_l + MAX_ORDER; + + ctx->inpre_r = ctx->inprebuf_r + MAX_ORDER; + ctx->step_r = ctx->stepbuf_r + MAX_ORDER; + ctx->out_r = ctx->outbuf_r + MAX_ORDER; + + ctx->sample_rate = 0; + + accumulator_clear (&ctx->track); + accumulator_clear (&ctx->album); + + return ctx; +} + +static void +reset_silence_detection (RgAnalysisCtx * ctx) +{ + ctx->buffer_timestamp = GST_CLOCK_TIME_NONE; + ctx->buffer_n_samples_done = 0; +} + +/* Adapt to given sample rate. Does nothing if already the current + * rate (returns TRUE then). Returns FALSE only if given sample rate + * is not supported. If the configured rate changes, the last + * unprocessed incomplete 50ms chunk of data is dropped because the + * filters are reset. */ + +gboolean +rg_analysis_set_sample_rate (RgAnalysisCtx * ctx, gint sample_rate) +{ + g_return_val_if_fail (ctx != NULL, FALSE); + + if (ctx->sample_rate == sample_rate) + return TRUE; + + switch (sample_rate) { + case 48000: + ctx->sample_rate_index = 0; + break; + case 44100: + ctx->sample_rate_index = 1; + break; + case 32000: + ctx->sample_rate_index = 2; + break; + case 24000: + ctx->sample_rate_index = 3; + break; + case 22050: + ctx->sample_rate_index = 4; + break; + case 16000: + ctx->sample_rate_index = 5; + break; + case 12000: + ctx->sample_rate_index = 6; + break; + case 11025: + ctx->sample_rate_index = 7; + break; + case 8000: + ctx->sample_rate_index = 8; + break; + default: + return FALSE; + } + + ctx->sample_rate = sample_rate; + /* The + 999 has the effect of ceil()ing: */ + ctx->window_n_samples = (guint) ((sample_rate * RMS_WINDOW_MSECS + 999) + / 1000); + + reset_filters (ctx); + reset_silence_detection (ctx); + + return TRUE; +} + +void +rg_analysis_init_silence_detection (RgAnalysisCtx * ctx, + void (*post_message) (gpointer analysis, GstClockTime timestamp, + GstClockTime duration, gdouble rglevel), gpointer analysis) +{ + ctx->post_message = post_message; + ctx->analysis = analysis; + reset_silence_detection (ctx); +} + +void +rg_analysis_start_buffer (RgAnalysisCtx * ctx, GstClockTime buffer_timestamp) +{ + ctx->buffer_timestamp = buffer_timestamp; + ctx->buffer_n_samples_done = 0; +} + +void +rg_analysis_destroy (RgAnalysisCtx * ctx) +{ + g_free (ctx); +} + +/* Entry points for analyzing sample data in common raw data formats. + * The stereo format functions expect interleaved frames. It is + * possible to pass data in different formats for the same context, + * there are no restrictions. All functions have the same signature; + * the depth argument for the float functions is not variable and must + * be given the value 32. */ + +void +rg_analysis_analyze_mono_float (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth) +{ + gfloat conv_samples[512]; + const gfloat *samples = (gfloat *) data; + guint n_samples = size / sizeof (gfloat); + gint i; + + g_return_if_fail (depth == 32); + g_return_if_fail (size % sizeof (gfloat) == 0); + + while (n_samples) { + gint n = MIN (n_samples, G_N_ELEMENTS (conv_samples)); + + n_samples -= n; + memcpy (conv_samples, samples, n * sizeof (gfloat)); + for (i = 0; i < n; i++) { + ctx->track.peak = MAX (ctx->track.peak, fabs (conv_samples[i])); + conv_samples[i] *= 32768.; + } + samples += n; + rg_analysis_analyze (ctx, conv_samples, NULL, n); + } +} + +void +rg_analysis_analyze_stereo_float (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth) +{ + gfloat conv_samples_l[256]; + gfloat conv_samples_r[256]; + const gfloat *samples = (gfloat *) data; + guint n_frames = size / (sizeof (gfloat) * 2); + gint i; + + g_return_if_fail (depth == 32); + g_return_if_fail (size % (sizeof (gfloat) * 2) == 0); + + while (n_frames) { + gint n = MIN (n_frames, G_N_ELEMENTS (conv_samples_l)); + + n_frames -= n; + for (i = 0; i < n; i++) { + gfloat old_sample; + + old_sample = samples[2 * i]; + ctx->track.peak = MAX (ctx->track.peak, fabs (old_sample)); + conv_samples_l[i] = old_sample * 32768.; + + old_sample = samples[2 * i + 1]; + ctx->track.peak = MAX (ctx->track.peak, fabs (old_sample)); + conv_samples_r[i] = old_sample * 32768.; + } + samples += 2 * n; + rg_analysis_analyze (ctx, conv_samples_l, conv_samples_r, n); + } +} + +void +rg_analysis_analyze_mono_int16 (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth) +{ + gfloat conv_samples[512]; + gint32 peak_sample = 0; + const gint16 *samples = (gint16 *) data; + guint n_samples = size / sizeof (gint16); + gint shift = sizeof (gint16) * 8 - depth; + gint i; + + g_return_if_fail (depth <= (sizeof (gint16) * 8)); + g_return_if_fail (size % sizeof (gint16) == 0); + + while (n_samples) { + gint n = MIN (n_samples, G_N_ELEMENTS (conv_samples)); + + n_samples -= n; + for (i = 0; i < n; i++) { + gint16 old_sample = samples[i] << shift; + + peak_sample = MAX (peak_sample, ABS ((gint32) old_sample)); + conv_samples[i] = (gfloat) old_sample; + } + samples += n; + rg_analysis_analyze (ctx, conv_samples, NULL, n); + } + ctx->track.peak = MAX (ctx->track.peak, + (gdouble) peak_sample / ((gdouble) (1u << 15))); +} + +void +rg_analysis_analyze_stereo_int16 (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth) +{ + gfloat conv_samples_l[256]; + gfloat conv_samples_r[256]; + gint32 peak_sample = 0; + const gint16 *samples = (gint16 *) data; + guint n_frames = size / (sizeof (gint16) * 2); + gint shift = sizeof (gint16) * 8 - depth; + gint i; + + g_return_if_fail (depth <= (sizeof (gint16) * 8)); + g_return_if_fail (size % (sizeof (gint16) * 2) == 0); + + while (n_frames) { + gint n = MIN (n_frames, G_N_ELEMENTS (conv_samples_l)); + + n_frames -= n; + for (i = 0; i < n; i++) { + gint16 old_sample; + + old_sample = samples[2 * i] << shift; + peak_sample = MAX (peak_sample, ABS ((gint32) old_sample)); + conv_samples_l[i] = (gfloat) old_sample; + + old_sample = samples[2 * i + 1] << shift; + peak_sample = MAX (peak_sample, ABS ((gint32) old_sample)); + conv_samples_r[i] = (gfloat) old_sample; + } + samples += 2 * n; + rg_analysis_analyze (ctx, conv_samples_l, conv_samples_r, n); + } + ctx->track.peak = MAX (ctx->track.peak, + (gdouble) peak_sample / ((gdouble) (1u << 15))); +} + +/* Analyze the given chunk of samples. The sample data is given in + * floating point format but should be scaled such that the values + * +/-32768.0 correspond to the -0dBFS reference amplitude. + * + * samples_l: Buffer with sample data for the left channel or of the + * mono channel. + * + * samples_r: Buffer with sample data for the right channel or NULL + * for mono. + * + * n_samples: Number of samples passed in each buffer. + */ + +void +rg_analysis_analyze (RgAnalysisCtx * ctx, const gfloat * samples_l, + const gfloat * samples_r, guint n_samples) +{ + const gfloat *input_l, *input_r; + guint n_samples_done; + gint i; + + g_return_if_fail (ctx != NULL); + g_return_if_fail (samples_l != NULL); + g_return_if_fail (ctx->sample_rate != 0); + + if (n_samples == 0) + return; + + if (samples_r == NULL) + /* Mono. */ + samples_r = samples_l; + + memcpy (ctx->inpre_l, samples_l, + MIN (n_samples, MAX_ORDER) * sizeof (gfloat)); + memcpy (ctx->inpre_r, samples_r, + MIN (n_samples, MAX_ORDER) * sizeof (gfloat)); + + n_samples_done = 0; + while (n_samples_done < n_samples) { + /* Limit number of samples to be processed in this iteration to + * the number needed to complete the next window: */ + guint n_samples_current = MIN (n_samples - n_samples_done, + ctx->window_n_samples - ctx->window_n_samples_done); + + if (n_samples_done < MAX_ORDER) { + input_l = ctx->inpre_l + n_samples_done; + input_r = ctx->inpre_r + n_samples_done; + n_samples_current = MIN (n_samples_current, MAX_ORDER - n_samples_done); + } else { + input_l = samples_l + n_samples_done; + input_r = samples_r + n_samples_done; + } + + apply_filters (ctx, input_l, input_r, n_samples_current); + + /* Update the square sum. */ + for (i = 0; i < n_samples_current; i++) + ctx->window_square_sum += ctx->out_l[ctx->window_n_samples_done + i] + * ctx->out_l[ctx->window_n_samples_done + i] + + ctx->out_r[ctx->window_n_samples_done + i] + * ctx->out_r[ctx->window_n_samples_done + i]; + + ctx->window_n_samples_done += n_samples_current; + ctx->buffer_n_samples_done += n_samples_current; + + g_return_if_fail (ctx->window_n_samples_done <= ctx->window_n_samples); + + if (ctx->window_n_samples_done == ctx->window_n_samples) { + /* Get the Root Mean Square (RMS) for this set of samples. */ + gdouble val = STEPS_PER_DB * 10. * log10 (ctx->window_square_sum / + ctx->window_n_samples * 0.5 + 1.e-37); + gint ival = CLAMP ((gint) val, 0, + (gint) G_N_ELEMENTS (ctx->track.histogram) - 1); + /* Compute the per-window gain */ + const gdouble gain = PINK_REF - (gdouble) ival / STEPS_PER_DB; + const GstClockTime timestamp = ctx->buffer_timestamp + + gst_util_uint64_scale_int_ceil (GST_SECOND, + ctx->buffer_n_samples_done, + ctx->sample_rate) + - RMS_WINDOW_MSECS * GST_MSECOND; + + ctx->post_message (ctx->analysis, timestamp, + RMS_WINDOW_MSECS * GST_MSECOND, -gain); + + + ctx->track.histogram[ival]++; + ctx->window_square_sum = 0.; + ctx->window_n_samples_done = 0; + + /* No need for memmove here, the areas never overlap: Even for + * the smallest sample rate, the number of samples needed for + * the window is greater than MAX_ORDER. */ + + memcpy (ctx->stepbuf_l, ctx->stepbuf_l + ctx->window_n_samples, + MAX_ORDER * sizeof (gfloat)); + memcpy (ctx->outbuf_l, ctx->outbuf_l + ctx->window_n_samples, + MAX_ORDER * sizeof (gfloat)); + + memcpy (ctx->stepbuf_r, ctx->stepbuf_r + ctx->window_n_samples, + MAX_ORDER * sizeof (gfloat)); + memcpy (ctx->outbuf_r, ctx->outbuf_r + ctx->window_n_samples, + MAX_ORDER * sizeof (gfloat)); + } + + n_samples_done += n_samples_current; + } + + if (n_samples >= MAX_ORDER) { + + memcpy (ctx->inprebuf_l, samples_l + n_samples - MAX_ORDER, + MAX_ORDER * sizeof (gfloat)); + + memcpy (ctx->inprebuf_r, samples_r + n_samples - MAX_ORDER, + MAX_ORDER * sizeof (gfloat)); + + } else { + + memmove (ctx->inprebuf_l, ctx->inprebuf_l + n_samples, + (MAX_ORDER - n_samples) * sizeof (gfloat)); + memcpy (ctx->inprebuf_l + MAX_ORDER - n_samples, samples_l, + n_samples * sizeof (gfloat)); + + memmove (ctx->inprebuf_r, ctx->inprebuf_r + n_samples, + (MAX_ORDER - n_samples) * sizeof (gfloat)); + memcpy (ctx->inprebuf_r + MAX_ORDER - n_samples, samples_r, + n_samples * sizeof (gfloat)); + + } +} + +/* Obtain track gain and peak. Returns TRUE on success. Can fail if + * not enough samples have been processed. Updates album accumulator. + * Resets track accumulator. */ + +gboolean +rg_analysis_track_result (RgAnalysisCtx * ctx, gdouble * gain, gdouble * peak) +{ + gboolean result; + + g_return_val_if_fail (ctx != NULL, FALSE); + + accumulator_add (&ctx->album, &ctx->track); + result = accumulator_result (&ctx->track, gain, peak); + accumulator_clear (&ctx->track); + + reset_filters (ctx); + reset_silence_detection (ctx); + + return result; +} + +/* Obtain album gain and peak. Returns TRUE on success. Can fail if + * not enough samples have been processed. Resets album + * accumulator. */ + +gboolean +rg_analysis_album_result (RgAnalysisCtx * ctx, gdouble * gain, gdouble * peak) +{ + gboolean result; + + g_return_val_if_fail (ctx != NULL, FALSE); + + result = accumulator_result (&ctx->album, gain, peak); + accumulator_clear (&ctx->album); + + return result; +} + +void +rg_analysis_reset_album (RgAnalysisCtx * ctx) +{ + accumulator_clear (&ctx->album); +} + +/* Reset internal buffers as well as track and album accumulators. + * Configured sample rate is kept intact. */ + +void +rg_analysis_reset (RgAnalysisCtx * ctx) +{ + g_return_if_fail (ctx != NULL); + + reset_filters (ctx); + accumulator_clear (&ctx->track); + accumulator_clear (&ctx->album); + reset_silence_detection (ctx); +} diff --git a/gst/replaygain/rganalysis.h b/gst/replaygain/rganalysis.h new file mode 100644 index 0000000..f57ad0a --- /dev/null +++ b/gst/replaygain/rganalysis.h @@ -0,0 +1,63 @@ +/* GStreamer ReplayGain analysis + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * Copyright (C) 2001 David Robinson <David@Robinson.org> + * Glen Sawyer <glensawyer@hotmail.com> + * + * rganalysis.h: Analyze raw audio data in accordance with ReplayGain + * + * This library 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. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __RG_ANALYSIS_H__ +#define __RG_ANALYSIS_H__ + +#include <glib.h> +#include <gst/gst.h> + +G_BEGIN_DECLS + +typedef struct _RgAnalysisCtx RgAnalysisCtx; + +RgAnalysisCtx *rg_analysis_new (void); +gboolean rg_analysis_set_sample_rate (RgAnalysisCtx * ctx, gint sample_rate); +void rg_analysis_analyze_mono_float (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth); +void rg_analysis_analyze_stereo_float (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth); +void rg_analysis_analyze_mono_int16 (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth); +void rg_analysis_analyze_stereo_int16 (RgAnalysisCtx * ctx, gconstpointer data, + gsize size, guint depth); +void rg_analysis_analyze (RgAnalysisCtx * ctx, const gfloat * samples_l, + const gfloat * samples_r, guint n_samples); +gboolean rg_analysis_track_result (RgAnalysisCtx * ctx, gdouble * gain, + gdouble * peak); +gboolean rg_analysis_album_result (RgAnalysisCtx * ctx, gdouble * gain, + gdouble * peak); +void rg_analysis_init_silence_detection ( + RgAnalysisCtx * ctx, + void (*post_message) (gpointer analysis, GstClockTime timestamp, GstClockTime duration, gdouble rglevel), + gpointer analysis); +void rg_analysis_start_buffer (RgAnalysisCtx * ctx, + GstClockTime buffer_timestamp); +void rg_analysis_reset_album (RgAnalysisCtx * ctx); +void rg_analysis_reset (RgAnalysisCtx * ctx); +void rg_analysis_destroy (RgAnalysisCtx * ctx); + +G_END_DECLS + +#endif /* __RG_ANALYSIS_H__ */ |