gstreamer/tests/check/pipelines/oggmux.c
Tim-Philipp Müller 099989ff0f oggmux: don't drop the streamheader field from the output caps
Revert previous 'fix' for bug #588717 and fix it properly, whilst
maintaining the streamheader field on the output caps. Also make
sure we don't leak header buffers we couldn't push when downstream
is unlinked. Add unit test for the presence of the streamheader
field on the output caps and for the issue from bug #588717.
2009-08-20 13:14:19 +01:00

431 lines
12 KiB
C

/* GStreamer
*
* unit tests for oggmux
*
* Copyright (C) 2006 James Livingston <doclivingston at gmail.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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <gst/check/gstcheck.h>
#include <ogg/ogg.h>
typedef enum
{
CODEC_UNKNOWN,
CODEC_VORBIS,
CODEC_THEORA,
CODEC_SPEEX,
} ChainCodec;
typedef struct
{
gboolean eos;
gulong serialno;
gint64 last_granule;
ChainCodec codec;
} ChainState;
static ogg_sync_state oggsync;
static GHashTable *eos_chain_states;
static gulong probe_id;
static ChainCodec
get_page_codec (ogg_page * page)
{
ChainCodec codec = CODEC_UNKNOWN;
ogg_stream_state state;
ogg_packet packet;
ogg_stream_init (&state, ogg_page_serialno (page));
if (ogg_stream_pagein (&state, page) == 0) {
if (ogg_stream_packetpeek (&state, &packet) > 0) {
if (strncmp ((char *) &packet.packet[1], "vorbis",
strlen ("vorbis")) == 0)
codec = CODEC_VORBIS;
else if (strncmp ((char *) &packet.packet[1], "theora",
strlen ("theora")) == 0)
codec = CODEC_THEORA;
else if (strncmp ((char *) &packet.packet[0], "Speex ",
strlen ("Speex ")) == 0)
codec = CODEC_SPEEX;
}
}
ogg_stream_clear (&state);
return codec;
}
static gboolean
check_chain_final_state (gpointer key, ChainState * state, gpointer data)
{
fail_unless (state->eos, "missing EOS flag on chain %u", state->serialno);
/* return TRUE to empty the chain table */
return TRUE;
}
static void
fail_if_audio (gpointer key, ChainState * state, gpointer data)
{
fail_if (state->codec == CODEC_VORBIS,
"vorbis BOS occurred before theora BOS");
fail_if (state->codec == CODEC_SPEEX, "speex BOS occurred before theora BOS");
}
static ChainState *
validate_ogg_page (ogg_page * page)
{
gulong serialno;
gint64 granule;
ChainState *state;
serialno = ogg_page_serialno (page);
granule = ogg_page_granulepos (page);
state = g_hash_table_lookup (eos_chain_states, GINT_TO_POINTER (serialno));
fail_if (ogg_page_packets (page) == 0 && granule != -1,
"Must have granulepos -1 when page has no packets, has %" G_GINT64_FORMAT,
granule);
if (ogg_page_bos (page)) {
fail_unless (state == NULL, "Extraneous BOS flag on chain %u", serialno);
state = g_new0 (ChainState, 1);
g_hash_table_insert (eos_chain_states, GINT_TO_POINTER (serialno), state);
state->serialno = serialno;
state->last_granule = granule;
state->codec = get_page_codec (page);
/* check for things like BOS ordering, etc */
switch (state->codec) {
case CODEC_THEORA:
/* check we have no vorbis/speex chains yet */
g_hash_table_foreach (eos_chain_states, (GHFunc) fail_if_audio, NULL);
break;
case CODEC_VORBIS:
case CODEC_SPEEX:
/* no checks (yet) */
break;
case CODEC_UNKNOWN:
default:
break;
}
} else if (ogg_page_eos (page)) {
fail_unless (state != NULL, "Missing BOS flag on chain %u", serialno);
state->eos = TRUE;
} else {
fail_unless (state != NULL, "Missing BOS flag on chain %u", serialno);
fail_unless (!state->eos, "Data after EOS flag on chain %u", serialno);
}
if (granule != -1) {
fail_unless (granule >= state->last_granule,
"Granulepos out-of-order for chain %u: old=%" G_GINT64_FORMAT ", new="
G_GINT64_FORMAT, serialno, state->last_granule, granule);
state->last_granule = granule;
}
return state;
}
static void
is_video (gpointer key, ChainState * state, gpointer data)
{
if (state->codec == CODEC_THEORA)
*((gboolean *) data) = TRUE;
}
static gboolean
eos_buffer_probe (GstPad * pad, GstBuffer * buffer, gpointer unused)
{
gint ret;
gint size;
guint8 *data;
gchar *oggbuffer;
ChainState *state = NULL;
gboolean has_video = FALSE;
size = GST_BUFFER_SIZE (buffer);
data = GST_BUFFER_DATA (buffer);
oggbuffer = ogg_sync_buffer (&oggsync, size);
memcpy (oggbuffer, data, size);
ogg_sync_wrote (&oggsync, size);
do {
ogg_page page;
ret = ogg_sync_pageout (&oggsync, &page);
if (ret > 0)
state = validate_ogg_page (&page);
}
while (ret != 0);
if (state) {
/* Now, we can do buffer-level checks...
* If we have video somewhere, then we should have DELTA_UNIT set on all
* non-header (not IN_CAPS), non-video buffers
*/
g_hash_table_foreach (eos_chain_states, (GHFunc) is_video, &has_video);
if (has_video && state->codec != CODEC_THEORA) {
if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_IN_CAPS))
fail_unless (GST_BUFFER_FLAG_IS_SET (buffer,
GST_BUFFER_FLAG_DELTA_UNIT),
"Non-video buffer doesn't have DELTA_UNIT in stream with video");
}
}
return TRUE;
}
static void
start_pipeline (GstElement * bin, GstPad * pad)
{
GstStateChangeReturn ret;
ogg_sync_init (&oggsync);
eos_chain_states =
g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
probe_id =
gst_pad_add_buffer_probe (pad, G_CALLBACK (eos_buffer_probe), NULL);
ret = gst_element_set_state (bin, GST_STATE_PLAYING);
fail_if (ret == GST_STATE_CHANGE_FAILURE, "Could not start test pipeline");
if (ret == GST_STATE_CHANGE_ASYNC) {
ret = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "Could not start test pipeline");
}
}
static void
stop_pipeline (GstElement * bin, GstPad * pad)
{
GstStateChangeReturn ret;
ret = gst_element_set_state (bin, GST_STATE_NULL);
fail_if (ret == GST_STATE_CHANGE_FAILURE, "Could not stop test pipeline");
if (ret == GST_STATE_CHANGE_ASYNC) {
ret = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "Could not stop test pipeline");
}
gst_pad_remove_buffer_probe (pad, (guint) probe_id);
ogg_sync_clear (&oggsync);
/* check end conditions, such as EOS flags */
g_hash_table_foreach_remove (eos_chain_states,
(GHRFunc) check_chain_final_state, NULL);
}
static gboolean
eos_watch (GstBus * bus, GstMessage * message, GMainLoop * loop)
{
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS) {
g_main_loop_quit (loop);
}
return TRUE;
}
static void
test_pipeline (const char *pipeline)
{
GstElement *bin, *sink;
GstPad *pad, *sinkpad;
GstBus *bus;
GError *error = NULL;
GMainLoop *loop;
GstPadLinkReturn linkret;
bin = gst_parse_launch (pipeline, &error);
fail_unless (bin != NULL, "Error parsing pipeline: %s",
error ? error->message : "(invalid error)");
pad = gst_bin_find_unlinked_pad (GST_BIN (bin), GST_PAD_SRC);
fail_unless (pad != NULL, "Could not locate free src pad");
/* connect the fake sink */
sink = gst_element_factory_make ("fakesink", "fake_sink");
fail_unless (sink != NULL, "Could create fakesink");
fail_unless (gst_bin_add (GST_BIN (bin), sink), "Could not insert fakesink");
sinkpad = gst_element_get_static_pad (sink, "sink");
fail_unless (sinkpad != NULL, "Could not get fakesink src pad");
linkret = gst_pad_link (pad, sinkpad);
fail_unless (GST_PAD_LINK_SUCCESSFUL (linkret),
"Could not link to fake sink");
gst_object_unref (sinkpad);
/* run until we receive EOS */
loop = g_main_loop_new (NULL, FALSE);
bus = gst_element_get_bus (bin);
gst_bus_add_watch (bus, (GstBusFunc) eos_watch, loop);
gst_object_unref (bus);
start_pipeline (bin, pad);
g_main_loop_run (loop);
/* we're EOS now; make sure oggmux out caps have stream headers on them */
{
GstStructure *s;
GstCaps *muxcaps;
muxcaps = gst_pad_get_negotiated_caps (sinkpad);
fail_unless (muxcaps != NULL);
s = gst_caps_get_structure (muxcaps, 0);
fail_unless (gst_structure_has_name (s, "application/ogg"));
fail_unless (gst_structure_has_field (s, "streamheader"));
fail_unless (gst_structure_has_field_typed (s, "streamheader",
GST_TYPE_ARRAY));
gst_caps_unref (muxcaps);
}
stop_pipeline (bin, pad);
/* clean up */
g_main_loop_unref (loop);
gst_object_unref (pad);
gst_object_unref (bin);
}
#ifdef HAVE_VORBIS
GST_START_TEST (test_vorbis)
{
test_pipeline
("audiotestsrc num-buffers=5 ! audioconvert ! vorbisenc ! oggmux");
}
GST_END_TEST;
GST_START_TEST (test_vorbis_oggmux_unlinked)
{
GstElement *pipe;
GstMessage *msg;
pipe = gst_parse_launch ("audiotestsrc ! vorbisenc ! oggmux", NULL);
if (pipe == NULL) {
g_printerr ("Skipping test 'test_vorbis_oggmux_unlinked'");
return;
}
/* no sink, no async state change */
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PAUSED),
GST_STATE_CHANGE_SUCCESS);
/* we expect an error (without any criticals/warnings) */
msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1,
GST_MESSAGE_ERROR);
gst_message_unref (msg);
fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
GST_STATE_CHANGE_SUCCESS);
gst_object_unref (pipe);
}
GST_END_TEST;
#endif
#ifdef HAVE_THEORA
GST_START_TEST (test_theora)
{
test_pipeline
("videotestsrc num-buffers=5 ! ffmpegcolorspace ! theoraenc ! oggmux");
}
GST_END_TEST;
#endif
#if (defined (HAVE_THEORA) && defined (HAVE_VORBIS))
GST_START_TEST (test_theora_vorbis)
{
test_pipeline
("videotestsrc num-buffers=10 ! ffmpegcolorspace ! theoraenc ! queue ! oggmux name=mux "
"audiotestsrc num-buffers=2 ! audioconvert ! vorbisenc ! queue ! mux.");
}
GST_END_TEST;
GST_START_TEST (test_vorbis_theora)
{
test_pipeline
("videotestsrc num-buffers=2 ! ffmpegcolorspace ! theoraenc ! queue ! oggmux name=mux "
"audiotestsrc num-buffers=10 ! audioconvert ! vorbisenc ! queue ! mux.");
}
GST_END_TEST;
#endif
GST_START_TEST (test_simple_cleanup)
{
GstElement *oggmux;
oggmux = gst_element_factory_make ("oggmux", NULL);
gst_object_unref (oggmux);
}
GST_END_TEST;
GST_START_TEST (test_request_pad_cleanup)
{
GstElement *oggmux;
GstPad *pad;
oggmux = gst_element_factory_make ("oggmux", NULL);
pad = gst_element_get_request_pad (oggmux, "sink_%d");
fail_unless (pad != NULL);
gst_object_unref (pad);
pad = gst_element_get_request_pad (oggmux, "sink_%d");
fail_unless (pad != NULL);
gst_object_unref (pad);
gst_object_unref (oggmux);
}
GST_END_TEST;
static Suite *
oggmux_suite (void)
{
Suite *s = suite_create ("oggmux");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
#ifdef HAVE_VORBIS
tcase_add_test (tc_chain, test_vorbis);
tcase_add_test (tc_chain, test_vorbis_oggmux_unlinked);
#endif
#ifdef HAVE_THEORA
tcase_add_test (tc_chain, test_theora);
#endif
#if (defined (HAVE_THEORA) && defined (HAVE_VORBIS))
tcase_add_test (tc_chain, test_vorbis_theora);
tcase_add_test (tc_chain, test_theora_vorbis);
#endif
tcase_add_test (tc_chain, test_simple_cleanup);
tcase_add_test (tc_chain, test_request_pad_cleanup);
return s;
}
GST_CHECK_MAIN (oggmux);