summaryrefslogtreecommitdiffstats
path: root/gst/replaygain/gstrganalysis.c
diff options
context:
space:
mode:
authorRené Stadler <mail@renestadler.de>2006-10-06 15:56:01 +0000
committerTim-Philipp Müller <tim@centricular.net>2006-10-06 15:56:01 +0000
commitedc2785eeb8304b58cd4122d9a228d093f6c1c79 (patch)
tree5d64aaa7f752b91def302ba03fe78b85a5c6265e /gst/replaygain/gstrganalysis.c
parent1e2737eb83dff0d03e76f709da26792a9e083df3 (diff)
Add ReplayGain analysis element (#357069).
Original commit message from CVS: Patch by: René Stadler <mail at renestadler de> * configure.ac: * docs/plugins/Makefile.am: * docs/plugins/gst-plugins-bad-plugins-docs.sgml: * docs/plugins/gst-plugins-bad-plugins-sections.txt: * gst/replaygain/Makefile.am: * gst/replaygain/gstrganalysis.c: (gst_rg_analysis_base_init), (gst_rg_analysis_class_init), (gst_rg_analysis_init), (gst_rg_analysis_set_property), (gst_rg_analysis_get_property), (gst_rg_analysis_start), (gst_rg_analysis_set_caps), (gst_rg_analysis_transform_ip), (gst_rg_analysis_event), (gst_rg_analysis_stop), (gst_rg_analysis_handle_tags), (gst_rg_analysis_handle_eos), (gst_rg_analysis_track_result), (gst_rg_analysis_album_result), (plugin_init): * gst/replaygain/gstrganalysis.h: * gst/replaygain/rganalysis.c: (yule_filter), (butter_filter), (apply_filters), (reset_filters), (accumulator_add), (accumulator_clear), (accumulator_result), (rg_analysis_new), (rg_analysis_set_sample_rate), (rg_analysis_destroy), (rg_analysis_analyze_mono_float), (rg_analysis_analyze_stereo_float), (rg_analysis_analyze_mono_int16), (rg_analysis_analyze_stereo_int16), (rg_analysis_analyze), (rg_analysis_track_result), (rg_analysis_album_result), (rg_analysis_reset_album), (rg_analysis_reset): * gst/replaygain/rganalysis.h: Add ReplayGain analysis element (#357069). * tests/check/Makefile.am: * tests/check/elements/.cvsignore: * tests/check/elements/rganalysis.c: (get_expected_gain), (setup_rganalysis), (cleanup_rganalysis), (set_playing_state), (send_eos_event), (send_tag_event), (poll_eos), (poll_tags), (fail_unless_track_gain), (fail_unless_track_peak), (fail_unless_album_gain), (fail_unless_album_peak), (fail_if_track_tags), (fail_if_album_tags), (fail_unless_num_tracks), (test_buffer_const_float_mono), (test_buffer_const_float_stereo), (test_buffer_const_int16_mono), (test_buffer_const_int16_stereo), (test_buffer_square_float_mono), (test_buffer_square_float_stereo), (test_buffer_square_int16_mono), (test_buffer_square_int16_stereo), (push_buffer), (GST_START_TEST), (rganalysis_suite), (main): Unit tests for the new replaygain element.
Diffstat (limited to 'gst/replaygain/gstrganalysis.c')
-rw-r--r--gst/replaygain/gstrganalysis.c686
1 files changed, 686 insertions, 0 deletions
diff --git a/gst/replaygain/gstrganalysis.c b/gst/replaygain/gstrganalysis.c
new file mode 100644
index 00000000..adf85555
--- /dev/null
+++ b/gst/replaygain/gstrganalysis.c
@@ -0,0 +1,686 @@
+/* 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
+ *
+ * <refsect2>
+ * <para>
+ * GstRgAnalysis 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 and #GST_TAG_TRACK_PEAK.
+ * </para>
+ * <title>Album processing</title>
+ * <para>
+ * 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 receive
+ * the same amplification. This keeps the relative volume levels
+ * between the tracks intact. To enable this, set the <link
+ * linkend="GstRgAnalysis--num-tracks">num-tracks</link> 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
+ * will be 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 make sure that the
+ * album gain and peak values are also associated with the other
+ * tracks when storing the results. It is thus a bit more complex to
+ * implement, but should not be avoided since the album gain is
+ * generally more valuable for use during playback than the track
+ * gain.
+ * </para>
+ * <title>Skipping processing</title>
+ * <para>
+ * For assisting transcoder/converter applications, the element can
+ * silently skip the processing of streams that already contain the
+ * necessary meta data 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 the <link
+ * linkend="GstRgAnalysis--forced">forced</link> property to #FALSE.
+ * If used in conjunction with album processing, 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, your
+ * application has to scan all files beforehand and enable forcing of
+ * processing if needed.
+ * </para>
+ * <title>Tips</title>
+ * <itemizedlist>
+ * <listitem><para>
+ * Because the generated metadata tags become available at the end of
+ * streams, downstream muxer and encoder elements are normally unable
+ * to save them in their output since they generally save metadata in
+ * the file header. Therefore, it is often necessary that
+ * applications read the results in a bus event handler for the tag
+ * message. Obtaining the values this way is always needed for album
+ * processing since the album gain and peak values need to be
+ * associated with all tracks of an album, not just the last one.
+ * </para></listitem>
+ * <listitem><para>
+ * 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 rganalysis element's state using
+ * gst_element_set_locked_state() when it is in PAUSED or PLAYING. As
+ * with any other element, don't forget to unlock it again and set it
+ * to the NULL state before dropping the last reference.
+ * </para></listitem>
+ * <listitem><para>
+ * If the total number of album tracks is unknown beforehand, set the
+ * num-tracks property to some large value like #G_MAXINT (or set it
+ * to >= 2 before each track starts). Before the last track ends, set
+ * the property value to 1.
+ * </para></listitem>
+ * </itemizedlist>
+ * <title>Compliance</title>
+ * <para>
+ * Analyzing the ReplayGain pink noise reference waveform will compute
+ * a result of +6.00dB instead of the expected 0.00dB because the
+ * default reference level is 89dB. To obtain values as lined out in
+ * the original proposal of ReplayGain, set the <link
+ * linkend="GstRgAnalysis--reference-level">reference-level</link>
+ * property to 83. Almost all software uses 89dB as a reference
+ * however, which works against the tendency of the algorithm to
+ * advise to drastically lower the volume of music with a highly
+ * compressed dynamic range and high average output levels. This
+ * tendency is normally to be fought during playback (if wanted), by
+ * using a default pre-amp value of at least +6.00dB. At one point,
+ * the majority of analyzer implementations switched to 89dB which
+ * moved this adjustment to the analyzing/metadata writing process.
+ * This change has been acknowledged by the author of the ReplayGain
+ * proposal, however at the time of this writing, the webpage is still
+ * not updated.
+ * </para>
+ * <title>Example launch lines</title>
+ * <para>Analyze a simple test waveform:</para>
+ * <programlisting>
+ * gst-launch -t audiotestsrc wave=sine num-buffers=512 ! rganalysis ! fakesink
+ * </programlisting>
+ * <para>Analyze a given file:</para>
+ * <programlisting>
+ * gst-launch -t filesrc location="Some file.ogg" ! decodebin ! audioconvert ! audioresample ! rganalysis ! fakesink
+ * </programlisting>
+ * <para>Analyze the pink noise reference file:</para>
+ * <programlisting>
+ * gst-launch -t gnomevfssrc location=http://replaygain.hydrogenaudio.org/ref_pink.wav ! wavparse ! rganalysis ! fakesink
+ * </programlisting>
+ * <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 <string.h>
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+
+#include "gstrganalysis.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;
+
+ g_object_class_install_property (gobject_class, PROP_NUM_TRACKS,
+ g_param_spec_int ("num-tracks", "Number of album tracks",
+ "Number of remaining tracks in the album",
+ 0, G_MAXINT, 0, G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_FORCED,
+ g_param_spec_boolean ("forced", "Force processing",
+ "Analyze streams even when ReplayGain tags exist",
+ FORCED_DEFAULT, G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class, PROP_REFERENCE_LEVEL,
+ g_param_spec_double ("reference-level", "Reference level",
+ "Reference level in dB (83.0 for original proposal)",
+ 0.0, G_MAXDOUBLE, 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)
+{
+ 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_DEBUG_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 *mime_type;
+ 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);
+ mime_type = 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 (strcmp (mime_type, "audio/x-raw-float") == 0) {
+
+ 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 (strcmp (mime_type, "audio/x-raw-int") == 0) {
+
+ 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_ERROR);
+ g_return_val_if_fail (filter->analyze != NULL, GST_FLOW_ERROR);
+
+ if (filter->skip)
+ return GST_FLOW_OK;
+
+ GST_DEBUG_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_DEBUG_OBJECT (filter, "Received EOS event");
+
+ gst_rg_analysis_handle_eos (filter);
+
+ GST_DEBUG_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 TRUE;
+}
+
+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_DEBUG_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_INFO_OBJECT (filter, "Ignoring TAG event: Skipping album");
+ return;
+ } else if (filter->skip) {
+ GST_INFO_OBJECT (filter, "Ignoring TAG event: Skipping track");
+ return;
+ } else if (filter->ignore_tags) {
+ GST_INFO_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_INFO_OBJECT (filter, "Track tags not complete yet");
+ return;
+ }
+
+ if (album_processing && !(filter->has_album_gain && filter->has_album_peak)) {
+ GST_INFO_OBJECT (filter, "Album tags not complete yet");
+ return;
+ }
+
+ if (filter->forced) {
+ GST_INFO_OBJECT (filter,
+ "Existing tags are sufficient, but processing anyway (forced)");
+ return;
+ }
+
+ filter->skip = TRUE;
+ rg_analysis_reset (filter->ctx);
+
+ if (!album_processing)
+ GST_INFO_OBJECT (filter,
+ "Existing tags are sufficient, will not process this track");
+ else
+ GST_INFO_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_DEBUG_OBJECT (filter, "Posting tag list with results");
+ /* 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_INFO_OBJECT (filter, "Album not finished yet (num-tracks is now %u)",
+ filter->num_tracks);
+ else
+ GST_INFO_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;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "rganalysis", GST_RANK_NONE,
+ GST_TYPE_RG_ANALYSIS);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "replaygain",
+ "ReplayGain analysis", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN);