Remove interleave and replaygain plugins that have moved to -good

Original commit message from CVS:
* docs/plugins/Makefile.am:
* docs/plugins/gst-plugins-bad-plugins-docs.sgml:
* docs/plugins/gst-plugins-bad-plugins-sections.txt:
* docs/plugins/gst-plugins-bad-plugins.args:
* docs/plugins/gst-plugins-bad-plugins.hierarchy:
* docs/plugins/gst-plugins-bad-plugins.interfaces:
* docs/plugins/gst-plugins-bad-plugins.prerequisites:
* docs/plugins/inspect/plugin-interleave.xml:
* docs/plugins/inspect/plugin-replaygain.xml:
* gst/interleave/Makefile.am:
* gst/interleave/deinterleave.c:
* gst/interleave/deinterleave.h:
* gst/interleave/interleave.c:
* gst/interleave/interleave.h:
* gst/interleave/plugin.c:
* gst/interleave/plugin.h:
* gst/replaygain/Makefile.am:
* gst/replaygain/gstrganalysis.c:
* gst/replaygain/gstrganalysis.h:
* gst/replaygain/gstrglimiter.c:
* gst/replaygain/gstrglimiter.h:
* gst/replaygain/gstrgvolume.c:
* gst/replaygain/gstrgvolume.h:
* gst/replaygain/replaygain.c:
* gst/replaygain/replaygain.h:
* gst/replaygain/rganalysis.c:
* gst/replaygain/rganalysis.h:
* tests/check/Makefile.am:
* tests/check/elements/deinterleave.c:
* tests/check/elements/interleave.c:
* tests/check/elements/rganalysis.c:
* tests/check/elements/rglimiter.c:
* tests/check/elements/rgvolume.c:
Remove interleave and replaygain plugins that have moved to -good
This commit is contained in:
Jan Schmidt 2008-07-19 00:58:49 +00:00
parent 26cb95316c
commit e985585a4e
31 changed files with 40 additions and 9573 deletions

View file

@ -1,3 +1,40 @@
2008-07-19 Jan Schmidt <jan.schmidt@sun.com>
* docs/plugins/Makefile.am:
* docs/plugins/gst-plugins-bad-plugins-docs.sgml:
* docs/plugins/gst-plugins-bad-plugins-sections.txt:
* docs/plugins/gst-plugins-bad-plugins.args:
* docs/plugins/gst-plugins-bad-plugins.hierarchy:
* docs/plugins/gst-plugins-bad-plugins.interfaces:
* docs/plugins/gst-plugins-bad-plugins.prerequisites:
* docs/plugins/inspect/plugin-interleave.xml:
* docs/plugins/inspect/plugin-replaygain.xml:
* gst/interleave/Makefile.am:
* gst/interleave/deinterleave.c:
* gst/interleave/deinterleave.h:
* gst/interleave/interleave.c:
* gst/interleave/interleave.h:
* gst/interleave/plugin.c:
* gst/interleave/plugin.h:
* gst/replaygain/Makefile.am:
* gst/replaygain/gstrganalysis.c:
* gst/replaygain/gstrganalysis.h:
* gst/replaygain/gstrglimiter.c:
* gst/replaygain/gstrglimiter.h:
* gst/replaygain/gstrgvolume.c:
* gst/replaygain/gstrgvolume.h:
* gst/replaygain/replaygain.c:
* gst/replaygain/replaygain.h:
* gst/replaygain/rganalysis.c:
* gst/replaygain/rganalysis.h:
* tests/check/Makefile.am:
* tests/check/elements/deinterleave.c:
* tests/check/elements/interleave.c:
* tests/check/elements/rganalysis.c:
* tests/check/elements/rglimiter.c:
* tests/check/elements/rgvolume.c:
Remove interleave and replaygain plugins that have moved to -good
2008-07-18 Sebastian Dröge <sebastian.droege@collabora.co.uk> 2008-07-18 Sebastian Dröge <sebastian.droege@collabora.co.uk>
* configure.ac: * configure.ac:

View file

@ -115,15 +115,10 @@ EXTRA_HFILES = \
$(top_srcdir)/gst/deinterlace/gstdeinterlace.h \ $(top_srcdir)/gst/deinterlace/gstdeinterlace.h \
$(top_srcdir)/gst/dvdspu/gstdvdspu.h \ $(top_srcdir)/gst/dvdspu/gstdvdspu.h \
$(top_srcdir)/gst/festival/gstfestival.h \ $(top_srcdir)/gst/festival/gstfestival.h \
$(top_srcdir)/gst/interleave/interleave.h \
$(top_srcdir)/gst/interleave/deinterleave.h \
$(top_srcdir)/gst/modplug/gstmodplug.h \ $(top_srcdir)/gst/modplug/gstmodplug.h \
$(top_srcdir)/gst/nuvdemux/gstnuvdemux.h \ $(top_srcdir)/gst/nuvdemux/gstnuvdemux.h \
$(top_srcdir)/gst/rawparse/gstaudioparse.h \ $(top_srcdir)/gst/rawparse/gstaudioparse.h \
$(top_srcdir)/gst/rawparse/gstvideoparse.h \ $(top_srcdir)/gst/rawparse/gstvideoparse.h \
$(top_srcdir)/gst/replaygain/gstrganalysis.h \
$(top_srcdir)/gst/replaygain/gstrglimiter.h \
$(top_srcdir)/gst/replaygain/gstrgvolume.h \
$(top_srcdir)/gst/rtpmanager/gstrtpbin.h \ $(top_srcdir)/gst/rtpmanager/gstrtpbin.h \
$(top_srcdir)/gst/rtpmanager/gstrtpclient.h \ $(top_srcdir)/gst/rtpmanager/gstrtpclient.h \
$(top_srcdir)/gst/rtpmanager/gstrtpjitterbuffer.h \ $(top_srcdir)/gst/rtpmanager/gstrtpjitterbuffer.h \

View file

@ -17,7 +17,6 @@
<xi:include href="xml/element-amrwbparse.xml" /> <xi:include href="xml/element-amrwbparse.xml" />
<xi:include href="xml/element-audioparse.xml" /> <xi:include href="xml/element-audioparse.xml" />
<xi:include href="xml/element-deinterlace.xml" /> <xi:include href="xml/element-deinterlace.xml" />
<xi:include href="xml/element-deinterleave.xml" />
<xi:include href="xml/element-dfbvideosink.xml" /> <xi:include href="xml/element-dfbvideosink.xml" />
<xi:include href="xml/element-dvbsrc.xml" /> <xi:include href="xml/element-dvbsrc.xml" />
<xi:include href="xml/element-dvdspu.xml" /> <xi:include href="xml/element-dvdspu.xml" />
@ -29,7 +28,6 @@
<xi:include href="xml/element-gstrtpsession.xml" /> <xi:include href="xml/element-gstrtpsession.xml" />
<xi:include href="xml/element-gstrtpssrcdemux.xml" /> <xi:include href="xml/element-gstrtpssrcdemux.xml" />
<xi:include href="xml/element-input-selector.xml" /> <xi:include href="xml/element-input-selector.xml" />
<xi:include href="xml/element-interleave.xml" />
<xi:include href="xml/element-ivorbisdec.xml" /> <xi:include href="xml/element-ivorbisdec.xml" />
<xi:include href="xml/element-jackaudiosink.xml" /> <xi:include href="xml/element-jackaudiosink.xml" />
<xi:include href="xml/element-metadatademux.xml" /> <xi:include href="xml/element-metadatademux.xml" />
@ -39,9 +37,6 @@
<xi:include href="xml/element-mythtvsrc.xml" /> <xi:include href="xml/element-mythtvsrc.xml" />
<xi:include href="xml/element-nuvdemux.xml" /> <xi:include href="xml/element-nuvdemux.xml" />
<xi:include href="xml/element-output-selector.xml" /> <xi:include href="xml/element-output-selector.xml" />
<xi:include href="xml/element-rganalysis.xml" />
<xi:include href="xml/element-rglimiter.xml" />
<xi:include href="xml/element-rgvolume.xml" />
<xi:include href="xml/element-sdlaudiosink.xml" /> <xi:include href="xml/element-sdlaudiosink.xml" />
<xi:include href="xml/element-sdlvideosink.xml" /> <xi:include href="xml/element-sdlvideosink.xml" />
<xi:include href="xml/element-sdpdemux.xml" /> <xi:include href="xml/element-sdpdemux.xml" />
@ -84,7 +79,6 @@
<xi:include href="xml/plugin-gstinterlace.xml" /> <xi:include href="xml/plugin-gstinterlace.xml" />
<xi:include href="xml/plugin-gstrtpmanager.xml" /> <xi:include href="xml/plugin-gstrtpmanager.xml" />
<xi:include href="xml/plugin-h264parse.xml" /> <xi:include href="xml/plugin-h264parse.xml" />
<xi:include href="xml/plugin-interleave.xml" />
<xi:include href="xml/plugin-jack.xml" /> <xi:include href="xml/plugin-jack.xml" />
<xi:include href="xml/plugin-ladspa.xml" /> <xi:include href="xml/plugin-ladspa.xml" />
<xi:include href="xml/plugin-metadata.xml" /> <xi:include href="xml/plugin-metadata.xml" />
@ -103,7 +97,6 @@
<xi:include href="xml/plugin-nuvdemux.xml" /> <xi:include href="xml/plugin-nuvdemux.xml" />
<xi:include href="xml/plugin-rawparse.xml" /> <xi:include href="xml/plugin-rawparse.xml" />
<xi:include href="xml/plugin-real.xml" /> <xi:include href="xml/plugin-real.xml" />
<xi:include href="xml/plugin-replaygain.xml" />
<xi:include href="xml/plugin-rfbsrc.xml" /> <xi:include href="xml/plugin-rfbsrc.xml" />
<xi:include href="xml/plugin-sdl.xml" /> <xi:include href="xml/plugin-sdl.xml" />
<xi:include href="xml/plugin-sdp.xml" /> <xi:include href="xml/plugin-sdp.xml" />

View file

@ -168,6 +168,7 @@ FESTIVAL_DEFAULT_SERVER_PORT
FESTIVAL_DEFAULT_TEXT_MODE FESTIVAL_DEFAULT_TEXT_MODE
</SECTION> </SECTION>
<SECTION>
<FILE>element-input-selector</FILE> <FILE>element-input-selector</FILE>
<TITLE>input-selector</TITLE> <TITLE>input-selector</TITLE>
GstInputSelector GstInputSelector
@ -201,38 +202,6 @@ GST_TYPE_IVORBIS_DEC
gst_ivorbis_dec_get_type gst_ivorbis_dec_get_type
</SECTION> </SECTION>
<SECTION>
<FILE>element-interleave</FILE>
<TITLE>interleave</TITLE>
GstInterleave
<SUBSECTION Standard>
GST_INTERLEAVE
GST_INTERLEAVE_CLASS
GST_INTERLEAVE_GET_CLASS
GST_IS_INTERLEAVE
GST_IS_INTERLEAVE_CLASS
GST_TYPE_INTERLEAVE
GstInterleaveClass
GstInterleaveFunc
gst_interleave_get_type
</SECTION>
<SECTION>
<FILE>element-deinterleave</FILE>
<TITLE>deinterleave</TITLE>
GstDeinterleave
<SUBSECTION Standard>
GST_DEINTERLEAVE
GST_DEINTERLEAVE_CLASS
GST_DEINTERLEAVE_GET_CLASS
GST_IS_DEINTERLEAVE
GST_IS_DEINTERLEAVE_CLASS
GST_TYPE_DEINTERLEAVE
GstDeinterleaveClass
GstDeinterleaveFunc
gst_deinterleave_get_type
</SECTION>
<SECTION> <SECTION>
<FILE>element-jackaudiosink</FILE> <FILE>element-jackaudiosink</FILE>
GstJackAudioSink GstJackAudioSink
@ -380,48 +349,6 @@ GST_TYPE_OUTPUT_SELECTOR
gst_output_selector_get_type gst_output_selector_get_type
</SECTION> </SECTION>
<SECTION>
<FILE>element-rganalysis</FILE>
<TITLE>rganalysis</TITLE>
GstRgAnalysis
<SUBSECTION Standard>
GstRgAnalysisClass
GST_RG_ANALYSIS
GST_RG_ANALYSIS_CLASS
GST_IS_RG_ANALYSIS
GST_IS_RG_ANALYSIS_CLASS
GST_TYPE_RG_ANALYSIS
gst_rg_analysis_get_type
</SECTION>
<SECTION>
<FILE>element-rglimiter</FILE>
<TITLE>rglimiter</TITLE>
GstRgLimiter
<SUBSECTION Standard>
GstRgLimiterClass
GST_RG_LIMITER
GST_RG_LIMITER_CLASS
GST_IS_RG_LIMITER
GST_IS_RG_LIMITER_CLASS
GST_TYPE_RG_LIMITER
gst_rg_limiter_get_type
</SECTION>
<SECTION>
<FILE>element-rgvolume</FILE>
<TITLE>rgvolume</TITLE>
GstRgVolume
<SUBSECTION Standard>
GstRgVolumeClass
GST_RG_VOLUME
GST_RG_VOLUME_CLASS
GST_IS_RG_VOLUME
GST_TYPE_RG_VOLUME
GST_IS_RG_VOLUME_CLASS
gst_rg_volume_get_type
</SECTION>
<SECTION> <SECTION>
<FILE>element-gstrtpbin</FILE> <FILE>element-gstrtpbin</FILE>
<TITLE>gstrtpbin</TITLE> <TITLE>gstrtpbin</TITLE>

View file

@ -2,4 +2,6 @@ GstChildProxy GstObject
GstTagSetter GstObject GstElement GstTagSetter GstObject GstElement
GstImplementsInterface GstObject GstElement GstImplementsInterface GstObject GstElement
GstXOverlay GstObject GstImplementsInterface GstElement GstXOverlay GstObject GstImplementsInterface GstElement
GstTagSetter GstObject GstElement
GstColorBalance GstObject GstImplementsInterface GstElement
GstMixer GstObject GstImplementsInterface GstElement GstMixer GstObject GstImplementsInterface GstElement

View file

