gstreamer/tests/check/libs/audiodecoder.c
Sebastian Dröge 909dd7831b audiodecoder: Don't be too picky about the output frame counter
With most decoder libraries, and especially when accessing codecs via
OpenMAX or similar APIs, we don't have the ability to properly related
the output buffers to a number of input samples. And could e.g. get
a fractional number of input buffers decoded at a time.

Previously this would in the end lead to an error message and stopped
playback. Change it to a warning message instead and try to handle it
gracefully. In theory the subclass can now get timestamp tracking
wrong if it completely misuses the API, but if on average it behaves
correct (and gst-omx and others do) it will continue to work properly.

Also add a test for the new behaviour.

We don't change it in the encoder yet as that requires more internal logic
changes AFAIU and I'm not aware of a case where this was a problem so far.
2014-06-20 11:02:55 +02:00

665 lines
18 KiB
C

/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <ts.santos@sisa.samsung.com>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/check/gstcheck.h>
#include <gst/audio/audio.h>
#include <gst/app/app.h>
static GstPad *mysrcpad, *mysinkpad;
static GstElement *dec;
static GList *events = NULL;
#define TEST_MSECS_PER_SAMPLE 44100
#define GST_AUDIO_DECODER_TESTER_TYPE gst_audio_decoder_tester_get_type()
static GType gst_audio_decoder_tester_get_type (void);
typedef struct _GstAudioDecoderTester GstAudioDecoderTester;
typedef struct _GstAudioDecoderTesterClass GstAudioDecoderTesterClass;
struct _GstAudioDecoderTester
{
GstAudioDecoder parent;
gboolean setoutputformat_on_decoding;
gboolean output_too_many_frames;
};
struct _GstAudioDecoderTesterClass
{
GstAudioDecoderClass parent_class;
};
G_DEFINE_TYPE (GstAudioDecoderTester, gst_audio_decoder_tester,
GST_TYPE_AUDIO_DECODER);
static gboolean
gst_audio_decoder_tester_start (GstAudioDecoder * dec)
{
return TRUE;
}
static gboolean
gst_audio_decoder_tester_stop (GstAudioDecoder * dec)
{
return TRUE;
}
static void
gst_audio_decoder_tester_flush (GstAudioDecoder * dec, gboolean hard)
{
}
static gboolean
gst_audio_decoder_tester_set_format (GstAudioDecoder * dec, GstCaps * caps)
{
GstAudioDecoderTester *tester = (GstAudioDecoderTester *) dec;
GstAudioInfo info;
gst_caps_unref (caps);
if (!tester->setoutputformat_on_decoding) {
caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S32LE",
"channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100,
"layout", G_TYPE_STRING, "interleaved", NULL);
gst_audio_info_from_caps (&info, caps);
gst_caps_unref (caps);
gst_audio_decoder_set_output_format (dec, &info);
}
return TRUE;
}
static GstFlowReturn
gst_audio_decoder_tester_handle_frame (GstAudioDecoder * dec,
GstBuffer * buffer)
{
GstAudioDecoderTester *tester = (GstAudioDecoderTester *) dec;
guint8 *data;
gint size;
GstMapInfo map;
GstBuffer *output_buffer;
if (buffer == NULL)
return GST_FLOW_OK;
if (tester->setoutputformat_on_decoding) {
GstCaps *caps;
GstAudioInfo info;
caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S32LE",
"channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100,
"layout", G_TYPE_STRING, "interleaved", NULL);
gst_audio_info_from_caps (&info, caps);
gst_caps_unref (caps);
gst_audio_decoder_set_output_format (dec, &info);
}
gst_buffer_map (buffer, &map, GST_MAP_READ);
/* the output is SE32LE stereo 44100 Hz */
size = 2 * 4;
g_assert (size == sizeof (guint64));
data = g_malloc0 (size);
memcpy (data, map.data, sizeof (guint64));
output_buffer = gst_buffer_new_wrapped (data, size);
gst_buffer_unmap (buffer, &map);
if (tester->output_too_many_frames) {
return gst_audio_decoder_finish_frame (dec, output_buffer, 2);
} else {
return gst_audio_decoder_finish_frame (dec, output_buffer, 1);
}
}
static void
gst_audio_decoder_tester_class_init (GstAudioDecoderTesterClass * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstAudioDecoderClass *audiosink_class = GST_AUDIO_DECODER_CLASS (klass);
static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK, GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-test-custom"));
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC, GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw"));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sink_templ));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&src_templ));
gst_element_class_set_metadata (element_class,
"AudioDecoderTester", "Decoder/Audio", "yep", "me");
audiosink_class->start = gst_audio_decoder_tester_start;
audiosink_class->stop = gst_audio_decoder_tester_stop;
audiosink_class->flush = gst_audio_decoder_tester_flush;
audiosink_class->handle_frame = gst_audio_decoder_tester_handle_frame;
audiosink_class->set_format = gst_audio_decoder_tester_set_format;
}
static void
gst_audio_decoder_tester_init (GstAudioDecoderTester * tester)
{
}
static gboolean
_mysinkpad_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
events = g_list_append (events, event);
return TRUE;
}
static void
setup_audiodecodertester (void)
{
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw, format=(string)S32LE, "
"rate=(int)[1, 320000], channels=(int)[1, 32],"
"layout=(string)interleaved")
);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-test-custom")
);
dec = g_object_new (GST_AUDIO_DECODER_TESTER_TYPE, NULL);
mysrcpad = gst_check_setup_src_pad (dec, &srctemplate);
mysinkpad = gst_check_setup_sink_pad (dec, &sinktemplate);
gst_pad_set_event_function (mysinkpad, _mysinkpad_event);
}
static void
cleanup_audiodecodertest (void)
{
gst_pad_set_active (mysrcpad, FALSE);
gst_pad_set_active (mysinkpad, FALSE);
gst_check_teardown_src_pad (dec);
gst_check_teardown_sink_pad (dec);
gst_check_teardown_element (dec);
}
static GstBuffer *
create_test_buffer (guint64 num)
{
GstBuffer *buffer;
guint64 *data = g_malloc (sizeof (guint64));
*data = num;
buffer = gst_buffer_new_wrapped (data, sizeof (guint64));
GST_BUFFER_PTS (buffer) =
gst_util_uint64_scale_round (num, GST_SECOND, TEST_MSECS_PER_SAMPLE);
GST_BUFFER_DURATION (buffer) =
gst_util_uint64_scale_round (1, GST_SECOND, TEST_MSECS_PER_SAMPLE);
return buffer;
}
static void
send_startup_events (void)
{
GstCaps *caps;
fail_unless (gst_pad_push_event (mysrcpad,
gst_event_new_stream_start ("randomvalue")));
/* push caps */
caps =
gst_caps_new_simple ("audio/x-test-custom", "channels", G_TYPE_INT, 2,
"rate", G_TYPE_INT, 44100, NULL);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)));
}
#define NUM_BUFFERS 1000
GST_START_TEST (audiodecoder_playback)
{
GstSegment segment;
GstBuffer *buffer;
guint64 i;
setup_audiodecodertester ();
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push buffers, the data is actually a number so we can track them */
for (i = 0; i < NUM_BUFFERS; i++) {
GstMapInfo map;
guint64 num;
buffer = create_test_buffer (i);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK);
/* check that buffer was received by our source pad */
buffer = buffers->data;
gst_buffer_map (buffer, &map, GST_MAP_READ);
num = *(guint64 *) map.data;
fail_unless_equals_uint64 (i, num);
fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer),
gst_util_uint64_scale_round (i, GST_SECOND, TEST_MSECS_PER_SAMPLE));
fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer),
gst_util_uint64_scale_round (1, GST_SECOND, TEST_MSECS_PER_SAMPLE));
gst_buffer_unmap (buffer, &map);
gst_buffer_unref (buffer);
buffers = g_list_delete_link (buffers, buffers);
}
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
fail_unless (buffers == NULL);
cleanup_audiodecodertest ();
}
GST_END_TEST;
static void
check_audiodecoder_negotiation (void)
{
gboolean received_caps = FALSE;
GList *iter;
for (iter = events; iter; iter = g_list_next (iter)) {
GstEvent *event = iter->data;
if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
GstCaps *caps;
GstStructure *structure;
gint channels;
gint rate;
gst_event_parse_caps (event, &caps);
structure = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (structure, "rate", &rate));
fail_unless (gst_structure_get_int (structure, "channels", &channels));
fail_unless (rate == 44100, "%d != %d", rate, 44100);
fail_unless (channels == 2, "%d != %d", channels, 2);
received_caps = TRUE;
break;
}
}
fail_unless (received_caps);
}
GST_START_TEST (audiodecoder_negotiation_with_buffer)
{
GstSegment segment;
GstBuffer *buffer;
setup_audiodecodertester ();
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push a buffer event to force audiodecoder to push a caps event */
buffer = create_test_buffer (0);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK);
check_audiodecoder_negotiation ();
cleanup_audiodecodertest ();
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
}
GST_END_TEST;
GST_START_TEST (audiodecoder_negotiation_with_gap_event)
{
GstSegment segment;
setup_audiodecodertester ();
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push a gap event to force audiodecoder to push a caps event */
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_gap (0,
GST_SECOND)));
fail_unless (buffers == NULL);
check_audiodecoder_negotiation ();
cleanup_audiodecodertest ();
}
GST_END_TEST;
GST_START_TEST (audiodecoder_delayed_negotiation_with_gap_event)
{
GstSegment segment;
setup_audiodecodertester ();
((GstAudioDecoderTester *) dec)->setoutputformat_on_decoding = TRUE;
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push a gap event to force audiodecoder to push a caps event */
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_gap (0,
GST_SECOND)));
fail_unless (buffers == NULL);
check_audiodecoder_negotiation ();
cleanup_audiodecodertest ();
}
GST_END_TEST;
GST_START_TEST (audiodecoder_flush_events)
{
GstSegment segment;
GstBuffer *buffer;
guint64 i;
GList *events_iter;
setup_audiodecodertester ();
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push buffers, the data is actually a number so we can track them */
for (i = 0; i < NUM_BUFFERS; i++) {
if (i % 10 == 0) {
GstTagList *tags;
tags = gst_tag_list_new (GST_TAG_TRACK_NUMBER, i, NULL);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_tag (tags)));
} else {
buffer = create_test_buffer (i);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK);
}
}
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
events_iter = events;
/* make sure the usual events have been received */
{
GstEvent *sstart = events_iter->data;
fail_unless (GST_EVENT_TYPE (sstart) == GST_EVENT_STREAM_START);
events_iter = g_list_next (events_iter);
}
{
GstEvent *caps_event = events_iter->data;
fail_unless (GST_EVENT_TYPE (caps_event) == GST_EVENT_CAPS);
events_iter = g_list_next (events_iter);
}
{
GstEvent *segment_event = events_iter->data;
fail_unless (GST_EVENT_TYPE (segment_event) == GST_EVENT_SEGMENT);
events_iter = g_list_next (events_iter);
}
/* check that EOS was received */
fail_unless (GST_PAD_IS_EOS (mysrcpad));
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_start ()));
fail_unless (GST_PAD_IS_EOS (mysrcpad));
/* Check that we have tags */
{
GstEvent *tags = gst_pad_get_sticky_event (mysrcpad, GST_EVENT_TAG, 0);
fail_unless (tags != NULL);
gst_event_unref (tags);
}
/* Check that we still have a segment set */
{
GstEvent *segment =
gst_pad_get_sticky_event (mysrcpad, GST_EVENT_SEGMENT, 0);
fail_unless (segment != NULL);
gst_event_unref (segment);
}
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_stop (TRUE)));
fail_if (GST_PAD_IS_EOS (mysrcpad));
/* Check that the segment was flushed on FLUSH_STOP */
{
GstEvent *segment =
gst_pad_get_sticky_event (mysrcpad, GST_EVENT_SEGMENT, 0);
fail_unless (segment == NULL);
}
/* Check the tags were not lost on FLUSH_STOP */
{
GstEvent *tags = gst_pad_get_sticky_event (mysrcpad, GST_EVENT_TAG, 0);
fail_unless (tags != NULL);
gst_event_unref (tags);
}
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
cleanup_audiodecodertest ();
}
GST_END_TEST;
GST_START_TEST (audiodecoder_buffer_after_segment)
{
GstSegment segment;
GstBuffer *buffer;
guint64 i;
GstClockTime pos;
setup_audiodecodertester ();
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
segment.stop = GST_SECOND;
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push buffers, the data is actually a number so we can track them */
i = 0;
pos = 0;
while (pos < GST_SECOND) {
GstMapInfo map;
guint64 num;
buffer = create_test_buffer (i);
pos = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK);
/* check that buffer was received by our source pad */
buffer = buffers->data;
gst_buffer_map (buffer, &map, GST_MAP_READ);
num = *(guint64 *) map.data;
fail_unless_equals_uint64 (i, num);
fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer),
gst_util_uint64_scale_round (i, GST_SECOND, TEST_MSECS_PER_SAMPLE));
fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer),
gst_util_uint64_scale_round (1, GST_SECOND, TEST_MSECS_PER_SAMPLE));
gst_buffer_unmap (buffer, &map);
gst_buffer_unref (buffer);
buffers = g_list_delete_link (buffers, buffers);
i++;
}
/* this buffer is after the segment */
buffer = create_test_buffer (i++);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_EOS);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
fail_unless (buffers == NULL);
cleanup_audiodecodertest ();
}
GST_END_TEST;
GST_START_TEST (audiodecoder_output_too_many_frames)
{
GstSegment segment;
GstBuffer *buffer;
guint64 i;
setup_audiodecodertester ();
((GstAudioDecoderTester *) dec)->output_too_many_frames = TRUE;
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
send_startup_events ();
/* push a new segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
/* push buffers, the data is actually a number so we can track them */
for (i = 0; i < 3; i++) {
GstMapInfo map;
guint64 num;
buffer = create_test_buffer (i);
fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK);
/* check that buffer was received by our source pad */
buffer = buffers->data;
gst_buffer_map (buffer, &map, GST_MAP_READ);
num = *(guint64 *) map.data;
fail_unless_equals_uint64 (i, num);
fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer),
gst_util_uint64_scale_round (i, GST_SECOND, TEST_MSECS_PER_SAMPLE));
fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer),
gst_util_uint64_scale_round (1, GST_SECOND, TEST_MSECS_PER_SAMPLE));
gst_buffer_unmap (buffer, &map);
gst_buffer_unref (buffer);
buffers = g_list_delete_link (buffers, buffers);
}
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
fail_unless (buffers == NULL);
cleanup_audiodecodertest ();
}
GST_END_TEST;
static Suite *
gst_audiodecoder_suite (void)
{
Suite *s = suite_create ("GstAudioDecoder");
TCase *tc = tcase_create ("general");
suite_add_tcase (s, tc);
tcase_add_test (tc, audiodecoder_playback);
tcase_add_test (tc, audiodecoder_flush_events);
tcase_add_test (tc, audiodecoder_negotiation_with_buffer);
tcase_add_test (tc, audiodecoder_negotiation_with_gap_event);
tcase_add_test (tc, audiodecoder_delayed_negotiation_with_gap_event);
tcase_add_test (tc, audiodecoder_buffer_after_segment);
tcase_add_test (tc, audiodecoder_output_too_many_frames);
return s;
}
GST_CHECK_MAIN (gst_audiodecoder);