gstreamer/tests/check/elements/rtpbin.c
Wim Taymans 3f3b2d0886 rtpbin: handle multiple encoder instances
Keep track of elements that are added to multiple sessions and make sure
we only add them to the rtpbin once and that we clean them when no
session refers to them anymore.
2013-12-30 16:28:57 +01:00

542 lines
15 KiB
C

/* GStreamer
*
* unit test for gstrtpbin
*
* Copyright (C) <2009> Wim Taymans <wim.taymans@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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/check/gstcheck.h>
GST_START_TEST (test_pads)
{
GstElement *element;
GstPad *pad;
element = gst_element_factory_make ("rtpsession", NULL);
pad = gst_element_get_request_pad (element, "recv_rtcp_sink");
gst_object_unref (pad);
gst_object_unref (element);
}
GST_END_TEST;
GST_START_TEST (test_cleanup_send)
{
GstElement *rtpbin;
GstPad *rtp_sink, *rtp_src, *rtcp_src;
GObject *session;
gint count = 2;
rtpbin = gst_element_factory_make ("rtpbin", "rtpbin");
while (count--) {
/* request session 0 */
rtp_sink = gst_element_get_request_pad (rtpbin, "send_rtp_sink_0");
fail_unless (rtp_sink != NULL);
ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2);
/* this static pad should be created automatically now */
rtp_src = gst_element_get_static_pad (rtpbin, "send_rtp_src_0");
fail_unless (rtp_src != NULL);
ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 2);
/* we should be able to get an internal session 0 now */
g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session);
fail_unless (session != NULL);
g_object_unref (session);
/* get the send RTCP pad too */
rtcp_src = gst_element_get_request_pad (rtpbin, "send_rtcp_src_0");
fail_unless (rtcp_src != NULL);
ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtcp_src", 2);
gst_element_release_request_pad (rtpbin, rtp_sink);
/* we should only have our refs to the pads now */
ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 1);
ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 1);
ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtp_src", 2);
/* the other pad should be gone now */
fail_unless (gst_element_get_static_pad (rtpbin, "send_rtp_src_0") == NULL);
/* internal session should still be there */
g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session);
fail_unless (session != NULL);
g_object_unref (session);
/* release the RTCP pad */
gst_element_release_request_pad (rtpbin, rtcp_src);
/* we should only have our refs to the pads now */
ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 1);
ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 1);
ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtp_src", 1);
/* the session should be gone now */
g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session);
fail_unless (session == NULL);
/* unref the request pad and the static pad */
gst_object_unref (rtp_sink);
gst_object_unref (rtp_src);
gst_object_unref (rtcp_src);
}
gst_object_unref (rtpbin);
}
GST_END_TEST;
typedef struct
{
guint16 seqnum;
gboolean pad_added;
GstPad *pad;
GMutex lock;
GCond cond;
GstPad *sinkpad;
GList *pads;
} CleanupData;
static void
init_data (CleanupData * data)
{
data->seqnum = 10;
data->pad_added = FALSE;
g_mutex_init (&data->lock);
g_cond_init (&data->cond);
data->pads = NULL;
}
static void
clean_data (CleanupData * data)
{
g_list_foreach (data->pads, (GFunc) gst_object_unref, NULL);
g_list_free (data->pads);
g_mutex_clear (&data->lock);
g_cond_clear (&data->cond);
}
static guint8 rtp_packet[] = { 0x80, 0x60, 0x94, 0xbc, 0x8f, 0x37, 0x4e, 0xb8,
0x44, 0xa8, 0xf3, 0x7c, 0x06, 0x6a, 0x0c, 0xce,
0x13, 0x25, 0x19, 0x69, 0x1f, 0x93, 0x25, 0x9d,
0x2b, 0x82, 0x31, 0x3b, 0x36, 0xc1, 0x3c, 0x13
};
static GstFlowReturn
chain_rtp_packet (GstPad * pad, CleanupData * data)
{
GstFlowReturn res;
static GstCaps *caps = NULL;
GstSegment segment;
GstBuffer *buffer;
GstMapInfo map;
if (caps == NULL) {
caps = gst_caps_from_string ("application/x-rtp,"
"media=(string)audio, clock-rate=(int)44100, "
"encoding-name=(string)L16, encoding-params=(string)1, channels=(int)1");
data->seqnum = 0;
}
gst_pad_send_event (pad, gst_event_new_stream_start (GST_OBJECT_NAME (pad)));
gst_pad_send_event (pad, gst_event_new_caps (caps));
gst_segment_init (&segment, GST_FORMAT_TIME);
gst_pad_send_event (pad, gst_event_new_segment (&segment));
buffer = gst_buffer_new_and_alloc (sizeof (rtp_packet));
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
memcpy (map.data, rtp_packet, sizeof (rtp_packet));
map.data[2] = (data->seqnum >> 8) & 0xff;
map.data[3] = data->seqnum & 0xff;
data->seqnum++;
gst_buffer_unmap (buffer, &map);
res = gst_pad_chain (pad, buffer);
return res;
}
static GstFlowReturn
dummy_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
gst_buffer_unref (buffer);
return GST_FLOW_OK;
}
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp"));
static GstPad *
make_sinkpad (CleanupData * data)
{
GstPad *pad;
pad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_pad_set_chain_function (pad, dummy_chain);
gst_pad_set_active (pad, TRUE);
data->pads = g_list_prepend (data->pads, pad);
return pad;
}
static void
pad_added_cb (GstElement * rtpbin, GstPad * pad, CleanupData * data)
{
GstPad *sinkpad;
GST_DEBUG ("pad added %s:%s\n", GST_DEBUG_PAD_NAME (pad));
if (GST_PAD_IS_SINK (pad))
return;
fail_unless (data->pad_added == FALSE);
sinkpad = make_sinkpad (data);
fail_unless (gst_pad_link (pad, sinkpad) == GST_PAD_LINK_OK);
g_mutex_lock (&data->lock);
data->pad_added = TRUE;
data->pad = pad;
g_cond_signal (&data->cond);
g_mutex_unlock (&data->lock);
}
static void
pad_removed_cb (GstElement * rtpbin, GstPad * pad, CleanupData * data)
{
GST_DEBUG ("pad removed %s:%s\n", GST_DEBUG_PAD_NAME (pad));
if (data->pad != pad)
return;
fail_unless (data->pad_added == TRUE);
g_mutex_lock (&data->lock);
data->pad_added = FALSE;
g_cond_signal (&data->cond);
g_mutex_unlock (&data->lock);
}
GST_START_TEST (test_cleanup_recv)
{
GstElement *rtpbin;
GstPad *rtp_sink;
CleanupData data;
GstStateChangeReturn ret;
GstFlowReturn res;
gint count = 2;
init_data (&data);
rtpbin = gst_element_factory_make ("rtpbin", "rtpbin");
g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added_cb, &data);
g_signal_connect (rtpbin, "pad-removed", (GCallback) pad_removed_cb, &data);
ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
while (count--) {
/* request session 0 */
rtp_sink = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_0");
fail_unless (rtp_sink != NULL);
ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2);
/* no sourcepads are created yet */
fail_unless (rtpbin->numsinkpads == 1);
fail_unless (rtpbin->numsrcpads == 0);
res = chain_rtp_packet (rtp_sink, &data);
GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res));
fail_unless (res == GST_FLOW_OK);
res = chain_rtp_packet (rtp_sink, &data);
GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res));
fail_unless (res == GST_FLOW_OK);
/* we wait for the new pad to appear now */
g_mutex_lock (&data.lock);
while (!data.pad_added)
g_cond_wait (&data.cond, &data.lock);
g_mutex_unlock (&data.lock);
/* sourcepad created now */
fail_unless (rtpbin->numsinkpads == 1);
fail_unless (rtpbin->numsrcpads == 1);
/* remove the session */
gst_element_release_request_pad (rtpbin, rtp_sink);
gst_object_unref (rtp_sink);
/* pad should be gone now */
g_mutex_lock (&data.lock);
while (data.pad_added)
g_cond_wait (&data.cond, &data.lock);
g_mutex_unlock (&data.lock);
/* nothing left anymore now */
fail_unless (rtpbin->numsinkpads == 0);
fail_unless (rtpbin->numsrcpads == 0);
}
ret = gst_element_set_state (rtpbin, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
gst_object_unref (rtpbin);
clean_data (&data);
}
GST_END_TEST;
GST_START_TEST (test_cleanup_recv2)
{
GstElement *rtpbin;
GstPad *rtp_sink;
CleanupData data;
GstStateChangeReturn ret;
GstFlowReturn res;
gint count = 2;
init_data (&data);
rtpbin = gst_element_factory_make ("rtpbin", "rtpbin");
g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added_cb, &data);
g_signal_connect (rtpbin, "pad-removed", (GCallback) pad_removed_cb, &data);
ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
/* request session 0 */
rtp_sink = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_0");
fail_unless (rtp_sink != NULL);
ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2);
while (count--) {
/* no sourcepads are created yet */
fail_unless (rtpbin->numsinkpads == 1);
fail_unless (rtpbin->numsrcpads == 0);
res = chain_rtp_packet (rtp_sink, &data);
GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res));
fail_unless (res == GST_FLOW_OK);
res = chain_rtp_packet (rtp_sink, &data);
GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res));
fail_unless (res == GST_FLOW_OK);
/* we wait for the new pad to appear now */
g_mutex_lock (&data.lock);
while (!data.pad_added)
g_cond_wait (&data.cond, &data.lock);
g_mutex_unlock (&data.lock);
/* sourcepad created now */
fail_unless (rtpbin->numsinkpads == 1);
fail_unless (rtpbin->numsrcpads == 1);
/* change state */
ret = gst_element_set_state (rtpbin, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
/* pad should be gone now */
g_mutex_lock (&data.lock);
while (data.pad_added)
g_cond_wait (&data.cond, &data.lock);
g_mutex_unlock (&data.lock);
/* back to playing for the next round */
ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
}
/* remove the session */
gst_element_release_request_pad (rtpbin, rtp_sink);
gst_object_unref (rtp_sink);
/* nothing left anymore now */
fail_unless (rtpbin->numsinkpads == 0);
fail_unless (rtpbin->numsrcpads == 0);
ret = gst_element_set_state (rtpbin, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
gst_object_unref (rtpbin);
clean_data (&data);
}
GST_END_TEST;
GST_START_TEST (test_request_pad_by_template_name)
{
GstElement *rtpbin;
GstPad *rtp_sink1, *rtp_sink2, *rtp_sink3;
rtpbin = gst_element_factory_make ("rtpbin", "rtpbin");
rtp_sink1 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u");
fail_unless (rtp_sink1 != NULL);
fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "recv_rtp_sink_0");
ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2);
rtp_sink2 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u");
fail_unless (rtp_sink2 != NULL);
fail_unless_equals_string (GST_PAD_NAME (rtp_sink2), "recv_rtp_sink_1");
ASSERT_OBJECT_REFCOUNT (rtp_sink2, "rtp_sink2", 2);
rtp_sink3 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u");
fail_unless (rtp_sink3 != NULL);
fail_unless_equals_string (GST_PAD_NAME (rtp_sink3), "recv_rtp_sink_2");
ASSERT_OBJECT_REFCOUNT (rtp_sink3, "rtp_sink3", 2);
gst_element_release_request_pad (rtpbin, rtp_sink2);
gst_element_release_request_pad (rtpbin, rtp_sink1);
gst_element_release_request_pad (rtpbin, rtp_sink3);
ASSERT_OBJECT_REFCOUNT (rtp_sink3, "rtp_sink3", 1);
ASSERT_OBJECT_REFCOUNT (rtp_sink2, "rtp_sink2", 1);
ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink", 1);
gst_object_unref (rtp_sink1);
gst_object_unref (rtp_sink2);
gst_object_unref (rtp_sink3);
gst_object_unref (rtpbin);
}
GST_END_TEST;
static GstElement *
encoder_cb (GstElement * rtpbin, guint sessid, GstElement * bin)
{
GstPad *srcpad, *sinkpad;
fail_unless (sessid == 2);
GST_DEBUG ("making encoder");
sinkpad = gst_ghost_pad_new_no_target ("rtp_sink_2", GST_PAD_SINK);
srcpad = gst_ghost_pad_new_no_target ("rtp_src_2", GST_PAD_SRC);
gst_element_add_pad (bin, sinkpad);
gst_element_add_pad (bin, srcpad);
return gst_object_ref (bin);
}
static GstElement *
encoder_cb2 (GstElement * rtpbin, guint sessid, GstElement * bin)
{
GstPad *srcpad, *sinkpad;
fail_unless (sessid == 3);
GST_DEBUG ("making encoder");
sinkpad = gst_ghost_pad_new_no_target ("rtp_sink_3", GST_PAD_SINK);
srcpad = gst_ghost_pad_new_no_target ("rtp_src_3", GST_PAD_SRC);
gst_element_add_pad (bin, sinkpad);
gst_element_add_pad (bin, srcpad);
return gst_object_ref (bin);
}
GST_START_TEST (test_encoder)
{
GstElement *rtpbin, *bin;
GstPad *rtp_sink1, *rtp_sink2;
gulong id;
bin = gst_bin_new ("rtpenc");
rtpbin = gst_element_factory_make ("rtpbin", "rtpbin");
id = g_signal_connect (rtpbin, "request-rtp-encoder", (GCallback) encoder_cb,
bin);
rtp_sink1 = gst_element_get_request_pad (rtpbin, "send_rtp_sink_2");
fail_unless (rtp_sink1 != NULL);
fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "send_rtp_sink_2");
ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2);
g_signal_handler_disconnect (rtpbin, id);
id = g_signal_connect (rtpbin, "request-rtp-encoder", (GCallback) encoder_cb2,
bin);
rtp_sink2 = gst_element_get_request_pad (rtpbin, "send_rtp_sink_3");
fail_unless (rtp_sink2 != NULL);
/* remove the session */
gst_element_release_request_pad (rtpbin, rtp_sink1);
gst_object_unref (rtp_sink1);
gst_element_release_request_pad (rtpbin, rtp_sink2);
gst_object_unref (rtp_sink2);
/* nothing left anymore now */
fail_unless (rtpbin->numsinkpads == 0);
fail_unless (rtpbin->numsrcpads == 0);
gst_object_unref (rtpbin);
gst_object_unref (bin);
}
GST_END_TEST;
static Suite *
gstrtpbin_suite (void)
{
Suite *s = suite_create ("rtpbin");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_pads);
tcase_add_test (tc_chain, test_cleanup_send);
tcase_add_test (tc_chain, test_cleanup_recv);
tcase_add_test (tc_chain, test_cleanup_recv2);
tcase_add_test (tc_chain, test_request_pad_by_template_name);
tcase_add_test (tc_chain, test_encoder);
return s;
}
int
main (int argc, char **argv)
{
int nf;
Suite *s = gstrtpbin_suite ();
SRunner *sr = srunner_create (s);
gst_check_init (&argc, &argv);
srunner_run_all (sr, CK_NORMAL);
nf = srunner_ntests_failed (sr);
srunner_free (sr);
return nf;
}