@ -1,55 +0,0 @@
<plugin>
<name>interleave</name>
<description>Audio interleaver/deinterleaver</description>
<filename>../../gst/interleave/.libs/libgstinterleave.so</filename>
<basename>libgstinterleave.so</basename>
<version>0.10.7.1</version>
<license>LGPL</license>
<source>gst-plugins-bad</source>
<package>GStreamer Bad Plug-ins CVS/prerelease</package>
<origin>http://gstreamer.freedesktop.org</origin>
<elements>
<element>
<name>deinterleave</name>
<longname>Audio deinterleaver</longname>
<class>Filter/Converter/Audio</class>
<description>Splits one interleaved multichannel audio stream into many mono audio streams</description>
<author>Andy Wingo &lt;wingo at pobox.com&gt;, Iain &lt;iain@prettypeople.org&gt;, Sebastian Dröge &lt;slomo@circular-chaos.org&gt;</author>
<pads>
<caps>
<name>sink</name>
<direction>sink</direction>
<presence>always</presence>
<details>audio/x-raw-int, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int){ 1234, 4321 }, width=(int){ 8, 16, 24, 32 }, depth=(int)[ 1, 32 ], signed=(boolean){ true, false }; audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int){ 1234, 4321 }, width=(int){ 32, 64 }</details>
</caps>
<caps>
<name>src%d</name>
<direction>source</direction>
<presence>sometimes</presence>
<details>audio/x-raw-int, rate=(int)[ 1, 2147483647 ], channels=(int)1, endianness=(int){ 1234, 4321 }, width=(int){ 8, 16, 24, 32 }, depth=(int)[ 1, 32 ], signed=(boolean){ true, false }; audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)1, endianness=(int){ 1234, 4321 }, width=(int){ 32, 64 }</details>
</caps>
</pads>
</element>
<element>
<name>interleave</name>
<longname>Audio interleaver</longname>
<class>Filter/Converter/Audio</class>
<description>Folds many mono channels into one interleaved audio stream</description>
<author>Andy Wingo &lt;wingo at pobox.com&gt;, Sebastian Dröge &lt;slomo@circular-chaos.org&gt;</author>
<pads>
<caps>
<name>sink%d</name>
<direction>sink</direction>
<presence>request</presence>
<details>audio/x-raw-int, rate=(int)[ 1, 2147483647 ], channels=(int)1, endianness=(int){ 1234, 4321 }, width=(int){ 8, 16, 24, 32 }, depth=(int)[ 1, 32 ], signed=(boolean)true; audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)1, endianness=(int){ 1234, 4321 }, width=(int){ 32, 64 }</details>
</caps>
<caps>
<name>src</name>
<direction>source</direction>
<presence>always</presence>
<details>audio/x-raw-int, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int){ 1234, 4321 }, width=(int){ 8, 16, 24, 32 }, depth=(int)[ 1, 32 ], signed=(boolean)true; audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int){ 1234, 4321 }, width=(int){ 32, 64 }</details>
</caps>
</pads>
</element>
</elements>
</plugin>

View file

@ -1,76 +0,0 @@
<plugin>
<name>replaygain</name>
<description>ReplayGain volume normalization</description>
<filename>../../gst/replaygain/.libs/libgstreplaygain.so</filename>
<basename>libgstreplaygain.so</basename>
<version>0.10.7.1</version>
<license>LGPL</license>
<source>gst-plugins-bad</source>
<package>GStreamer Bad Plug-ins CVS/prerelease</package>
<origin>http://gstreamer.freedesktop.org</origin>
<elements>
<element>
<name>rganalysis</name>
<longname>ReplayGain analysis</longname>
<class>Filter/Analyzer/Audio</class>
<description>Perform the ReplayGain analysis</description>
<author>René Stadler &lt;mail@renestadler.de&gt;</author>
<pads>
<caps>
<name>src</name>
<direction>source</direction>
<presence>always</presence>
<details>audio/x-raw-float, width=(int)32, endianness=(int)1234, channels=(int){ 1, 2 }, rate=(int){ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }; audio/x-raw-int, width=(int)16, depth=(int)[ 1, 16 ], signed=(boolean)true, endianness=(int)1234, channels=(int){ 1, 2 }, rate=(int){ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }</details>
</caps>
<caps>
<name>sink</name>
<direction>sink</direction>
<presence>always</presence>
<details>audio/x-raw-float, width=(int)32, endianness=(int)1234, channels=(int){ 1, 2 }, rate=(int){ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }; audio/x-raw-int, width=(int)16, depth=(int)[ 1, 16 ], signed=(boolean)true, endianness=(int)1234, channels=(int){ 1, 2 }, rate=(int){ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }</details>
</caps>
</pads>
</element>
<element>
<name>rglimiter</name>
<longname>ReplayGain limiter</longname>
<class>Filter/Effect/Audio</class>
<description>Apply signal compression to raw audio data</description>
<author>René Stadler &lt;mail@renestadler.de&gt;</author>
<pads>
<caps>
<name>src</name>
<direction>source</direction>
<presence>always</presence>
<details>audio/x-raw-float, width=(int)32, channels=(int)[ 1, 2147483647 ], rate=(int)[ 1, 2147483647 ], endianness=(int)1234</details>
</caps>
<caps>
<name>sink</name>
<direction>sink</direction>
<presence>always</presence>
<details>audio/x-raw-float, width=(int)32, channels=(int)[ 1, 2147483647 ], rate=(int)[ 1, 2147483647 ], endianness=(int)1234</details>
</caps>
</pads>
</element>
<element>
<name>rgvolume</name>
<longname>ReplayGain volume</longname>
<class>Filter/Effect/Audio</class>
<description>Apply ReplayGain volume adjustment</description>
<author>René Stadler &lt;mail@renestadler.de&gt;</author>
<pads>
<caps>
<name>src</name>
<direction>source</direction>
<presence>always</presence>
<details>audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int)1234, width=(int)32; audio/x-raw-int, channels=(int)[ 1, 2147483647 ], rate=(int)[ 1, 2147483647 ], endianness=(int)1234, width=(int)16, depth=(int)16, signed=(boolean)true</details>
</caps>
<caps>
<name>sink</name>
<direction>sink</direction>
<presence>always</presence>
<details>audio/x-raw-float, rate=(int)[ 1, 2147483647 ], channels=(int)[ 1, 2147483647 ], endianness=(int)1234, width=(int)32; audio/x-raw-int, channels=(int)[ 1, 2147483647 ], rate=(int)[ 1, 2147483647 ], endianness=(int)1234, width=(int)16, depth=(int)16, signed=(boolean)true</details>
</caps>
</pads>
</element>
</elements>
</plugin>

View file

@ -1,9 +0,0 @@
plugin_LTLIBRARIES = libgstinterleave.la
libgstinterleave_la_SOURCES = plugin.c interleave.c deinterleave.c
libgstinterleave_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS)
libgstinterleave_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR)
libgstinterleave_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
noinst_HEADERS = plugin.h interleave.h deinterleave.h

View file

