gstreamer/tests/check/elements/rgvolume.c
Tim-Philipp Müller a4d64b5caa rgvolume: make tag list writable before modifying it
Making the event itself writable is not enough, it won't make
the actual taglist in the event writable as well. Instead, just
make a copy of the taglist and then create a new tag event from
that if required, replacing the old one. Before we would
inadvertently modify taglists upstream elements might still
be holding on to. Add unit test for this as well.

https://bugzilla.gnome.org/show_bug.cgi?id=762793
2016-02-28 14:44:39 +00:00

678 lines
20 KiB
C

/* 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 <gst/audio/audio.h>
#include <math.h>
static 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, " \
"format = (string) "GST_AUDIO_NE (F32) ", " \
"layout = (string) interleaved, " \
"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)
);
static GstBuffer *test_buffer_new (gfloat value);
/* 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, GstObject * parent, GstEvent * event)
{
GST_DEBUG ("received event %p (%s)", event, GST_EVENT_TYPE_NAME (event));
events = g_list_append (events, event);
return TRUE;
}
static GstElement *
setup_rgvolume (void)
{
GstElement *element;
GST_DEBUG ("setup_rgvolume");
element = gst_check_setup_element ("rgvolume");
mysrcpad = gst_check_setup_src_pad (element, &srctemplate);
mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate);
/* 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;
}
static void
send_empty_buffer (void)
{
GstBuffer *buf;
buf = test_buffer_new (0.0);
gst_buffer_resize (buf, 0, 0);
GST_BUFFER_DURATION (buf) = 0;
GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf);
fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
fail_unless (g_list_length (buffers) == 1);
fail_unless (buffers->data == buf);
gst_mini_object_unref ((GstMiniObject *) buffers->data);
buffers = g_list_remove (buffers, buf);
}
static 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_flush_events (GstElement * element)
{
gboolean res;
res = gst_pad_push_event (mysrcpad, gst_event_new_flush_start ());
fail_unless (res, "flush-start even not handled");
res = gst_pad_push_event (mysrcpad, gst_event_new_flush_stop (TRUE));
fail_unless (res, "flush-stop event not handled");
}
static void
send_stream_start_event (GstElement * element)
{
gboolean res;
res = gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));
fail_unless (res, "STREAM_START event not handled");
}
static void
send_caps_event (GstElement * element)
{
GstCaps *caps;
gboolean res;
caps = gst_caps_from_string ("audio/x-raw, format = " GST_AUDIO_NE (F32) ", "
"layout = interleaved, rate = 8000, channels = 1");
res = gst_pad_push_event (mysrcpad, gst_event_new_caps (caps));
fail_unless (res, "CAPS event not handled");
gst_caps_unref (caps);
}
static void
send_segment_event (GstElement * element)
{
GstSegment segment;
gboolean res;
gst_segment_init (&segment, GST_FORMAT_TIME);
res = gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment));
fail_unless (res, "SEGMENT event not handled");
}
static void
send_eos_event (GstElement * element)
{
GstEvent *event = gst_event_new_eos ();
fail_unless (gst_pad_push_event (mysrcpad, event),
"Pushing EOS event failed");
}
static GstEvent *
send_tag_event (GstElement * element, GstEvent * event)
{
GList *l;
GstTagList *tag_list;
gdouble dummy;
g_return_val_if_fail (event->type == GST_EVENT_TAG, NULL);
fail_unless (gst_pad_push_event (mysrcpad, event),
"Pushing tag event failed");
event = NULL;
for (l = g_list_last (events); l; l = l->prev) {
if (GST_EVENT_TYPE (l->data) == GST_EVENT_TAG) {
event = l->data;
events = g_list_delete_link (events, l);
break;
}
}
/* Event got filtered out */
if (event == NULL)
return NULL;
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;
GstMapInfo map;
gfloat *data;
gint i;
buf = gst_buffer_new_and_alloc (8 * sizeof (gfloat));
gst_buffer_map (buf, &map, GST_MAP_WRITE);
data = (gfloat *) map.data;
for (i = 0; i < 8; i++)
data[i] = value;
gst_buffer_unmap (buf, &map);
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 *data;
gfloat input_sample, output_sample;
gdouble gain, prop_gain;
gboolean is_passthrough, expect_passthrough;
gint i;
GstMapInfo map;
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_get_size (output_buf),
8 * sizeof (gfloat));
gst_buffer_map (output_buf, &map, GST_MAP_READ);
data = (gfloat *) map.data;
output_sample = *data;
fail_if (output_sample == 0.0, "First output sample is zero");
for (i = 1; i < 8; i++) {
fail_unless (output_sample == data[i], "Output samples not uniform");
};
gst_buffer_unmap (output_buf, &map);
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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);
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_empty ();
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);
gst_tag_list_ref (tag_list);
event = gst_event_new_tag (tag_list);
new_event = send_tag_event (element, event);
/* Make sure our tags weren't modified in place while we still got a ref */
fail_unless_equals_int (5, gst_tag_list_n_tags (tag_list));
gst_tag_list_unref (tag_list);
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);
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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);
send_flush_events (element);
send_segment_event (element);
tag_list = gst_tag_list_new_empty ();
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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. */
send_flush_events (element);
send_segment_event (element);
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. */
send_flush_events (element);
send_segment_event (element);
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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);
send_flush_events (element);
send_segment_event (element);
g_object_set (element, "headroom", +2.00, NULL);
tag_list = gst_tag_list_new_empty ();
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);
send_flush_events (element);
send_segment_event (element);
g_object_set (element, "album-mode", TRUE, NULL);
tag_list = gst_tag_list_new_empty ();
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);
send_stream_start_event (element);
send_caps_event (element);
send_segment_event (element);
send_empty_buffer ();
tag_list = gst_tag_list_new_empty ();
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. */
send_flush_events (element);
send_segment_event (element);
tag_list = gst_tag_list_new_empty ();
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;
static 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;
}
GST_CHECK_MAIN (rgvolume);