@ -1,889 +0,0 @@
/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
* 2005 Wim Taymans <wim@fluendo.com>
* 2007 Andy Wingo <wingo at pobox.com>
* 2008 Sebastian Dröge <slomo@circular-chaos.org>
*
* deinterleave.c: deinterleave samples
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* TODO:
* - handle changes in number of channels
* - handle changes in channel positions
* - better capsnego by using a buffer alloc function
* and passing downstream caps changes upstream there
*/
/**
* SECTION:element-deinterleave
* @see_also: interleave
*
* Splits one interleaved multichannel audio stream into many mono audio streams.
*
* This element handles all raw audio formats and supports changing the input caps as long as
* all downstream elements can handle the new caps and the number of channels and the channel
* positions stay the same. This restriction will be removed in later versions by adding or
* removing some source pads as required.
*
* In most cases a queue and an audioconvert element should be added after each source pad
* before further processing of the audio data.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-0.10 filesrc location=/path/to/file.mp3 ! decodebin ! audioconvert ! "audio/x-raw-int,channels=2 ! deinterleave name=d d.src0 ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=channel1.ogg d.src1 ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=channel2.ogg
* ]| Decodes an MP3 file and encodes the left and right channel into separate
* Ogg Vorbis files.
* |[
* gst-launch-0.10 filesrc location=file.mp3 ! decodebin ! audioconvert ! "audio/x-raw-int,channels=2" ! deinterleave name=d interleave name=i ! audioconvert ! wavenc ! filesink location=test.wav d.src0 ! queue ! audioconvert ! i.sink1 d.src1 ! queue ! audioconvert ! i.sink0
* ]| Decodes and deinterleaves a Stereo MP3 file into separate channels and
* then interleaves the channels again to a WAV file with the channel with the
* channels exchanged.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/gst.h>
#include <string.h>
#include "deinterleave.h"
GST_DEBUG_CATEGORY_STATIC (gst_deinterleave_debug);
#define GST_CAT_DEFAULT gst_deinterleave_debug
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src%d",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("audio/x-raw-int, "
"rate = (int) [ 1, MAX ], "
"channels = (int) 1, "
"endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, "
"width = (int) { 8, 16, 24, 32 }, "
"depth = (int) [ 1, 32 ], "
"signed = (boolean) { true, false }; "
"audio/x-raw-float, "
"rate = (int) [ 1, MAX ], "
"channels = (int) 1, "
"endianness = (int) { LITTLE_ENDIAN , BIG_ENDIAN }, "
"width = (int) { 32, 64 }")
);
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, MAX ], "
"endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, "
"width = (int) { 8, 16, 24, 32 }, "
"depth = (int) [ 1, 32 ], "
"signed = (boolean) { true, false }; "
"audio/x-raw-float, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, MAX ], "
"endianness = (int) { LITTLE_ENDIAN , BIG_ENDIAN }, "
"width = (int) { 32, 64 }")
);
#define MAKE_FUNC(type) \
static void deinterleave_##type (guint##type *out, guint##type *in, \
guint stride, guint nframes) \
{ \
gint i; \
\
for (i = 0; i < nframes; i++) { \
out[i] = *in; \
in += stride; \
} \
}
MAKE_FUNC (8);
MAKE_FUNC (16);
MAKE_FUNC (32);
MAKE_FUNC (64);
static void
deinterleave_24 (guint8 * out, guint8 * in, guint stride, guint nframes)
{
gint i;
for (i = 0; i < nframes; i++) {
memcpy (out, in, 3);
out += 3;
in += stride * 3;
}
}
GST_BOILERPLATE (GstDeinterleave, gst_deinterleave, GstElement,
GST_TYPE_ELEMENT);
enum
{
PROP_0,
PROP_KEEP_POSITIONS
};
static GstFlowReturn gst_deinterleave_chain (GstPad * pad, GstBuffer * buffer);
static gboolean gst_deinterleave_sink_setcaps (GstPad * pad, GstCaps * caps);
static GstCaps *gst_deinterleave_sink_getcaps (GstPad * pad);
static gboolean gst_deinterleave_sink_activate_push (GstPad * pad,
gboolean active);
static gboolean gst_deinterleave_sink_event (GstPad * pad, GstEvent * event);
static gboolean gst_deinterleave_src_query (GstPad * pad, GstQuery * query);
static void gst_deinterleave_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_deinterleave_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void
gst_deinterleave_finalize (GObject * obj)
{
GstDeinterleave *self = GST_DEINTERLEAVE (obj);
if (self->pos) {
g_free (self->pos);
self->pos = NULL;
}
if (self->pending_events) {
g_list_foreach (self->pending_events, (GFunc) gst_mini_object_unref, NULL);
g_list_free (self->pending_events);
self->pending_events = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
gst_deinterleave_base_init (gpointer g_class)
{
GstElementClass *gstelement_class = (GstElementClass *) g_class;
gst_element_class_set_details_simple (gstelement_class, "Audio deinterleaver",
"Filter/Converter/Audio",
"Splits one interleaved multichannel audio stream into many mono audio streams",
"Andy Wingo <wingo at pobox.com>, "
"Iain <iain@prettypeople.org>, "
"Sebastian Dröge <slomo@circular-chaos.org>");
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&sink_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&src_template));
}
static void
gst_deinterleave_class_init (GstDeinterleaveClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (gst_deinterleave_debug, "deinterleave", 0,
"deinterleave element");
gobject_class->finalize = gst_deinterleave_finalize;
gobject_class->set_property = gst_deinterleave_set_property;
gobject_class->get_property = gst_deinterleave_get_property;
/**
* GstDeinterleave:keep-positions
*
* Keep positions: When enable the caps on the output buffers will
* contain the original channel positions. This can be used to correctly
* interleave the output again later but can also lead to unwanted effects
* if the output should be handled as Mono.
*
*/
g_object_class_install_property (gobject_class, PROP_KEEP_POSITIONS,
g_param_spec_boolean ("keep-positions", "Keep positions",
"Keep the original channel positions on the output buffers",
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_deinterleave_init (GstDeinterleave * self, GstDeinterleaveClass * klass)
{
self->channels = 0;
self->pos = NULL;
self->keep_positions = FALSE;
self->width = 0;
self->func = NULL;
/* Add sink pad */
self->sink = gst_pad_new_from_static_template (&sink_template, "sink");
gst_pad_set_chain_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_chain));
gst_pad_set_setcaps_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_setcaps));
gst_pad_set_getcaps_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_getcaps));
gst_pad_set_activatepush_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_activate_push));
gst_pad_set_event_function (self->sink,
GST_DEBUG_FUNCPTR (gst_deinterleave_sink_event));
gst_element_add_pad (GST_ELEMENT (self), self->sink);
}
static void
gst_deinterleave_add_new_pads (GstDeinterleave * self, GstCaps * caps)
{
GstPad *pad;
guint i;
for (i = 0; i < self->channels; i++) {
gchar *name = g_strdup_printf ("src%d", i);
GstCaps *srccaps;
GstStructure *s;
pad = gst_pad_new_from_static_template (&src_template, name);
g_free (name);
/* Set channel position if we know it */
if (self->keep_positions) {
GstAudioChannelPosition pos[1] = { GST_AUDIO_CHANNEL_POSITION_NONE };
srccaps = gst_caps_copy (caps);
s = gst_caps_get_structure (srccaps, 0);
if (self->pos)
gst_audio_set_channel_positions (s, &self->pos[i]);
else
gst_audio_set_channel_positions (s, pos);
} else {
srccaps = caps;
}
gst_pad_set_caps (pad, srccaps);
gst_pad_use_fixed_caps (pad);
gst_pad_set_query_function (pad,
GST_DEBUG_FUNCPTR (gst_deinterleave_src_query));
gst_pad_set_active (pad, TRUE);
gst_element_add_pad (GST_ELEMENT (self), pad);
self->srcpads = g_list_prepend (self->srcpads, gst_object_ref (pad));
if (self->keep_positions)
gst_caps_unref (srccaps);
}
gst_element_no_more_pads (GST_ELEMENT (self));
self->srcpads = g_list_reverse (self->srcpads);
}
static void
gst_deinterleave_set_pads_caps (GstDeinterleave * self, GstCaps * caps)
{
GList *l;
GstStructure *s;
gint i;
for (l = self->srcpads, i = 0; l; l = l->next, i++) {
GstPad *pad = GST_PAD (l->data);
GstCaps *srccaps;
/* Set channel position if we know it */
if (self->keep_positions) {
GstAudioChannelPosition pos[1] = { GST_AUDIO_CHANNEL_POSITION_NONE };
srccaps = gst_caps_copy (caps);
s = gst_caps_get_structure (srccaps, 0);
if (self->pos)
gst_audio_set_channel_positions (s, &self->pos[i]);
else
gst_audio_set_channel_positions (s, pos);
} else {
srccaps = caps;
}
gst_pad_set_caps (pad, srccaps);
if (self->keep_positions)
gst_caps_unref (srccaps);
}
}
static void
gst_deinterleave_remove_pads (GstDeinterleave * self)
{
GList *l;
GST_INFO_OBJECT (self, "removing pads");
for (l = self->srcpads; l; l = l->next) {
GstPad *pad = GST_PAD (l->data);
gst_element_remove_pad (GST_ELEMENT_CAST (self), pad);
gst_object_unref (pad);
}
g_list_free (self->srcpads);
self->srcpads = NULL;
gst_pad_set_caps (self->sink, NULL);
gst_caps_replace (&self->sinkcaps, NULL);
}
static gboolean
gst_deinterleave_set_process_function (GstDeinterleave * self, GstCaps * caps)
{
GstStructure *s;
s = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (s, "width", &self->width))
return FALSE;
switch (self->width) {
case 8:
self->func = (GstDeinterleaveFunc) deinterleave_8;
break;
case 16:
self->func = (GstDeinterleaveFunc) deinterleave_16;
break;
case 24:
self->func = (GstDeinterleaveFunc) deinterleave_24;
break;
case 32:
self->func = (GstDeinterleaveFunc) deinterleave_32;
break;
case 64:
self->func = (GstDeinterleaveFunc) deinterleave_64;
break;
default:
return FALSE;
}
return TRUE;
}
static gboolean
gst_deinterleave_sink_setcaps (GstPad * pad, GstCaps * caps)
{
GstDeinterleave *self;
GstCaps *srccaps;
GstStructure *s;
self = GST_DEINTERLEAVE (gst_pad_get_parent (pad));
GST_DEBUG_OBJECT (self, "got caps: %" GST_PTR_FORMAT, caps);
if (self->sinkcaps && !gst_caps_is_equal (caps, self->sinkcaps)) {
gint new_channels, i;
GstAudioChannelPosition *pos;
gboolean same_layout = TRUE;
s = gst_caps_get_structure (caps, 0);
/* We allow caps changes as long as the number of channels doesn't change
* and the channel positions stay the same. _getcaps() should've cared
* for this already but better be safe.
*/
if (!gst_structure_get_int (s, "channels", &new_channels) ||
new_channels != self->channels ||
!gst_deinterleave_set_process_function (self, caps))
goto cannot_change_caps;
/* Now check the channel positions. If we had no channel positions
* and get them or the other way around things have changed.
* If we had channel positions and get different ones things have
* changed too of course
*/
pos = gst_audio_get_channel_positions (s);
if ((pos && !self->pos) || (!pos && self->pos))
goto cannot_change_caps;
if (pos) {
for (i = 0; i < self->channels; i++) {
if (self->pos[i] != pos[i]) {
same_layout = FALSE;
break;
}
}
g_free (pos);
if (!same_layout)
goto cannot_change_caps;
}
} else {
s = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (s, "channels", &self->channels))
goto no_channels;
if (!gst_deinterleave_set_process_function (self, caps))
goto unsupported_caps;
self->pos = gst_audio_get_channel_positions (s);
}
gst_caps_replace (&self->sinkcaps, caps);
/* Get srcpad caps */
srccaps = gst_caps_copy (caps);
s = gst_caps_get_structure (srccaps, 0);
gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
gst_structure_remove_field (s, "channel-positions");
/* If we already have pads, update the caps otherwise
* add new pads */
if (self->srcpads) {
gst_deinterleave_set_pads_caps (self, srccaps);
} else {
gst_deinterleave_add_new_pads (self, srccaps);
}
gst_caps_unref (srccaps);
gst_object_unref (self);
return TRUE;
cannot_change_caps:
{
GST_ERROR_OBJECT (self, "can't set new caps: %" GST_PTR_FORMAT, caps);
gst_object_unref (self);
return FALSE;
}
unsupported_caps:
{
GST_ERROR_OBJECT (self, "caps not supported: %" GST_PTR_FORMAT, caps);
gst_object_unref (self);
return FALSE;
}
no_channels:
{
GST_ERROR_OBJECT (self, "invalid caps");
gst_object_unref (self);
return FALSE;
}
}
static void
__remove_channels (GstCaps * caps)
{
GstStructure *s;
gint i, size;
size = gst_caps_get_size (caps);
for (i = 0; i < size; i++) {
s = gst_caps_get_structure (caps, i);
gst_structure_remove_field (s, "channel-positions");
gst_structure_remove_field (s, "channels");
}
}
static void
__set_channels (GstCaps * caps, gint channels)
{
GstStructure *s;
gint i, size;
size = gst_caps_get_size (caps);
for (i = 0; i < size; i++) {
s = gst_caps_get_structure (caps, i);
if (channels > 0)
gst_structure_set (s, "channels", G_TYPE_INT, channels, NULL);
else
gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
}
}
static GstCaps *
gst_deinterleave_sink_getcaps (GstPad * pad)
{
GstDeinterleave *self = GST_DEINTERLEAVE (gst_pad_get_parent (pad));
GstCaps *ret;
GList *l;
GST_OBJECT_LOCK (self);
/* Intersect all of our pad template caps with the peer caps of the pad
* to get all formats that are possible up- and downstream.
*
* For the pad for which the caps are requested we don't remove the channel
* informations as they must be in the returned caps and incompatibilities
* will be detected here already
*/
ret = gst_caps_new_any ();
for (l = GST_ELEMENT (self)->pads; l != NULL; l = l->next) {
GstPad *ourpad = GST_PAD (l->data);
GstCaps *peercaps = NULL, *ourcaps;
ourcaps = gst_caps_copy (gst_pad_get_pad_template_caps (ourpad));
if (pad == ourpad) {
if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK)
__set_channels (ourcaps, self->channels);
else
__set_channels (ourcaps, 1);
} else {
__remove_channels (ourcaps);
/* Only ask for peer caps for other pads than pad
* as otherwise gst_pad_peer_get_caps() might call
* back into this function and deadlock
*/
peercaps = gst_pad_peer_get_caps (ourpad);
}
/* If the peer exists and has caps add them to the intersection,
* otherwise assume that the peer accepts everything */
if (peercaps) {
GstCaps *intersection;
GstCaps *oldret = ret;
__remove_channels (peercaps);
intersection = gst_caps_intersect (peercaps, ourcaps);
ret = gst_caps_intersect (ret, intersection);
gst_caps_unref (intersection);
gst_caps_unref (peercaps);
gst_caps_unref (oldret);
} else {
GstCaps *oldret = ret;
ret = gst_caps_intersect (ret, ourcaps);
gst_caps_unref (oldret);
}
gst_caps_unref (ourcaps);
}
GST_OBJECT_UNLOCK (self);
gst_object_unref (self);
GST_DEBUG_OBJECT (pad, "Intersected caps to %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_deinterleave_sink_event (GstPad * pad, GstEvent * event)
{
GstDeinterleave *self = GST_DEINTERLEAVE (gst_pad_get_parent (pad));
gboolean ret;
GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
GST_DEBUG_PAD_NAME (pad));
/* Send FLUSH_STOP, FLUSH_START and EOS immediately, no matter if
* we have src pads already or not. Queue all other events and
* push them after we have src pads
*/
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
case GST_EVENT_FLUSH_START:
case GST_EVENT_EOS:
ret = gst_pad_event_default (pad, event);
break;
default:
if (self->srcpads) {
ret = gst_pad_event_default (pad, event);
} else {
GST_OBJECT_LOCK (self);
self->pending_events = g_list_append (self->pending_events, event);
GST_OBJECT_UNLOCK (self);
ret = TRUE;
}
break;
}
gst_object_unref (self);
return ret;
}
static gboolean
gst_deinterleave_src_query (GstPad * pad, GstQuery * query)
{
GstDeinterleave *self = GST_DEINTERLEAVE (gst_pad_get_parent (pad));
gboolean res;
res = gst_pad_query_default (pad, query);
if (res && GST_QUERY_TYPE (query) == GST_QUERY_DURATION) {
GstFormat format;
gint64 dur;
gst_query_parse_duration (query, &format, &dur);
/* Need to divide by the number of channels in byte format
* to get the correct value. All other formats should be fine
*/
if (format == GST_FORMAT_BYTES && dur != -1)
gst_query_set_duration (query, format, dur / self->channels);
} else if (res && GST_QUERY_TYPE (query) == GST_QUERY_POSITION) {
GstFormat format;
gint64 pos;
gst_query_parse_position (query, &format, &pos);
/* Need to divide by the number of channels in byte format
* to get the correct value. All other formats should be fine
*/
if (format == GST_FORMAT_BYTES && pos != -1)
gst_query_set_position (query, format, pos / self->channels);
}
gst_object_unref (self);
return res;
}
static void
gst_deinterleave_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstDeinterleave *self = GST_DEINTERLEAVE (object);
switch (prop_id) {
case PROP_KEEP_POSITIONS:
self->keep_positions = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_deinterleave_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstDeinterleave *self = GST_DEINTERLEAVE (object);
switch (prop_id) {
case PROP_KEEP_POSITIONS:
g_value_set_boolean (value, self->keep_positions);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstFlowReturn
gst_deinterleave_process (GstDeinterleave * self, GstBuffer * buf)
{
GstFlowReturn ret = GST_FLOW_OK;
guint channels = self->channels;
guint pads_pushed = 0, buffers_allocated = 0;
guint nframes = GST_BUFFER_SIZE (buf) / channels / (self->width / 8);
guint bufsize = nframes * (self->width / 8);
guint i;
GList *srcs;
GstBuffer **buffers_out = g_new0 (GstBuffer *, channels);
guint8 *in, *out;
/* Send any pending events to all src pads */
GST_OBJECT_LOCK (self);
if (self->pending_events) {
GList *events;
GstEvent *event;
GST_DEBUG_OBJECT (self, "Sending pending events to all src pads");
for (events = self->pending_events; events != NULL; events = events->next) {
event = GST_EVENT (events->data);
for (srcs = self->srcpads; srcs != NULL; srcs = srcs->next)
gst_pad_push_event (GST_PAD (srcs->data), gst_event_ref (event));
gst_event_unref (event);
}
g_list_free (self->pending_events);
self->pending_events = NULL;
}
GST_OBJECT_UNLOCK (self);
/* Allocate buffers */
for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
GstPad *pad = (GstPad *) srcs->data;
buffers_out[i] = NULL;
ret =
gst_pad_alloc_buffer (pad, GST_BUFFER_OFFSET_NONE, bufsize,
GST_PAD_CAPS (pad), &buffers_out[i]);
/* Make sure we got a correct buffer. The only other case we allow
* here is an unliked pad */
if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)
goto alloc_buffer_failed;
else if (buffers_out[i] && GST_BUFFER_SIZE (buffers_out[i]) != bufsize)
goto alloc_buffer_bad_size;
else if (buffers_out[i] &&
!gst_caps_is_equal (GST_BUFFER_CAPS (buffers_out[i]),
GST_PAD_CAPS (pad)))
goto invalid_caps;
if (buffers_out[i]) {
gst_buffer_copy_metadata (buffers_out[i], buf,
GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS);
buffers_allocated++;
}
}
/* Return NOT_LINKED if no pad was linked */
if (!buffers_allocated) {
GST_WARNING_OBJECT (self,
"Couldn't allocate any buffers because no pad was linked");
ret = GST_FLOW_NOT_LINKED;
goto done;
}
/* deinterleave */
for (srcs = self->srcpads, i = 0; srcs; srcs = srcs->next, i++) {
GstPad *pad = (GstPad *) srcs->data;
in = (guint8 *) GST_BUFFER_DATA (buf);
in += i * (self->width / 8);
if (buffers_out[i]) {
out = (guint8 *) GST_BUFFER_DATA (buffers_out[i]);
self->func (out, in, channels, nframes);
ret = gst_pad_push (pad, buffers_out[i]);
buffers_out[i] = NULL;
if (ret == GST_FLOW_OK)
pads_pushed++;
else if (ret == GST_FLOW_NOT_LINKED)
ret = GST_FLOW_OK;
else
goto push_failed;
}
}
/* Return NOT_LINKED if no pad was linked */
if (!pads_pushed)
ret = GST_FLOW_NOT_LINKED;
done:
gst_buffer_unref (buf);
g_free (buffers_out);
return ret;
alloc_buffer_failed:
{
GST_WARNING ("gst_pad_alloc_buffer() returned %s", gst_flow_get_name (ret));
goto clean_buffers;
}
alloc_buffer_bad_size:
{
GST_WARNING ("called alloc_buffer(), but didn't get requested bytes");
ret = GST_FLOW_NOT_NEGOTIATED;
goto clean_buffers;
}
invalid_caps:
{
GST_WARNING ("called alloc_buffer(), but didn't get requested caps");
ret = GST_FLOW_NOT_NEGOTIATED;
goto clean_buffers;
}
push_failed:
{
GST_DEBUG ("push() failed, flow = %s", gst_flow_get_name (ret));
goto clean_buffers;
}
clean_buffers:
{
for (i = 0; i < channels; i++) {
if (buffers_out[i])
gst_buffer_unref (buffers_out[i]);
}
gst_buffer_unref (buf);
g_free (buffers_out);
return ret;
}
}
static GstFlowReturn
gst_deinterleave_chain (GstPad * pad, GstBuffer * buffer)
{
GstDeinterleave *self = GST_DEINTERLEAVE (GST_PAD_PARENT (pad));
GstFlowReturn ret;
g_return_val_if_fail (self->func != NULL, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (self->width > 0, GST_FLOW_NOT_NEGOTIATED);
g_return_val_if_fail (self->channels > 0, GST_FLOW_NOT_NEGOTIATED);
ret = gst_deinterleave_process (self, buffer);
if (ret != GST_FLOW_OK)
GST_DEBUG_OBJECT (self, "flow return: %s", gst_flow_get_name (ret));
return ret;
}
static gboolean
gst_deinterleave_sink_activate_push (GstPad * pad, gboolean active)
{
GstDeinterleave *self = GST_DEINTERLEAVE (gst_pad_get_parent (pad));
/* Reset everything when the pad is deactivated */
if (!active) {
gst_deinterleave_remove_pads (self);
if (self->pos) {
g_free (self->pos);
self->pos = NULL;
}
self->channels = 0;
self->width = 0;
self->func = NULL;
if (self->pending_events) {
g_list_foreach (self->pending_events, (GFunc) gst_mini_object_unref,
NULL);
g_list_free (self->pending_events);
self->pending_events = NULL;
}
}
gst_object_unref (self);
return TRUE;
}

View file

@ -1,75 +0,0 @@
/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
* 2005 Wim Taymans <wim@fluendo.com>
* 2007 Andy Wingo <wingo at pobox.com>
* 2008 Sebastian Dröge <slomo@circular-chaos.org>
*
* deinterleave.c: deinterleave samples
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __DEINTERLEAVE_H__
#define __DEINTERLEAVE_H__
G_BEGIN_DECLS
#include <gst/gst.h>
#include <gst/audio/multichannel.h>
#define GST_TYPE_DEINTERLEAVE (gst_deinterleave_get_type())
#define GST_DEINTERLEAVE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DEINTERLEAVE,GstDeinterleave))
#define GST_DEINTERLEAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DEINTERLEAVE,GstDeinterleaveClass))
#define GST_DEINTERLEAVE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_DEINTERLEAVE,GstDeinterleaveClass))
#define GST_IS_DEINTERLEAVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DEINTERLEAVE))
#define GST_IS_DEINTERLEAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DEINTERLEAVE))
typedef struct _GstDeinterleave GstDeinterleave;
typedef struct _GstDeinterleaveClass GstDeinterleaveClass;
typedef void (*GstDeinterleaveFunc) (gpointer out, gpointer in, guint stride, guint nframes);
struct _GstDeinterleave
{
GstElement element;
/*< private > */
GList *srcpads;
GstCaps *sinkcaps;
gint channels;
GstAudioChannelPosition *pos;
gboolean keep_positions;
GstPad *sink;
gint width;
GstDeinterleaveFunc func;
GList *pending_events;
};
struct _GstDeinterleaveClass
{
GstElementClass parent_class;
};
GType gst_deinterleave_get_type (void);
G_END_DECLS
#endif /* __DEINTERLEAVE_H__ */

File diff suppressed because it is too large Load diff

View file

@ -1,89 +0,0 @@
/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
* 2005 Wim Taymans <wim@fluendo.com>
* 2007 Andy Wingo <wingo at pobox.com>
* 2008 Sebastian Dröge <slomo@circular-chaos.org>
*
* interleave.c: interleave samples, mostly based on adder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __INTERLEAVE_H__
#define __INTERLEAVE_H__
#include <gst/gst.h>
#include <gst/base/gstcollectpads.h>
G_BEGIN_DECLS
#define GST_TYPE_INTERLEAVE (gst_interleave_get_type())
#define GST_INTERLEAVE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_INTERLEAVE,GstInterleave))
#define GST_INTERLEAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_INTERLEAVE,GstInterleaveClass))
#define GST_INTERLEAVE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_INTERLEAVE,GstInterleaveClass))
#define GST_IS_INTERLEAVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_INTERLEAVE))
#define GST_IS_INTERLEAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_INTERLEAVE))
typedef struct _GstInterleave GstInterleave;
typedef struct _GstInterleaveClass GstInterleaveClass;
typedef void (*GstInterleaveFunc) (gpointer out, gpointer in, guint stride, guint nframes);
struct _GstInterleave
{
GstElement element;
/*< private >*/
GstCollectPads *collect;
gint channels;
gint padcounter;
gint rate;
gint width;
GValueArray *channel_positions;
GValueArray *input_channel_positions;
gboolean channel_positions_from_input;
GstCaps *sinkcaps;
GstClockTime timestamp;
guint64 offset;
gboolean segment_pending;
guint64 segment_position;
gdouble segment_rate;
GstSegment segment;
GstPadEventFunction collect_event;
GstInterleaveFunc func;
GstPad *src;
};
struct _GstInterleaveClass
{
GstElementClass parent_class;
};
GType gst_interleave_get_type (void);
G_END_DECLS
#endif /* __INTERLEAVE_H__ */

View file

@ -1,44 +0,0 @@
/* GStreamer interleave plugin
* Copyright (C) 2004,2007 Andy Wingo <wingo at pobox.com>
*
* plugin.c: the stubs for the interleave plugin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "plugin.h"
static gboolean
plugin_init (GstPlugin * plugin)
{
if (!gst_element_register (plugin, "interleave",
GST_RANK_NONE, gst_interleave_get_type ()) ||
!gst_element_register (plugin, "deinterleave",
GST_RANK_NONE, gst_deinterleave_get_type ()))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"interleave",
"Audio interleaver/deinterleaver",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);

View file

@ -1,31 +0,0 @@
/* GStreamer interleave plugin
* Copyright (C) 2004,2007 Andy Wingo <wingo at pobox.com>
*
* plugin.h: the stubs for the interleave plugin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_PLUGIN_INTERLEAVE_H__
#define __GST_PLUGIN_INTERLEAVE_H__
#include <gst/gst.h>
#include "interleave.h"
#include "deinterleave.h"
#endif /* __GST_PLUGIN_INTERLEAVE_H__ */

View file

@ -1,21 +0,0 @@
plugin_LTLIBRARIES = libgstreplaygain.la
libgstreplaygain_la_SOURCES = \
gstrganalysis.c \
gstrglimiter.c \
gstrgvolume.c \
replaygain.c \
rganalysis.c
libgstreplaygain_la_CFLAGS = \
$(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS)
libgstreplaygain_la_LIBADD = \
$(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstpbutils-0.10 $(LIBM)
libgstreplaygain_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
# headers we need but don't want installed
noinst_HEADERS = \
gstrganalysis.h \
gstrglimiter.h \
gstrgvolume.h \
replaygain.h \
rganalysis.h

View file

@ -1,692 +0,0 @@
/* 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 <link linkend="GstRgAnalysis--num-tracks">album processing</link>
* 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 -t audiotestsrc wave=sine num-buffers=512 ! rganalysis ! fakesink
* ]| Analyze a simple test waveform
* |[
* gst-launch -t filesrc location=filename.ext ! decodebin \
* ! audioconvert ! audioresample ! rganalysis ! fakesink
* ]| Analyze a given file
* |[
* gst-launch -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 "gstrganalysis.h"
#include "replaygain.h"
GST_DEBUG_CATEGORY_STATIC (gst_rg_analysis_debug);
#define GST_CAT_DEFAULT gst_rg_analysis_debug
static const GstElementDetails rganalysis_details = {
"ReplayGain analysis",
"Filter/Analyzer/Audio",
"Perform the ReplayGain analysis",
"Ren\xc3\xa9 Stadler <mail@renestadler.de>"
};
/* Default property value. */
#define FORCED_DEFAULT TRUE
enum
{
PROP_0,
PROP_NUM_TRACKS,
PROP_FORCED,
PROP_REFERENCE_LEVEL
};
/* 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 \
"channels = (int) { 1, 2 }, " \
"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 ("audio/x-raw-float, "
"width = (int) 32, " "endianness = (int) BYTE_ORDER, "
REPLAY_GAIN_CAPS "; "
"audio/x-raw-int, "
"width = (int) 16, " "depth = (int) [ 1, 16 ], "
"signed = (boolean) true, " "endianness = (int) BYTE_ORDER, "
REPLAY_GAIN_CAPS));
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, " "endianness = (int) BYTE_ORDER, "
REPLAY_GAIN_CAPS "; "
"audio/x-raw-int, "
"width = (int) 16, " "depth = (int) [ 1, 16 ], "
"signed = (boolean) true, " "endianness = (int) BYTE_ORDER, "
REPLAY_GAIN_CAPS));
GST_BOILERPLATE (GstRgAnalysis, gst_rg_analysis, GstBaseTransform,
GST_TYPE_BASE_TRANSFORM);
static void gst_rg_analysis_class_init (GstRgAnalysisClass * klass);
static void gst_rg_analysis_init (GstRgAnalysis * filter,
GstRgAnalysisClass * gclass);
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_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_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
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_details (element_class, &rganalysis_details);
GST_DEBUG_CATEGORY_INIT (gst_rg_analysis_debug, "rganalysis", 0,
"ReplayGain analysis element");
}
static void
gst_rg_analysis_class_init (GstRgAnalysisClass * klass)
{
GObjectClass *gobject_class;
GstBaseTransformClass *trans_class;
gobject_class = (GObjectClass *) 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));
/**
* 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));
/**
* 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));
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->event = GST_DEBUG_FUNCPTR (gst_rg_analysis_event);
trans_class->stop = GST_DEBUG_FUNCPTR (gst_rg_analysis_stop);
trans_class->passthrough_on_same_caps = TRUE;
}
static void
gst_rg_analysis_init (GstRgAnalysis * filter, GstRgAnalysisClass * gclass)
{
GstBaseTransform *base = GST_BASE_TRANSFORM (filter);
gst_base_transform_set_gap_aware (base, TRUE);
filter->num_tracks = 0;
filter->forced = FORCED_DEFAULT;
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);
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rg_analysis_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstRgAnalysis *filter = GST_RG_ANALYSIS (object);
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
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 ();
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);
GstStructure *structure;
const gchar *name;
gint n_channels, sample_rate, sample_bit_size, sample_size;
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);
structure = gst_caps_get_structure (in_caps, 0);
name = gst_structure_get_name (structure);
if (!gst_structure_get_int (structure, "width", &sample_bit_size)
|| !gst_structure_get_int (structure, "channels", &n_channels)
|| !gst_structure_get_int (structure, "rate", &sample_rate))
goto invalid_format;
if (!rg_analysis_set_sample_rate (filter->ctx, sample_rate))
goto invalid_format;
if (sample_bit_size % 8 != 0)
goto invalid_format;
sample_size = sample_bit_size / 8;
if (g_str_equal (name, "audio/x-raw-float")) {
if (sample_size != sizeof (gfloat))
goto invalid_format;
/* 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 (n_channels == 1)
filter->analyze = rg_analysis_analyze_mono_float;
else if (n_channels == 2)
filter->analyze = rg_analysis_analyze_stereo_float;
else
goto invalid_format;
} else if (g_str_equal (name, "audio/x-raw-int")) {
if (sample_size != sizeof (gint16))
goto invalid_format;
if (!gst_structure_get_int (structure, "depth", &filter->depth))
goto invalid_format;
if (filter->depth < 1 || filter->depth > 16)
goto invalid_format;
if (n_channels == 1)
filter->analyze = rg_analysis_analyze_mono_int16;
else if (n_channels == 2)
filter->analyze = rg_analysis_analyze_stereo_int16;
else
goto invalid_format;
} else {
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);
g_return_val_if_fail (filter->ctx != NULL, GST_FLOW_WRONG_STATE);
g_return_val_if_fail (filter->analyze != NULL, GST_FLOW_NOT_NEGOTIATED);
if (filter->skip)
return GST_FLOW_OK;
/* Buffers made up of silence have no influence on the analysis: */
if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP))
return GST_FLOW_OK;
GST_LOG_OBJECT (filter, "processing buffer of size %u",
GST_BUFFER_SIZE (buf));
filter->analyze (filter->ctx, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf),
filter->depth);
return GST_FLOW_OK;
}
static gboolean
gst_rg_analysis_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)->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;
}
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 steals our reference to the list: */
gst_element_found_tags_for_pad (GST_ELEMENT (filter),
GST_BASE_TRANSFORM_SRC_PAD (GST_BASE_TRANSFORM (filter)), tag_list);
}
}
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");
}
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 ();
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 ();
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;
}

View file

@ -1,85 +0,0 @@
/* 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;
/* 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__ */

View file

@ -1,202 +0,0 @@
/* 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 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 "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-float, "
"width = (int) 32, channels = (int) [1, MAX], "
"rate = (int) [1, MAX], endianness = (int) BYTE_ORDER"));
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, channels = (int) [1, MAX], "
"rate = (int) [1, MAX], endianness = (int) BYTE_ORDER"));
GST_BOILERPLATE (GstRgLimiter, gst_rg_limiter, GstBaseTransform,
GST_TYPE_BASE_TRANSFORM);
static void gst_rg_limiter_class_init (GstRgLimiterClass * klass);
static void gst_rg_limiter_init (GstRgLimiter * filter,
GstRgLimiterClass * gclass);
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 const GstElementDetails element_details = {
"ReplayGain limiter",
"Filter/Effect/Audio",
"Apply signal compression to raw audio data",
"Ren\xc3\xa9 Stadler <mail@renestadler.de>"
};
static void
gst_rg_limiter_base_init (gpointer g_class)
{
GstElementClass *element_class = g_class;
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_details (element_class, &element_details);
GST_DEBUG_CATEGORY_INIT (gst_rg_limiter_debug, "rglimiter", 0,
"ReplayGain limiter element");
}
static void
gst_rg_limiter_class_init (GstRgLimiterClass * klass)
{
GObjectClass *gobject_class;
GstBaseTransformClass *trans_class;
gobject_class = (GObjectClass *) 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));
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;
}
static void
gst_rg_limiter_init (GstRgLimiter * filter, GstRgLimiterClass * gclass)
{
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;
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;
input = (gfloat *) GST_BUFFER_DATA (buf);
count = GST_BUFFER_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++;
}
return GST_FLOW_OK;
}

View file

@ -1,64 +0,0 @@
/* 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__ */

View file

@ -1,698 +0,0 @@
/* 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 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 <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. */
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-float, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, MAX ], "
"endianness = (int) BYTE_ORDER, "
"width = (int) 32; "
"audio/x-raw-int, "
"channels = (int) [ 1, MAX ], "
"rate = (int) [ 1, MAX ], "
"endianness = (int) BYTE_ORDER, "
"width = (int) 16, " "depth = (int) 16, " "signed = (bool) TRUE"));
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-float, "
"rate = (int) [ 1, MAX ], "
"channels = (int) [ 1, MAX ], "
"endianness = (int) BYTE_ORDER, "
"width = (int) 32; "
"audio/x-raw-int, "
"channels = (int) [ 1, MAX ], "
"rate = (int) [ 1, MAX ], "
"endianness = (int) BYTE_ORDER, "
"width = (int) 16, " "depth = (int) 16, " "signed = (bool) TRUE"));
GST_BOILERPLATE (GstRgVolume, gst_rg_volume, GstBin, GST_TYPE_BIN);
static void gst_rg_volume_class_init (GstRgVolumeClass * klass);
static void gst_rg_volume_init (GstRgVolume * self, GstRgVolumeClass * gclass);
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, 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_base_init (gpointer g_class)
{
GstElementClass *element_class = g_class;
static const GstElementDetails element_details = {
"ReplayGain volume",
"Filter/Effect/Audio",
"Apply ReplayGain volume adjustment",
"Ren\xc3\xa9 Stadler <mail@renestadler.de>"
};
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_details (element_class, &element_details);
GST_DEBUG_CATEGORY_INIT (gst_rg_volume_debug, "rgvolume", 0,
"ReplayGain volume element");
}
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 <link linkend="GstRgVolume--fallback-gain">fallback-gain</link>
* 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));
/**
* 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 (audio/x-raw-float). 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));
/**
* 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));
/**
* 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));
/**
* GstRgVolume:result-gain:
*
* Applied gain [dB]. This gain is applied to processed buffer data.
*
* This is set to the <link linkend="GstRgVolume--target-gain">target
* gain</link> 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 <link linkend="GstRgVolume--pre-amp">pre-amp</link> 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 <link linkend="GstRgVolume--pre-amp">pre-amp</link> has
* to be lowered and/or a limiter has to be used which facilitates the use of
* <link linkend="GstRgVolume--headroom">headroom</link>.
*/
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));
/**
* GstRgVolume:target-gain:
*
* Applicable gain [dB]. This gain is supposed to be applied.
*
* Depending on the value of the <link
* linkend="GstRgVolume--album-mode">album-mode</link> property and the
* presence of ReplayGain tags in the stream, this is set according to one of
* these simple formulas:
*
* <itemizedlist>
* <listitem><link linkend="GstRgVolume--pre-amp">pre-amp</link> + album gain
* of the stream</listitem>
* <listitem><link linkend="GstRgVolume--pre-amp">pre-amp</link> + track gain
* of the stream</listitem>
* <listitem><link linkend="GstRgVolume--pre-amp">pre-amp</link> + <link
* linkend="GstRgVolume--fallback-gain">fallback gain</link></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));
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;
}
static void
gst_rg_volume_init (GstRgVolume * self, GstRgVolumeClass * gclass)
{
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_get_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_get_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, GstEvent * event)
{
GstRgVolume *self;
GstPad *volume_sink_pad;
GstEvent *send_event = event;
gboolean res;
self = GST_RG_VOLUME (gst_pad_get_parent_element (pad));
volume_sink_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
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_send_event (volume_sink_pad, send_event);
else
res = TRUE;
gst_object_unref (volume_sink_pad);
gst_object_unref (self);
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;
}
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;
}
}

View file

@ -1,88 +0,0 @@
/* 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__ */

View file

@ -1,53 +0,0 @@
/* 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);

View file

@ -1,36 +0,0 @@
/* 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__ */

View file

@ -1,777 +0,0 @@
/* 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;
};
/* 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;
}
/* 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);
return TRUE;
}
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;
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);
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);
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);
}

View file

@ -1,56 +0,0 @@
/* 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>
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_reset_album (RgAnalysisCtx * ctx);
void rg_analysis_reset (RgAnalysisCtx * ctx);
void rg_analysis_destroy (RgAnalysisCtx * ctx);
G_END_DECLS
#endif /* __RG_ANALYSIS_H__ */

View file

@ -73,11 +73,6 @@ check_PROGRAMS = \
$(check_neon) \ $(check_neon) \
$(check_ofa) \ $(check_ofa) \
$(check_timidity) \ $(check_timidity) \
elements/deinterleave \
elements/interleave \
elements/rganalysis \
elements/rglimiter \
elements/rgvolume \
elements/selector \ elements/selector \
elements/y4menc elements/y4menc
@ -88,11 +83,6 @@ TESTS = $(check_PROGRAMS)
AM_CFLAGS = $(GST_OBJ_CFLAGS) $(GST_CHECK_CFLAGS) $(CHECK_CFLAGS) $(GST_OPTION_CFLAGS) AM_CFLAGS = $(GST_OBJ_CFLAGS) $(GST_CHECK_CFLAGS) $(CHECK_CFLAGS) $(GST_OPTION_CFLAGS)
LDADD = $(GST_OBJ_LIBS) $(GST_CHECK_LIBS) $(CHECK_LIBS) LDADD = $(GST_OBJ_LIBS) $(GST_CHECK_LIBS) $(CHECK_LIBS)
elements_deinterleave_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(CFLAGS) $(AM_CFLAGS)
elements_deinterleave_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) $(LDADD)
elements_interleave_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(CFLAGS) $(AM_CFLAGS)
elements_interleave_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) $(LDADD)
elements_timidity_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) elements_timidity_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS)
elements_timidity_LDADD = $(GST_BASE_LIBS) $(LDADD) elements_timidity_LDADD = $(GST_BASE_LIBS) $(LDADD)

View file

@ -1,558 +0,0 @@
/* GStreamer unit tests for the interleave element
* Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/check/gstcheck.h>
#include <gst/audio/multichannel.h>
GST_START_TEST (test_create_and_unref)
{
GstElement *deinterleave;
deinterleave = gst_element_factory_make ("deinterleave", NULL);
fail_unless (deinterleave != NULL);
gst_element_set_state (deinterleave, GST_STATE_NULL);
gst_object_unref (deinterleave);
}
GST_END_TEST;
static GstPad *mysrcpad, **mysinkpads;
static gint nsinkpads;
static GstBus *bus;
static GstElement *deinterleave;
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, "
"channels = (int) 1, "
"rate = (int) {32000, 48000}, " "endianness = (int) BYTE_ORDER"));
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, "
"channels = (int) { 2, 3 }, "
"rate = (int) {32000, 48000}, " "endianness = (int) BYTE_ORDER"));
#define CAPS_32khz \
"audio/x-raw-float, " \
"width = (int) 32, " \
"channels = (int) 2, " \
"rate = (int) 32000, " \
"endianness = (int) BYTE_ORDER"
#define CAPS_48khz \
"audio/x-raw-float, " \
"width = (int) 32, " \
"channels = (int) 2, " \
"rate = (int) 48000, " \
"endianness = (int) BYTE_ORDER"
#define CAPS_48khz_3CH \
"audio/x-raw-float, " \
"width = (int) 32, " \
"channels = (int) 3, " \
"rate = (int) 48000, " \
"endianness = (int) BYTE_ORDER"
static GstFlowReturn
deinterleave_chain_func (GstPad * pad, GstBuffer * buffer)
{
gint i;
gfloat *indata;
fail_unless (GST_IS_BUFFER (buffer));
fail_unless_equals_int (GST_BUFFER_SIZE (buffer), 48000 * sizeof (gfloat));
fail_unless (GST_BUFFER_DATA (buffer) != NULL);
indata = (gfloat *) GST_BUFFER_DATA (buffer);
if (strcmp (GST_PAD_NAME (pad), "sink0") == 0) {
for (i = 0; i < 48000; i++)
fail_unless_equals_float (indata[i], -1.0);
} else if (strcmp (GST_PAD_NAME (pad), "sink1") == 0) {
for (i = 0; i < 48000; i++)
fail_unless_equals_float (indata[i], 1.0);
} else {
g_assert_not_reached ();
}
gst_buffer_unref (buffer);
return GST_FLOW_OK;
}
static void
deinterleave_pad_added (GstElement * src, GstPad * pad, gpointer data)
{
gchar *name;
gint link = GPOINTER_TO_INT (data);
if (nsinkpads >= link)
return;
name = g_strdup_printf ("sink%d", nsinkpads);
mysinkpads[nsinkpads] =
gst_pad_new_from_static_template (&sinktemplate, name);
g_free (name);
fail_if (mysinkpads[nsinkpads] == NULL);
gst_pad_set_chain_function (mysinkpads[nsinkpads], deinterleave_chain_func);
fail_unless (gst_pad_link (pad, mysinkpads[nsinkpads]) == GST_PAD_LINK_OK);
gst_pad_set_active (mysinkpads[nsinkpads], TRUE);
nsinkpads++;
}
GST_START_TEST (test_2_channels)
{
GstPad *sinkpad;
gint i;
GstBuffer *inbuf;
GstCaps *caps;
gfloat *indata;
mysinkpads = g_new0 (GstPad *, 2);
nsinkpads = 0;
deinterleave = gst_element_factory_make ("deinterleave", NULL);
fail_unless (deinterleave != NULL);
mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src");
fail_unless (mysrcpad != NULL);
caps = gst_caps_from_string (CAPS_48khz);
fail_unless (gst_pad_set_caps (mysrcpad, caps));
gst_pad_use_fixed_caps (mysrcpad);
sinkpad = gst_element_get_static_pad (deinterleave, "sink");
fail_unless (sinkpad != NULL);
fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK);
g_object_unref (sinkpad);
g_signal_connect (deinterleave, "pad-added",
G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (2));
bus = gst_bus_new ();
gst_element_set_bus (deinterleave, bus);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 2 * 48000; i += 2) {
indata[i] = -1.0;
indata[i + 1] = 1.0;
}
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
for (i = 0; i < nsinkpads; i++)
g_object_unref (mysinkpads[i]);
g_free (mysinkpads);
mysinkpads = NULL;
g_object_unref (deinterleave);
g_object_unref (bus);
gst_caps_unref (caps);
}
GST_END_TEST;
GST_START_TEST (test_2_channels_1_linked)
{
GstPad *sinkpad;
gint i;
GstBuffer *inbuf;
GstCaps *caps;
gfloat *indata;
nsinkpads = 0;
mysinkpads = g_new0 (GstPad *, 2);
deinterleave = gst_element_factory_make ("deinterleave", NULL);
fail_unless (deinterleave != NULL);
mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src");
fail_unless (mysrcpad != NULL);
caps = gst_caps_from_string (CAPS_48khz);
fail_unless (gst_pad_set_caps (mysrcpad, caps));
gst_pad_use_fixed_caps (mysrcpad);
sinkpad = gst_element_get_static_pad (deinterleave, "sink");
fail_unless (sinkpad != NULL);
fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK);
g_object_unref (sinkpad);
g_signal_connect (deinterleave, "pad-added",
G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (1));
bus = gst_bus_new ();
gst_element_set_bus (deinterleave, bus);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 2 * 48000; i += 2) {
indata[i] = -1.0;
indata[i + 1] = 1.0;
}
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
for (i = 0; i < nsinkpads; i++)
g_object_unref (mysinkpads[i]);
g_free (mysinkpads);
mysinkpads = NULL;
g_object_unref (deinterleave);
g_object_unref (bus);
gst_caps_unref (caps);
}
GST_END_TEST;
GST_START_TEST (test_2_channels_caps_change)
{
GstPad *sinkpad;
GstCaps *caps, *caps2;
gint i;
GstBuffer *inbuf;
gfloat *indata;
nsinkpads = 0;
mysinkpads = g_new0 (GstPad *, 2);
deinterleave = gst_element_factory_make ("deinterleave", NULL);
fail_unless (deinterleave != NULL);
mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src");
fail_unless (mysrcpad != NULL);
caps = gst_caps_from_string (CAPS_48khz);
fail_unless (gst_pad_set_caps (mysrcpad, caps));
gst_pad_use_fixed_caps (mysrcpad);
sinkpad = gst_element_get_static_pad (deinterleave, "sink");
fail_unless (sinkpad != NULL);
fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK);
g_object_unref (sinkpad);
g_signal_connect (deinterleave, "pad-added",
G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (2));
bus = gst_bus_new ();
gst_element_set_bus (deinterleave, bus);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 2 * 48000; i += 2) {
indata[i] = -1.0;
indata[i + 1] = 1.0;
}
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
caps2 = gst_caps_from_string (CAPS_32khz);
gst_pad_set_caps (mysrcpad, caps2);
inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 2 * 48000; i += 2) {
indata[i] = -1.0;
indata[i + 1] = 1.0;
}
gst_buffer_set_caps (inbuf, caps2);
/* Should work fine because the caps changed in a compatible way */
fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
gst_caps_unref (caps2);
caps2 = gst_caps_from_string (CAPS_48khz_3CH);
gst_pad_set_caps (mysrcpad, caps2);
inbuf = gst_buffer_new_and_alloc (3 * 48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 3 * 48000; i += 3) {
indata[i] = -1.0;
indata[i + 1] = 1.0;
indata[i + 2] = 0.0;
}
gst_buffer_set_caps (inbuf, caps2);
/* Should break because the caps changed in an incompatible way */
fail_if (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
fail_unless (gst_element_set_state (deinterleave,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
for (i = 0; i < nsinkpads; i++)
g_object_unref (mysinkpads[i]);
g_free (mysinkpads);
mysinkpads = NULL;
g_object_unref (deinterleave);
g_object_unref (bus);
gst_caps_unref (caps);
gst_caps_unref (caps2);
}
GST_END_TEST;
#define SAMPLES_PER_BUFFER 10
#define NUM_CHANNELS 8
#define SAMPLE_RATE 44100
static guint pads_created;
static void
set_channel_positions (GstCaps * caps, int channels,
GstAudioChannelPosition * channelpositions)
{
GValue chanpos = { 0 };
GValue pos = { 0 };
GstStructure *structure = gst_caps_get_structure (caps, 0);
int c;
g_value_init (&chanpos, GST_TYPE_ARRAY);
g_value_init (&pos, GST_TYPE_AUDIO_CHANNEL_POSITION);
for (c = 0; c < channels; c++) {
g_value_set_enum (&pos, channelpositions[c]);
gst_value_array_append_value (&chanpos, &pos);
}
g_value_unset (&pos);
gst_structure_set_value (structure, "channel-positions", &chanpos);
g_value_unset (&chanpos);
}
static void
src_handoff_float32_8ch (GstElement * src, GstBuffer * buf, GstPad * pad,
gpointer user_data)
{
GstAudioChannelPosition layout[NUM_CHANNELS];
GstCaps *caps;
gfloat *data;
guint size, i, c;
caps = gst_caps_new_simple ("audio/x-raw-float",
"width", G_TYPE_INT, 32,
"depth", G_TYPE_INT, 32,
"channels", G_TYPE_INT, NUM_CHANNELS,
"rate", G_TYPE_INT, SAMPLE_RATE,
"endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
for (i = 0; i < NUM_CHANNELS; ++i)
layout[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
set_channel_positions (caps, NUM_CHANNELS, layout);
size = sizeof (gfloat) * SAMPLES_PER_BUFFER * NUM_CHANNELS;
data = (gfloat *) g_malloc (size);
GST_BUFFER_MALLOCDATA (buf) = (guint8 *) data;
GST_BUFFER_DATA (buf) = (guint8 *) data;
GST_BUFFER_SIZE (buf) = size;
GST_BUFFER_OFFSET (buf) = 0;
GST_BUFFER_TIMESTAMP (buf) = 0;
for (i = 0; i < SAMPLES_PER_BUFFER; ++i) {
for (c = 0; c < NUM_CHANNELS; ++c) {
*data = (gfloat) ((i * NUM_CHANNELS) + c);
++data;
}
}
gst_buffer_set_caps (buf, caps);
gst_caps_unref (caps);
}
static gboolean
float_buffer_check_probe (GstPad * pad, GstBuffer * buf, gpointer userdata)
{
gfloat *data;
guint padnum, numpads;
guint num, i;
GstCaps *caps;
GstStructure *s;
GstAudioChannelPosition *pos;
gint channels;
fail_unless_equals_int (sscanf (GST_PAD_NAME (pad), "src%u", &padnum), 1);
numpads = pads_created;
/* Check caps */
caps = GST_BUFFER_CAPS (buf);
fail_unless (caps != NULL);
s = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (s, "channels", &channels));
fail_unless_equals_int (channels, 1);
fail_unless (gst_structure_has_field (s, "channel-positions"));
pos = gst_audio_get_channel_positions (s);
fail_unless (pos != NULL && pos[0] == GST_AUDIO_CHANNEL_POSITION_NONE);
g_free (pos);
data = (gfloat *) GST_BUFFER_DATA (buf);
num = GST_BUFFER_SIZE (buf) / sizeof (gfloat);
/* Check buffer content */
for (i = 0; i < num; ++i) {
guint val, rest;
val = (guint) data[i];
GST_LOG ("%s[%u]: %8f", GST_PAD_NAME (pad), i, data[i]);
/* can't use the modulo operator in the assertion statement, since due to
* the way it gets expanded it would be interpreted as a printf operator
* in the failure case, which will result in segfaults */
rest = val % numpads;
/* check that the first channel is on pad src0, the second on src1 etc. */
fail_unless_equals_int (rest, padnum);
}
return TRUE; /* don't drop data */
}
static void
pad_added_setup_data_check_float32_8ch_cb (GstElement * deinterleave,
GstPad * pad, GstElement * pipeline)
{
GstElement *queue, *sink;
GstPad *sinkpad;
queue = gst_element_factory_make ("queue", NULL);
fail_unless (queue != NULL);
sink = gst_element_factory_make ("fakesink", NULL);
fail_unless (sink != NULL);
gst_bin_add_many (GST_BIN (pipeline), queue, sink, NULL);
fail_unless (gst_element_link_many (queue, sink, NULL));
sinkpad = gst_element_get_static_pad (queue, "sink");
fail_unless_equals_int (gst_pad_link (pad, sinkpad), GST_PAD_LINK_OK);
gst_object_unref (sinkpad);
gst_pad_add_buffer_probe (pad, G_CALLBACK (float_buffer_check_probe), NULL);
gst_element_set_state (sink, GST_STATE_PLAYING);
gst_element_set_state (queue, GST_STATE_PLAYING);
GST_LOG ("new pad: %s", GST_PAD_NAME (pad));
++pads_created;
}
static GstElement *
make_fake_src_8chans_float32 (void)
{
GstElement *src;
src = gst_element_factory_make ("fakesrc", "src");
fail_unless (src != NULL, "failed to create fakesrc element");
g_object_set (src, "num-buffers", 1, NULL);
g_object_set (src, "signal-handoffs", TRUE, NULL);
g_signal_connect (src, "handoff", G_CALLBACK (src_handoff_float32_8ch), NULL);
return src;
}
GST_START_TEST (test_8_channels_float32)
{
GstElement *pipeline, *src, *deinterleave;
GstMessage *msg;
pipeline = (GstElement *) gst_pipeline_new ("pipeline");
fail_unless (pipeline != NULL, "failed to create pipeline");
src = make_fake_src_8chans_float32 ();
deinterleave = gst_element_factory_make ("deinterleave", "deinterleave");
fail_unless (deinterleave != NULL, "failed to create deinterleave element");
g_object_set (deinterleave, "keep-positions", TRUE, NULL);
gst_bin_add_many (GST_BIN (pipeline), src, deinterleave, NULL);
fail_unless (gst_element_link (src, deinterleave),
"failed to link src <=> deinterleave");
g_signal_connect (deinterleave, "pad-added",
G_CALLBACK (pad_added_setup_data_check_float32_8ch_cb), pipeline);
pads_created = 0;
gst_element_set_state (pipeline, GST_STATE_PLAYING);
msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1);
gst_message_unref (msg);
fail_unless_equals_int (pads_created, NUM_CHANNELS);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
}
GST_END_TEST;
static Suite *
deinterleave_suite (void)
{
Suite *s = suite_create ("deinterleave");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_create_and_unref);
tcase_add_test (tc_chain, test_2_channels);
tcase_add_test (tc_chain, test_2_channels_1_linked);
tcase_add_test (tc_chain, test_2_channels_caps_change);
tcase_add_test (tc_chain, test_8_channels_float32);
return s;
}
GST_CHECK_MAIN (deinterleave);

View file

@ -1,761 +0,0 @@
/* GStreamer unit tests for the interleave element
* Copyright (C) 2007 Tim-Philipp Müller <tim centricular net>
* Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/check/gstcheck.h>
#include <gst/audio/multichannel.h>
GST_START_TEST (test_create_and_unref)
{
GstElement *interleave;
interleave = gst_element_factory_make ("interleave", NULL);
fail_unless (interleave != NULL);
gst_element_set_state (interleave, GST_STATE_NULL);
gst_object_unref (interleave);
}
GST_END_TEST;
GST_START_TEST (test_request_pads)
{
GstElement *interleave;
GstPad *pad1, *pad2;
interleave = gst_element_factory_make ("interleave", NULL);
fail_unless (interleave != NULL);
pad1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (pad1 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (pad1), "sink0");
pad2 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (pad2 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (pad2), "sink1");
gst_element_release_request_pad (interleave, pad2);
gst_object_unref (pad2);
gst_element_release_request_pad (interleave, pad1);
gst_object_unref (pad1);
gst_element_set_state (interleave, GST_STATE_NULL);
gst_object_unref (interleave);
}
GST_END_TEST;
static GstPad **mysrcpads, *mysinkpad;
static GstBus *bus;
static GstElement *interleave;
static gint have_data;
static gfloat input[2];
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, "
"channels = (int) 2, "
"rate = (int) 48000, " "endianness = (int) BYTE_ORDER"));
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-float, "
"width = (int) 32, "
"channels = (int) 1, "
"rate = (int) 48000, " "endianness = (int) BYTE_ORDER"));
#define CAPS_48khz \
"audio/x-raw-float, " \
"width = (int) 32, " \
"channels = (int) 1, " \
"rate = (int) 48000, " \
"endianness = (int) BYTE_ORDER"
static GstFlowReturn
interleave_chain_func (GstPad * pad, GstBuffer * buffer)
{
gfloat *outdata;
gint i;
fail_unless (GST_IS_BUFFER (buffer));
fail_unless_equals_int (GST_BUFFER_SIZE (buffer),
48000 * 2 * sizeof (gfloat));
fail_unless (GST_BUFFER_DATA (buffer) != NULL);
outdata = (gfloat *) GST_BUFFER_DATA (buffer);
for (i = 0; i < 48000 * 2; i += 2) {
fail_unless_equals_float (outdata[i], input[0]);
fail_unless_equals_float (outdata[i + 1], input[1]);
}
have_data++;
gst_buffer_unref (buffer);
return GST_FLOW_OK;
}
GST_START_TEST (test_interleave_2ch)
{
GstElement *queue;
GstPad *sink0, *sink1, *src, *tmp;
GstCaps *caps;
gint i;
GstBuffer *inbuf;
gfloat *indata;
mysrcpads = g_new0 (GstPad *, 2);
have_data = 0;
interleave = gst_element_factory_make ("interleave", NULL);
fail_unless (interleave != NULL);
queue = gst_element_factory_make ("queue", "queue");
fail_unless (queue != NULL);
sink0 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sink0 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (sink0), "sink0");
sink1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sink1 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (sink1), "sink1");
mysrcpads[0] = gst_pad_new_from_static_template (&srctemplate, "src0");
fail_unless (mysrcpads[0] != NULL);
caps = gst_caps_from_string (CAPS_48khz);
fail_unless (gst_pad_set_caps (mysrcpads[0], caps));
gst_pad_use_fixed_caps (mysrcpads[0]);
mysrcpads[1] = gst_pad_new_from_static_template (&srctemplate, "src1");
fail_unless (mysrcpads[1] != NULL);
fail_unless (gst_pad_set_caps (mysrcpads[1], caps));
gst_pad_use_fixed_caps (mysrcpads[1]);
tmp = gst_element_get_static_pad (queue, "sink");
fail_unless (gst_pad_link (mysrcpads[0], tmp) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
tmp = gst_element_get_static_pad (queue, "src");
fail_unless (gst_pad_link (tmp, sink0) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
fail_unless (gst_pad_link (mysrcpads[1], sink1) == GST_PAD_LINK_OK);
mysinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
fail_unless (mysinkpad != NULL);
gst_pad_set_chain_function (mysinkpad, interleave_chain_func);
gst_pad_set_active (mysinkpad, TRUE);
src = gst_element_get_static_pad (interleave, "src");
fail_unless (src != NULL);
fail_unless (gst_pad_link (src, mysinkpad) == GST_PAD_LINK_OK);
gst_object_unref (src);
bus = gst_bus_new ();
gst_element_set_bus (interleave, bus);
fail_unless (gst_element_set_state (interleave,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
fail_unless (gst_element_set_state (queue,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
input[0] = -1.0;
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = -1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK);
input[1] = 1.0;
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = 1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK);
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = -1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK);
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = 1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK);
fail_unless (have_data == 2);
gst_object_unref (mysrcpads[0]);
gst_object_unref (mysrcpads[1]);
gst_object_unref (mysinkpad);
gst_element_release_request_pad (interleave, sink0);
gst_object_unref (sink0);
gst_element_release_request_pad (interleave, sink1);
gst_object_unref (sink1);
gst_element_set_state (interleave, GST_STATE_NULL);
gst_element_set_state (queue, GST_STATE_NULL);
gst_object_unref (interleave);
gst_object_unref (queue);
gst_object_unref (bus);
gst_caps_unref (caps);
g_free (mysrcpads);
}
GST_END_TEST;
GST_START_TEST (test_interleave_2ch_1eos)
{
GstElement *queue;
GstPad *sink0, *sink1, *src, *tmp;
GstCaps *caps;
gint i;
GstBuffer *inbuf;
gfloat *indata;
mysrcpads = g_new0 (GstPad *, 2);
have_data = 0;
interleave = gst_element_factory_make ("interleave", NULL);
fail_unless (interleave != NULL);
queue = gst_element_factory_make ("queue", "queue");
fail_unless (queue != NULL);
sink0 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sink0 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (sink0), "sink0");
sink1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sink1 != NULL);
fail_unless_equals_string (GST_OBJECT_NAME (sink1), "sink1");
mysrcpads[0] = gst_pad_new_from_static_template (&srctemplate, "src0");
fail_unless (mysrcpads[0] != NULL);
caps = gst_caps_from_string (CAPS_48khz);
fail_unless (gst_pad_set_caps (mysrcpads[0], caps));
gst_pad_use_fixed_caps (mysrcpads[0]);
mysrcpads[1] = gst_pad_new_from_static_template (&srctemplate, "src1");
fail_unless (mysrcpads[1] != NULL);
fail_unless (gst_pad_set_caps (mysrcpads[1], caps));
gst_pad_use_fixed_caps (mysrcpads[1]);
tmp = gst_element_get_static_pad (queue, "sink");
fail_unless (gst_pad_link (mysrcpads[0], tmp) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
tmp = gst_element_get_static_pad (queue, "src");
fail_unless (gst_pad_link (tmp, sink0) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
fail_unless (gst_pad_link (mysrcpads[1], sink1) == GST_PAD_LINK_OK);
mysinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
fail_unless (mysinkpad != NULL);
gst_pad_set_chain_function (mysinkpad, interleave_chain_func);
gst_pad_set_active (mysinkpad, TRUE);
src = gst_element_get_static_pad (interleave, "src");
fail_unless (src != NULL);
fail_unless (gst_pad_link (src, mysinkpad) == GST_PAD_LINK_OK);
gst_object_unref (src);
bus = gst_bus_new ();
gst_element_set_bus (interleave, bus);
fail_unless (gst_element_set_state (interleave,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
fail_unless (gst_element_set_state (queue,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS);
input[0] = -1.0;
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = -1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK);
input[1] = 1.0;
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = 1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK);
input[0] = 0.0;
gst_pad_push_event (mysrcpads[0], gst_event_new_eos ());
input[1] = 1.0;
inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat));
indata = (gfloat *) GST_BUFFER_DATA (inbuf);
for (i = 0; i < 48000; i++)
indata[i] = 1.0;
gst_buffer_set_caps (inbuf, caps);
fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK);
fail_unless (have_data == 2);
gst_object_unref (mysrcpads[0]);
gst_object_unref (mysrcpads[1]);
gst_object_unref (mysinkpad);
gst_element_release_request_pad (interleave, sink0);
gst_object_unref (sink0);
gst_element_release_request_pad (interleave, sink1);
gst_object_unref (sink1);
gst_element_set_state (interleave, GST_STATE_NULL);
gst_element_set_state (queue, GST_STATE_NULL);
gst_object_unref (interleave);
gst_object_unref (queue);
gst_object_unref (bus);
gst_caps_unref (caps);
g_free (mysrcpads);
}
GST_END_TEST;
static void
src_handoff_float32 (GstElement * element, GstBuffer * buffer, GstPad * pad,
gpointer user_data)
{
gint n = GPOINTER_TO_INT (user_data);
GstCaps *caps;
gfloat *data;
gint i;
if (GST_PAD_CAPS (pad))
caps = gst_caps_ref (GST_PAD_CAPS (pad));
else {
caps = gst_caps_new_simple ("audio/x-raw-float",
"width", G_TYPE_INT, 32,
"channels", G_TYPE_INT, 1,
"rate", G_TYPE_INT, 48000, "endianness", G_TYPE_INT, G_BYTE_ORDER,
NULL);
if (n == 2) {
GstAudioChannelPosition pos[1] =
{ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT };
gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos);
} else if (n == 3) {
GstAudioChannelPosition pos[1] =
{ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT };
gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos);
}
}
data = g_new (gfloat, 48000);
GST_BUFFER_MALLOCDATA (buffer) = (guint8 *) data;
GST_BUFFER_DATA (buffer) = (guint8 *) data;
GST_BUFFER_SIZE (buffer) = 48000 * sizeof (gfloat);
GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE;
GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE;
GST_BUFFER_DURATION (buffer) = GST_SECOND;
gst_buffer_set_caps (buffer, caps);
gst_caps_unref (caps);
for (i = 0; i < 48000; i++)
data[i] = (n % 2 == 0) ? -1.0 : 1.0;
}
static void
sink_handoff_float32 (GstElement * element, GstBuffer * buffer, GstPad * pad,
gpointer user_data)
{
gint i;
gfloat *data;
GstCaps *caps;
gint n = GPOINTER_TO_INT (user_data);
fail_unless (GST_IS_BUFFER (buffer));
fail_unless_equals_int (GST_BUFFER_SIZE (buffer),
48000 * 2 * sizeof (gfloat));
fail_unless_equals_int (GST_BUFFER_DURATION (buffer), GST_SECOND);
caps = gst_caps_new_simple ("audio/x-raw-float",
"width", G_TYPE_INT, 32,
"channels", G_TYPE_INT, 2,
"rate", G_TYPE_INT, 48000, "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
if (n == 0) {
GstAudioChannelPosition pos[2] =
{ GST_AUDIO_CHANNEL_POSITION_NONE, GST_AUDIO_CHANNEL_POSITION_NONE };
gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos);
} else if (n == 1) {
GstAudioChannelPosition pos[2] = { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT
};
gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos);
} else if (n == 2) {
GstAudioChannelPosition pos[2] = { GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER
};
gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos);
}
fail_unless (gst_caps_is_equal (caps, GST_BUFFER_CAPS (buffer)));
gst_caps_unref (caps);
data = (gfloat *) GST_BUFFER_DATA (buffer);
for (i = 0; i < 48000 * 2; i += 2) {
fail_unless_equals_float (data[i], -1.0);
fail_unless_equals_float (data[i + 1], 1.0);
}
have_data++;
}
GST_START_TEST (test_interleave_2ch_pipeline)
{
GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink;
GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2;
GstMessage *msg;
have_data = 0;
pipeline = (GstElement *) gst_pipeline_new ("pipeline");
fail_unless (pipeline != NULL);
src1 = gst_element_factory_make ("fakesrc", "src1");
fail_unless (src1 != NULL);
g_object_set (src1, "num-buffers", 4, NULL);
g_object_set (src1, "signal-handoffs", TRUE, NULL);
g_signal_connect (src1, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (0));
gst_bin_add (GST_BIN (pipeline), src1);
src2 = gst_element_factory_make ("fakesrc", "src2");
fail_unless (src2 != NULL);
g_object_set (src2, "num-buffers", 4, NULL);
g_object_set (src2, "signal-handoffs", TRUE, NULL);
g_signal_connect (src2, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (1));
gst_bin_add (GST_BIN (pipeline), src2);
queue = gst_element_factory_make ("queue", "queue");
fail_unless (queue != NULL);
gst_bin_add (GST_BIN (pipeline), queue);
interleave = gst_element_factory_make ("interleave", "interleave");
fail_unless (interleave != NULL);
gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave));
sinkpad0 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad0 != NULL);
tmp = gst_element_get_static_pad (src1, "src");
fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sinkpad1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad1 != NULL);
tmp = gst_element_get_static_pad (src2, "src");
tmp2 = gst_element_get_static_pad (queue, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
tmp = gst_element_get_static_pad (queue, "src");
fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sink = gst_element_factory_make ("fakesink", "sink");
fail_unless (sink != NULL);
g_object_set (sink, "signal-handoffs", TRUE, NULL);
g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32),
GINT_TO_POINTER (0));
gst_bin_add (GST_BIN (pipeline), sink);
tmp = gst_element_get_static_pad (interleave, "src");
tmp2 = gst_element_get_static_pad (sink, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1);
gst_message_unref (msg);
fail_unless (have_data == 4);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_element_release_request_pad (interleave, sinkpad0);
gst_object_unref (sinkpad0);
gst_element_release_request_pad (interleave, sinkpad1);
gst_object_unref (sinkpad1);
gst_object_unref (interleave);
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_interleave_2ch_pipeline_input_chanpos)
{
GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink;
GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2;
GstMessage *msg;
have_data = 0;
pipeline = (GstElement *) gst_pipeline_new ("pipeline");
fail_unless (pipeline != NULL);
src1 = gst_element_factory_make ("fakesrc", "src1");
fail_unless (src1 != NULL);
g_object_set (src1, "num-buffers", 4, NULL);
g_object_set (src1, "signal-handoffs", TRUE, NULL);
g_signal_connect (src1, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (2));
gst_bin_add (GST_BIN (pipeline), src1);
src2 = gst_element_factory_make ("fakesrc", "src2");
fail_unless (src2 != NULL);
g_object_set (src2, "num-buffers", 4, NULL);
g_object_set (src2, "signal-handoffs", TRUE, NULL);
g_signal_connect (src2, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (3));
gst_bin_add (GST_BIN (pipeline), src2);
queue = gst_element_factory_make ("queue", "queue");
fail_unless (queue != NULL);
gst_bin_add (GST_BIN (pipeline), queue);
interleave = gst_element_factory_make ("interleave", "interleave");
fail_unless (interleave != NULL);
g_object_set (interleave, "channel-positions-from-input", TRUE, NULL);
gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave));
sinkpad0 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad0 != NULL);
tmp = gst_element_get_static_pad (src1, "src");
fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sinkpad1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad1 != NULL);
tmp = gst_element_get_static_pad (src2, "src");
tmp2 = gst_element_get_static_pad (queue, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
tmp = gst_element_get_static_pad (queue, "src");
fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sink = gst_element_factory_make ("fakesink", "sink");
fail_unless (sink != NULL);
g_object_set (sink, "signal-handoffs", TRUE, NULL);
g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32),
GINT_TO_POINTER (1));
gst_bin_add (GST_BIN (pipeline), sink);
tmp = gst_element_get_static_pad (interleave, "src");
tmp2 = gst_element_get_static_pad (sink, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1);
gst_message_unref (msg);
fail_unless (have_data == 4);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_element_release_request_pad (interleave, sinkpad0);
gst_object_unref (sinkpad0);
gst_element_release_request_pad (interleave, sinkpad1);
gst_object_unref (sinkpad1);
gst_object_unref (interleave);
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_interleave_2ch_pipeline_custom_chanpos)
{
GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink;
GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2;
GstMessage *msg;
GValueArray *arr;
GValue val = { 0, };
have_data = 0;
pipeline = (GstElement *) gst_pipeline_new ("pipeline");
fail_unless (pipeline != NULL);
src1 = gst_element_factory_make ("fakesrc", "src1");
fail_unless (src1 != NULL);
g_object_set (src1, "num-buffers", 4, NULL);
g_object_set (src1, "signal-handoffs", TRUE, NULL);
g_signal_connect (src1, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (0));
gst_bin_add (GST_BIN (pipeline), src1);
src2 = gst_element_factory_make ("fakesrc", "src2");
fail_unless (src2 != NULL);
g_object_set (src2, "num-buffers", 4, NULL);
g_object_set (src2, "signal-handoffs", TRUE, NULL);
g_signal_connect (src2, "handoff", G_CALLBACK (src_handoff_float32),
GINT_TO_POINTER (1));
gst_bin_add (GST_BIN (pipeline), src2);
queue = gst_element_factory_make ("queue", "queue");
fail_unless (queue != NULL);
gst_bin_add (GST_BIN (pipeline), queue);
interleave = gst_element_factory_make ("interleave", "interleave");
fail_unless (interleave != NULL);
g_object_set (interleave, "channel-positions-from-input", FALSE, NULL);
arr = g_value_array_new (2);
g_value_init (&val, GST_TYPE_AUDIO_CHANNEL_POSITION);
g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER);
g_value_array_append (arr, &val);
g_value_reset (&val);
g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER);
g_value_array_append (arr, &val);
g_value_unset (&val);
g_object_set (interleave, "channel-positions", arr, NULL);
g_value_array_free (arr);
gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave));
sinkpad0 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad0 != NULL);
tmp = gst_element_get_static_pad (src1, "src");
fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sinkpad1 = gst_element_get_request_pad (interleave, "sink%d");
fail_unless (sinkpad1 != NULL);
tmp = gst_element_get_static_pad (src2, "src");
tmp2 = gst_element_get_static_pad (queue, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
tmp = gst_element_get_static_pad (queue, "src");
fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
sink = gst_element_factory_make ("fakesink", "sink");
fail_unless (sink != NULL);
g_object_set (sink, "signal-handoffs", TRUE, NULL);
g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32),
GINT_TO_POINTER (2));
gst_bin_add (GST_BIN (pipeline), sink);
tmp = gst_element_get_static_pad (interleave, "src");
tmp2 = gst_element_get_static_pad (sink, "sink");
fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK);
gst_object_unref (tmp);
gst_object_unref (tmp2);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1);
gst_message_unref (msg);
fail_unless (have_data == 4);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_element_release_request_pad (interleave, sinkpad0);
gst_object_unref (sinkpad0);
gst_element_release_request_pad (interleave, sinkpad1);
gst_object_unref (sinkpad1);
gst_object_unref (interleave);
gst_object_unref (pipeline);
}
GST_END_TEST;
static Suite *
interleave_suite (void)
{
Suite *s = suite_create ("interleave");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_create_and_unref);
tcase_add_test (tc_chain, test_request_pads);
tcase_add_test (tc_chain, test_interleave_2ch);
tcase_add_test (tc_chain, test_interleave_2ch_1eos);
tcase_add_test (tc_chain, test_interleave_2ch_pipeline);
tcase_add_test (tc_chain, test_interleave_2ch_pipeline_input_chanpos);
tcase_add_test (tc_chain, test_interleave_2ch_pipeline_custom_chanpos);
return s;
}
GST_CHECK_MAIN (interleave);

File diff suppressed because it is too large Load diff

View file

@ -1,268 +0,0 @@
/* GStreamer ReplayGain limiter
*
* Copyright (C) 2007 Rene Stadler <mail@renestadler.de>
*
* rglimiter.c: Unit test for the rglimiter element
*
* 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
*/
#include <gst/check/gstcheck.h>
#include <math.h>
GList *buffers = NULL;
/* For ease of programming we use globals to keep refs for our floating
* src and sink pads we create; otherwise we always have to do get_pad,
* get_peer, and then remove references in every test function */
static GstPad *mysrcpad, *mysinkpad;
#define RG_LIMITER_CAPS_TEMPLATE_STRING \
"audio/x-raw-float, " \
"width = (int) 32, " \
"endianness = (int) BYTE_ORDER, " \
"channels = (int) [ 1, MAX ], " \
"rate = (int) [ 1, MAX ]"
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (RG_LIMITER_CAPS_TEMPLATE_STRING)
);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (RG_LIMITER_CAPS_TEMPLATE_STRING)
);
GstElement *
setup_rglimiter ()
{
GstElement *element;
GST_DEBUG ("setup_rglimiter");
element = gst_check_setup_element ("rglimiter");
mysrcpad = gst_check_setup_src_pad (element, &srctemplate, NULL);
mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate, NULL);
gst_pad_set_active (mysrcpad, TRUE);
gst_pad_set_active (mysinkpad, TRUE);
return element;
}
void
cleanup_rglimiter (GstElement * element)
{
GST_DEBUG ("cleanup_rglimiter");
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
g_list_free (buffers);
buffers = NULL;
gst_check_teardown_src_pad (element);
gst_check_teardown_sink_pad (element);
gst_check_teardown_element (element);
}
static void
set_playing_state (GstElement * element)
{
fail_unless (gst_element_set_state (element,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
"Could not set state to PLAYING");
}
static const gfloat test_input[] = {
-2.0, -1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0
};
static const gfloat test_output[] = {
-0.99752737684336523, /* -2.0 */
-0.88079707797788243, /* -1.0 */
-0.7310585786300049, /* -0.75 */
-0.5, -0.25, 0.0, 0.25, 0.5,
0.7310585786300049, /* 0.75 */
0.88079707797788243, /* 1.0 */
0.99752737684336523, /* 2.0 */
};
static GstBuffer *
create_test_buffer ()
{
GstBuffer *buf = gst_buffer_new_and_alloc (sizeof (test_input));
GstCaps *caps;
memcpy (GST_BUFFER_DATA (buf), test_input, sizeof (test_input));
caps = gst_caps_new_simple ("audio/x-raw-float",
"rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 1,
"endianness", G_TYPE_INT, G_BYTE_ORDER, "width", G_TYPE_INT, 32, NULL);
gst_buffer_set_caps (buf, caps);
gst_caps_unref (caps);
ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);
return buf;
}
static void
verify_test_buffer (GstBuffer * buf)
{
gfloat *output = (gfloat *) GST_BUFFER_DATA (buf);
gint i;
fail_unless (GST_BUFFER_SIZE (buf) == sizeof (test_output));
for (i = 0; i < G_N_ELEMENTS (test_input); i++)
fail_unless (ABS (output[i] - test_output[i]) < 1.e-6,
"Incorrect output value %.6f for input %.2f, expected %.6f",
output[i], test_input[i], test_output[i]);
}
/* Start of tests. */
GST_START_TEST (test_no_buffer)
{
GstElement *element = setup_rglimiter ();
set_playing_state (element);
cleanup_rglimiter (element);
}
GST_END_TEST;
GST_START_TEST (test_disabled)
{
GstElement *element = setup_rglimiter ();
GstBuffer *buf, *out_buf;
g_object_set (element, "enabled", FALSE, NULL);
set_playing_state (element);
buf = create_test_buffer ();
fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
fail_unless (g_list_length (buffers) == 1);
out_buf = buffers->data;
fail_if (out_buf == NULL);
buffers = g_list_remove (buffers, out_buf);
ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1);
fail_unless (buf == out_buf);
gst_buffer_unref (out_buf);
cleanup_rglimiter (element);
}
GST_END_TEST;
GST_START_TEST (test_limiting)
{
GstElement *element = setup_rglimiter ();
GstBuffer *buf, *out_buf;
set_playing_state (element);
/* Mutable variant. */
buf = create_test_buffer ();
fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
fail_unless (g_list_length (buffers) == 1);
out_buf = buffers->data;
fail_if (out_buf == NULL);
ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1);
verify_test_buffer (out_buf);
/* Immutable variant. */
buf = create_test_buffer ();
/* Extra ref: */
gst_buffer_ref (buf);
ASSERT_BUFFER_REFCOUNT (buf, "buf", 2);
fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);
fail_unless (g_list_length (buffers) == 2);
out_buf = g_list_last (buffers)->data;
fail_if (out_buf == NULL);
ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1);
fail_unless (buf != out_buf);
/* Drop our extra ref: */
gst_buffer_unref (buf);
verify_test_buffer (out_buf);
cleanup_rglimiter (element);
}
GST_END_TEST;
GST_START_TEST (test_gap)
{
GstElement *element = setup_rglimiter ();
GstBuffer *buf, *out_buf;
set_playing_state (element);
buf = create_test_buffer ();
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_GAP);
fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
fail_unless (g_list_length (buffers) == 1);
out_buf = buffers->data;
fail_if (out_buf == NULL);
ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1);
/* Verify that the baseclass does not lift the GAP flag: */
fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_GAP));
g_assert (GST_BUFFER_SIZE (out_buf) == GST_BUFFER_SIZE (buf));
/* We cheated by passing an input buffer with non-silence that has the GAP
* flag set. The element cannot know that however and must have skipped
* adjusting the buffer because of the flag, which we can easily verify: */
fail_if (memcmp (GST_BUFFER_DATA (out_buf),
GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (out_buf)) != 0);
cleanup_rglimiter (element);
}
GST_END_TEST;
Suite *
rglimiter_suite (void)
{
Suite *s = suite_create ("rglimiter");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_no_buffer);
tcase_add_test (tc_chain, test_disabled);
tcase_add_test (tc_chain, test_limiting);
tcase_add_test (tc_chain, test_gap);
return s;
}
int
main (int argc, char **argv)
{
gint nf;
Suite *s = rglimiter_suite ();
SRunner *sr = srunner_create (s);
gst_check_init (&argc, &argv);
srunner_run_all (sr, CK_ENV);
nf = srunner_ntests_failed (sr);
srunner_free (sr);
return nf;
}

View file

@ -1,573 +0,0 @@
/* GStreamer ReplayGain volume adjustment
*
* Copyright (C) 2007 Rene Stadler <mail@renestadler.de>
*
* rgvolume.c: Unit test for the rgvolume element
*
* 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
*/
#include <gst/check/gstcheck.h>
#include <math.h>
GList *buffers = NULL;
GList *events = NULL;
/* For ease of programming we use globals to keep refs for our floating src and
* sink pads we create; otherwise we always have to do get_pad, get_peer, and
* then remove references in every test function */
static GstPad *mysrcpad, *mysinkpad;
#define RG_VOLUME_CAPS_TEMPLATE_STRING \
"audio/x-raw-float, " \
"width = (int) 32, " \
"endianness = (int) BYTE_ORDER, " \
"channels = (int) [ 1, MAX ], " \
"rate = (int) [ 1, MAX ]"
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (RG_VOLUME_CAPS_TEMPLATE_STRING)
);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (RG_VOLUME_CAPS_TEMPLATE_STRING)
);
/* gstcheck sets up a chain function that appends buffers to a global list.
* This is our equivalent of that for event handling. */
static gboolean
event_func (GstPad * pad, GstEvent * event)
{
events = g_list_append (events, event);
return TRUE;
}
GstElement *
setup_rgvolume ()
{
GstElement *element;
GST_DEBUG ("setup_rgvolume");
element = gst_check_setup_element ("rgvolume");
mysrcpad = gst_check_setup_src_pad (element, &srctemplate, NULL);
mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate, NULL);
/* Capture events, to test tag filtering behavior: */
gst_pad_set_event_function (mysinkpad, event_func);
gst_pad_set_active (mysrcpad, TRUE);
gst_pad_set_active (mysinkpad, TRUE);
return element;
}
void
cleanup_rgvolume (GstElement * element)
{
GST_DEBUG ("cleanup_rgvolume");
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
g_list_free (buffers);
buffers = NULL;
g_list_foreach (events, (GFunc) gst_mini_object_unref, NULL);
g_list_free (events);
events = NULL;
gst_pad_set_active (mysrcpad, FALSE);
gst_pad_set_active (mysinkpad, FALSE);
gst_check_teardown_src_pad (element);
gst_check_teardown_sink_pad (element);
gst_check_teardown_element (element);
}
static void
set_playing_state (GstElement * element)
{
fail_unless (gst_element_set_state (element,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
"Could not set state to PLAYING");
}
static void
set_null_state (GstElement * element)
{
fail_unless (gst_element_set_state (element,
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS,
"Could not set state to NULL");
}
static void
send_eos_event (GstElement * element)
{
GstEvent *event = gst_event_new_eos ();
fail_unless (g_list_length (events) == 0);
fail_unless (gst_pad_push_event (mysrcpad, event),
"Pushing EOS event failed");
fail_unless (g_list_length (events) == 1);
fail_unless (events->data == event);
gst_mini_object_unref ((GstMiniObject *) events->data);
events = g_list_remove (events, event);
}
static GstEvent *
send_tag_event (GstElement * element, GstEvent * event)
{
g_return_val_if_fail (event->type == GST_EVENT_TAG, NULL);
fail_unless (g_list_length (events) == 0);
fail_unless (gst_pad_push_event (mysrcpad, event),
"Pushing tag event failed");
if (g_list_length (events) == 0) {
/* Event got filtered out. */
event = NULL;
} else {
GstTagList *tag_list;
gdouble dummy;
event = events->data;
events = g_list_remove (events, event);
fail_unless (event->type == GST_EVENT_TAG);
gst_event_parse_tag (event, &tag_list);
/* The element is supposed to filter out ReplayGain related tags. */
fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &dummy),
"tag event still contains track gain tag");
fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &dummy),
"tag event still contains track peak tag");
fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &dummy),
"tag event still contains album gain tag");
fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &dummy),
"tag event still contains album peak tag");
}
return event;
}
static GstBuffer *
test_buffer_new (gfloat value)
{
GstBuffer *buf;
GstCaps *caps;
gfloat *data;
gint i;
buf = gst_buffer_new_and_alloc (8 * sizeof (gfloat));
data = (gfloat *) GST_BUFFER_DATA (buf);
for (i = 0; i < 8; i++)
data[i] = value;
caps = gst_caps_from_string ("audio/x-raw-float, "
"rate = 8000, channels = 1, endianness = BYTE_ORDER, width = 32");
gst_buffer_set_caps (buf, caps);
gst_caps_unref (caps);
ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);
return buf;
}
#define MATCH_GAIN(g1, g2) ((g1 < g2 + 1e-6) && (g2 < g1 + 1e-6))
static void
fail_unless_target_gain (GstElement * element, gdouble expected_gain)
{
gdouble prop_gain;
g_object_get (element, "target-gain", &prop_gain, NULL);
fail_unless (MATCH_GAIN (prop_gain, expected_gain),
"Target gain is %.2f dB, expected %.2f dB", prop_gain, expected_gain);
}
static void
fail_unless_result_gain (GstElement * element, gdouble expected_gain)
{
GstBuffer *input_buf, *output_buf;
gfloat input_sample, output_sample;
gdouble gain, prop_gain;
gboolean is_passthrough, expect_passthrough;
gint i;
fail_unless (g_list_length (buffers) == 0);
input_sample = 1.0;
input_buf = test_buffer_new (input_sample);
/* We keep an extra reference to detect passthrough mode. */
gst_buffer_ref (input_buf);
/* Pushing steals a reference. */
fail_unless (gst_pad_push (mysrcpad, input_buf) == GST_FLOW_OK);
gst_buffer_unref (input_buf);
/* The output buffer ends up on the global buffer list. */
fail_unless (g_list_length (buffers) == 1);
output_buf = buffers->data;
fail_if (output_buf == NULL);
buffers = g_list_remove (buffers, output_buf);
ASSERT_BUFFER_REFCOUNT (output_buf, "output_buf", 1);
fail_unless_equals_int (GST_BUFFER_SIZE (output_buf), 8 * sizeof (gfloat));
output_sample = *((gfloat *) GST_BUFFER_DATA (output_buf));
fail_if (output_sample == 0.0, "First output sample is zero");
for (i = 1; i < 8; i++) {
gfloat output = ((gfloat *) GST_BUFFER_DATA (output_buf))[i];
fail_unless (output_sample == output, "Output samples not uniform");
};
gain = 20. * log10 (output_sample / input_sample);
fail_unless (MATCH_GAIN (gain, expected_gain),
"Applied gain is %.2f dB, expected %.2f dB", gain, expected_gain);
g_object_get (element, "result-gain", &prop_gain, NULL);
fail_unless (MATCH_GAIN (prop_gain, expected_gain),
"Result gain is %.2f dB, expected %.2f dB", prop_gain, expected_gain);
is_passthrough = (output_buf == input_buf);
expect_passthrough = MATCH_GAIN (expected_gain, +0.00);
fail_unless (is_passthrough == expect_passthrough,
expect_passthrough
? "Expected operation in passthrough mode"
: "Incorrect passthrough behaviour");
gst_buffer_unref (output_buf);
}
static void
fail_unless_gain (GstElement * element, gdouble expected_gain)
{
fail_unless_target_gain (element, expected_gain);
fail_unless_result_gain (element, expected_gain);
}
/* Start of tests. */
GST_START_TEST (test_no_buffer)
{
GstElement *element = setup_rgvolume ();
set_playing_state (element);
set_null_state (element);
set_playing_state (element);
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
GST_START_TEST (test_events)
{
GstElement *element = setup_rgvolume ();
GstEvent *event;
GstEvent *new_event;
GstTagList *tag_list;
gchar *artist;
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +4.95, GST_TAG_TRACK_PEAK, 0.59463,
GST_TAG_ALBUM_GAIN, -1.54, GST_TAG_ALBUM_PEAK, 0.693415,
GST_TAG_ARTIST, "Foobar", NULL);
event = gst_event_new_tag (tag_list);
new_event = send_tag_event (element, event);
/* Expect the element to modify the writable event. */
fail_unless (event == new_event, "Writable tag event not reused");
gst_event_parse_tag (new_event, &tag_list);
fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &artist));
fail_unless (g_str_equal (artist, "Foobar"));
g_free (artist);
gst_event_unref (new_event);
/* Same as above, but with a non-writable event. */
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +4.95, GST_TAG_TRACK_PEAK, 0.59463,
GST_TAG_ALBUM_GAIN, -1.54, GST_TAG_ALBUM_PEAK, 0.693415,
GST_TAG_ARTIST, "Foobar", NULL);
event = gst_event_new_tag (tag_list);
/* Holding an extra ref makes the event unwritable: */
gst_event_ref (event);
new_event = send_tag_event (element, event);
fail_unless (event != new_event, "Unwritable tag event reused");
gst_event_parse_tag (new_event, &tag_list);
fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &artist));
fail_unless (g_str_equal (artist, "Foobar"));
g_free (artist);
gst_event_unref (event);
gst_event_unref (new_event);
cleanup_rgvolume (element);
}
GST_END_TEST;
GST_START_TEST (test_simple)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
g_object_set (element, "album-mode", FALSE, "headroom", +0.00,
"pre-amp", -6.00, "fallback-gain", +1.23, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, -3.45, GST_TAG_TRACK_PEAK, 1.0,
GST_TAG_ALBUM_GAIN, +2.09, GST_TAG_ALBUM_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, -9.45); /* pre-amp + track gain */
send_eos_event (element);
g_object_set (element, "album-mode", TRUE, NULL);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, -3.45, GST_TAG_TRACK_PEAK, 1.0,
GST_TAG_ALBUM_GAIN, +2.09, GST_TAG_ALBUM_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, -3.91); /* pre-amp + album gain */
/* Switching back to track mode in the middle of a stream: */
g_object_set (element, "album-mode", FALSE, NULL);
fail_unless_gain (element, -9.45); /* pre-amp + track gain */
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
/* If there are no gain tags at all, the fallback gain is used. */
GST_START_TEST (test_fallback_gain)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
/* First some track where fallback does _not_ apply. */
g_object_set (element, "album-mode", FALSE, "headroom", 10.00,
"pre-amp", -6.00, "fallback-gain", -3.00, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +3.5, GST_TAG_TRACK_PEAK, 1.0,
GST_TAG_ALBUM_GAIN, -0.5, GST_TAG_ALBUM_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, -2.50); /* pre-amp + track gain */
send_eos_event (element);
/* Now a track completely missing tags. */
fail_unless_gain (element, -9.00); /* pre-amp + fallback-gain */
/* Changing the fallback gain in the middle of a stream, going to pass-through
* mode: */
g_object_set (element, "fallback-gain", +6.00, NULL);
fail_unless_gain (element, +0.00); /* pre-amp + fallback-gain */
send_eos_event (element);
/* Verify that result gain is set to +0.00 with pre-amp + fallback-gain >
* +0.00 and no headroom. */
g_object_set (element, "fallback-gain", +12.00, "headroom", +0.00, NULL);
fail_unless_target_gain (element, +6.00); /* pre-amp + fallback-gain */
fail_unless_result_gain (element, +0.00);
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
/* If album gain is to be preferred but not available, the track gain is to be
* taken instead. */
GST_START_TEST (test_fallback_track)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
g_object_set (element, "album-mode", TRUE, "headroom", +0.00,
"pre-amp", -6.00, "fallback-gain", +1.23, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +2.11, GST_TAG_TRACK_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, -3.89); /* pre-amp + track gain */
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
/* If track gain is to be preferred but not available, the album gain is to be
* taken instead. */
GST_START_TEST (test_fallback_album)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
g_object_set (element, "album-mode", FALSE, "headroom", +0.00,
"pre-amp", -6.00, "fallback-gain", +1.23, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_ALBUM_GAIN, +3.73, GST_TAG_ALBUM_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, -2.27); /* pre-amp + album gain */
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
GST_START_TEST (test_headroom)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
g_object_set (element, "album-mode", FALSE, "headroom", +0.00,
"pre-amp", +0.00, "fallback-gain", +1.23, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +3.50, GST_TAG_TRACK_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_target_gain (element, +3.50); /* pre-amp + track gain */
fail_unless_result_gain (element, +0.00);
send_eos_event (element);
g_object_set (element, "headroom", +2.00, NULL);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, +9.18, GST_TAG_TRACK_PEAK, 0.687149, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_target_gain (element, +9.18); /* pre-amp + track gain */
/* Result is 20. * log10 (1. / peak) + headroom. */
fail_unless_result_gain (element, 5.2589816238303335);
send_eos_event (element);
g_object_set (element, "album-mode", TRUE, NULL);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_ALBUM_GAIN, +5.50, GST_TAG_ALBUM_PEAK, 1.0, NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_target_gain (element, +5.50); /* pre-amp + album gain */
fail_unless_result_gain (element, +2.00); /* headroom */
send_eos_event (element);
cleanup_rgvolume (element);
}
GST_END_TEST;
GST_START_TEST (test_reference_level)
{
GstElement *element = setup_rgvolume ();
GstTagList *tag_list;
g_object_set (element,
"album-mode", FALSE,
"headroom", +0.00, "pre-amp", +0.00, "fallback-gain", +1.23, NULL);
set_playing_state (element);
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, 0.00, GST_TAG_TRACK_PEAK, 0.2,
GST_TAG_REFERENCE_LEVEL, 83., NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
/* Because our authorative reference is 89 dB, we bump it up by +6 dB. */
fail_unless_gain (element, +6.00); /* pre-amp + track gain */
send_eos_event (element);
g_object_set (element, "album-mode", TRUE, NULL);
/* Same as above, but with album gain. */
tag_list = gst_tag_list_new ();
gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE,
GST_TAG_TRACK_GAIN, 1.23, GST_TAG_TRACK_PEAK, 0.1,
GST_TAG_ALBUM_GAIN, 0.00, GST_TAG_ALBUM_PEAK, 0.2,
GST_TAG_REFERENCE_LEVEL, 83., NULL);
fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL);
fail_unless_gain (element, +6.00); /* pre-amp + album gain */
cleanup_rgvolume (element);
}
GST_END_TEST;
Suite *
rgvolume_suite (void)
{
Suite *s = suite_create ("rgvolume");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_no_buffer);
tcase_add_test (tc_chain, test_events);
tcase_add_test (tc_chain, test_simple);
tcase_add_test (tc_chain, test_fallback_gain);
tcase_add_test (tc_chain, test_fallback_track);
tcase_add_test (tc_chain, test_fallback_album);
tcase_add_test (tc_chain, test_headroom);
tcase_add_test (tc_chain, test_reference_level);
return s;
}
int
main (int argc, char **argv)
{
gint nf;
Suite *s = rgvolume_suite ();
SRunner *sr = srunner_create (s);
gst_check_init (&argc, &argv);
srunner_run_all (sr, CK_ENV);
nf = srunner_ntests_failed (sr);
srunner_free (sr);
return nf;
}