From 41dd6399a6afdb4db9c2f299fee60bc813b6dd7e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 29 Jan 2009 13:31:27 +0100 Subject: [PATCH] Reorganize things, prepare for media sharing Added various other test server examples Move the SDP message generation to a separate helper. Refactor common code for finding the session. Add content-base for realplayer compatibility Clean up request uris before processing for better vlc compatibility. Move prerolling and pipeline construction to the RTSPMedia object. Use multiudpsink for future pipeline reuse. --- examples/Makefile.am | 10 +- examples/test-mp4.c | 77 ++++++ examples/test-ogg.c | 77 ++++++ examples/{main.c => test-video.c} | 3 +- gst/rtsp-server/Makefile.am | 2 + gst/rtsp-server/rtsp-client.c | 481 ++++++++++++++--------------------- gst/rtsp-server/rtsp-client.h | 6 +- gst/rtsp-server/rtsp-media-factory.c | 27 +- gst/rtsp-server/rtsp-media-factory.h | 19 +- gst/rtsp-server/rtsp-media.c | 435 +++++++++++++++++++++++++++++++ gst/rtsp-server/rtsp-media.h | 68 ++++- gst/rtsp-server/rtsp-sdp.c | 145 +++++++++++ gst/rtsp-server/rtsp-sdp.h | 35 +++ gst/rtsp-server/rtsp-server.c | 46 ++-- gst/rtsp-server/rtsp-server.h | 21 +- gst/rtsp-server/rtsp-session.c | 315 ++++------------------- gst/rtsp-server/rtsp-session.h | 40 +-- 17 files changed, 1140 insertions(+), 667 deletions(-) create mode 100644 examples/test-mp4.c create mode 100644 examples/test-ogg.c rename examples/{main.c => test-video.c} (97%) create mode 100644 gst/rtsp-server/rtsp-sdp.c create mode 100644 gst/rtsp-server/rtsp-sdp.h diff --git a/examples/Makefile.am b/examples/Makefile.am index 062f595..1241eda 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,13 +1,9 @@ - -noinst_PROGRAMS = gst-rtsp-server +noinst_PROGRAMS = test-video test-ogg test-mp4 INCLUDES = -I$(top_srcdir) -I$(srcdir) -gst_rtsp_server_SOURCES = \ - main.c - -gst_rtsp_server_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) -gst_rtsp_server_LDFLAGS = \ +AM_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +AM_LDFLAGS = \ $(GST_LIBS) \ $(top_builddir)/gst/rtsp-server/libgstrtspserver-@GST_MAJORMINOR@.la diff --git a/examples/test-mp4.c b/examples/test-mp4.c new file mode 100644 index 0000000..a7756e2 --- /dev/null +++ b/examples/test-mp4.c @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * + * 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. + */ + +#include + +#include + +int +main (int argc, char *argv[]) +{ + GMainLoop *loop; + GstRTSPServer *server; + GstRTSPMediaMapping *mapping; + GstRTSPMediaFactory *factory; + gchar *str; + + gst_init (&argc, &argv); + + if (argc < 2) { + g_message ("usage: %s ", argv[0]); + return -1; + } + + loop = g_main_loop_new (NULL, FALSE); + + /* create a server instance */ + server = gst_rtsp_server_new (); + + /* get the mapping for this server, every server has a default mapper object + * that be used to map uri mount points to media factories */ + mapping = gst_rtsp_server_get_media_mapping (server); + + str = g_strdup_printf ( + "( " + "filesrc location=%s ! qtdemux name=d " + "d. ! queue ! rtph264pay pt=96 name=pay0 " + "d. ! queue ! rtpmp4apay pt=97 name=pay1 " + ")", argv[1]); + + /* make a media factory for a test stream. The default media factory can use + * gst-launch syntax to create pipelines. + * any launch line works as long as it contains elements named pay%d. Each + * element with pay%d names will be a stream */ + factory = gst_rtsp_media_factory_new (); + gst_rtsp_media_factory_set_launch (factory, str); + g_free (str); + + /* attach the test factory to the /test url */ + gst_rtsp_media_mapping_add_factory (mapping, "/test", factory); + + /* don't need the ref to the mapper anymore */ + g_object_unref (mapping); + + /* attach the server to the default maincontext */ + gst_rtsp_server_attach (server, NULL); + + /* start serving */ + g_main_loop_run (loop); + + return 0; +} diff --git a/examples/test-ogg.c b/examples/test-ogg.c new file mode 100644 index 0000000..0e39f01 --- /dev/null +++ b/examples/test-ogg.c @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * + * 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. + */ + +#include + +#include + +int +main (int argc, char *argv[]) +{ + GMainLoop *loop; + GstRTSPServer *server; + GstRTSPMediaMapping *mapping; + GstRTSPMediaFactory *factory; + gchar *str; + + gst_init (&argc, &argv); + + if (argc < 2) { + g_message ("usage: %s ", argv[0]); + return -1; + } + + loop = g_main_loop_new (NULL, FALSE); + + /* create a server instance */ + server = gst_rtsp_server_new (); + + /* get the mapping for this server, every server has a default mapper object + * that be used to map uri mount points to media factories */ + mapping = gst_rtsp_server_get_media_mapping (server); + + str = g_strdup_printf ( + "( " + "filesrc location=%s ! oggdemux name=d " + "d. ! queue ! theoraparse ! rtptheorapay name=pay0 " + "d. ! queue ! vorbisparse ! rtpvorbispay name=pay1 " + ")", argv[1]); + + /* make a media factory for a test stream. The default media factory can use + * gst-launch syntax to create pipelines. + * any launch line works as long as it contains elements named pay%d. Each + * element with pay%d names will be a stream */ + factory = gst_rtsp_media_factory_new (); + gst_rtsp_media_factory_set_launch (factory, str); + g_free (str); + + /* attach the test factory to the /test url */ + gst_rtsp_media_mapping_add_factory (mapping, "/test", factory); + + /* don't need the ref to the mapper anymore */ + g_object_unref (mapping); + + /* attach the server to the default maincontext */ + gst_rtsp_server_attach (server, NULL); + + /* start serving */ + g_main_loop_run (loop); + + return 0; +} diff --git a/examples/main.c b/examples/test-video.c similarity index 97% rename from examples/main.c rename to examples/test-video.c index 3e00d41..b03bc6c 100644 --- a/examples/main.c +++ b/examples/test-video.c @@ -47,10 +47,11 @@ main (int argc, char *argv[]) factory = gst_rtsp_media_factory_new (); gst_rtsp_media_factory_set_launch (factory, "( " "videotestsrc ! video/x-raw-yuv,width=352,height=288,framerate=15/1 ! " - "x264enc bitrate=300 ! rtph264pay name=pay0 pt=96 " + "ffenc_h263 ! rtph263pay name=pay0 pt=96 " "audiotestsrc ! audio/x-raw-int,rate=8000 ! " "alawenc ! rtppcmapay name=pay1 pt=97 " ")"); + /* attach the test factory to the /test url */ gst_rtsp_media_mapping_add_factory (mapping, "/test", factory); diff --git a/gst/rtsp-server/Makefile.am b/gst/rtsp-server/Makefile.am index 54ba251..68fa7fa 100644 --- a/gst/rtsp-server/Makefile.am +++ b/gst/rtsp-server/Makefile.am @@ -4,6 +4,7 @@ public_headers = \ rtsp-media.h \ rtsp-media-factory.h \ rtsp-media-mapping.h \ + rtsp-sdp.h \ rtsp-session-pool.h \ rtsp-session.h @@ -13,6 +14,7 @@ c_sources = \ rtsp-media.c \ rtsp-media-factory.c \ rtsp-media-mapping.c \ + rtsp-sdp.c \ rtsp-session-pool.c \ rtsp-session.c diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 4d518fe..56d9320 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -19,9 +19,8 @@ #include -#include - #include "rtsp-client.h" +#include "rtsp-sdp.h" #undef DEBUG @@ -66,6 +65,16 @@ gst_rtsp_client_new (void) } static void +handle_response (GstRTSPClient *client, GstRTSPMessage *response) +{ +#ifdef DEBUG + gst_rtsp_message_dump (response); +#endif + + gst_rtsp_connection_send (client->connection, response, NULL); +} + +static void handle_generic_response (GstRTSPClient *client, GstRTSPStatusCode code, GstRTSPMessage *request) { @@ -74,36 +83,101 @@ handle_generic_response (GstRTSPClient *client, GstRTSPStatusCode code, gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); } -static gboolean -handle_teardown_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *request) +static GstRTSPMedia * +find_media (GstRTSPClient *client, const GstRTSPUrl *uri, GstRTSPMessage *request) +{ + GstRTSPMediaFactory *factory; + GstRTSPMedia *media; + + /* find the factory for the uri first */ + if (!(factory = gst_rtsp_media_mapping_find_factory (client->media_mapping, uri))) + goto no_factory; + + /* prepare the media and add it to the pipeline */ + if (!(media = gst_rtsp_media_factory_construct (factory, uri))) + goto no_media; + + /* prepare the media */ + if (!(gst_rtsp_media_prepare (media))) + goto no_prepare; + + return media; + + /* ERRORS */ +no_factory: + { + handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); + return NULL; + } +no_media: + { + handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + g_object_unref (factory); + return NULL; + } +no_prepare: + { + handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + g_object_unref (media); + g_object_unref (factory); + return NULL; + } +} + +/* Get the session or NULL when there was no session */ +static GstRTSPSession * +ensure_session (GstRTSPClient *client, GstRTSPMessage *request) { GstRTSPResult res; - GstRTSPSessionMedia *media; GstRTSPSession *session; gchar *sessid; - GstRTSPMessage response = { 0 }; - GstRTSPStatusCode code; res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); if (res == GST_RTSP_OK) { /* we had a session in the request, find it again */ - if (!(session = gst_rtsp_session_pool_find (client->pool, sessid))) + if (!(session = gst_rtsp_session_pool_find (client->session_pool, sessid))) goto session_not_found; } else goto service_unavailable; + return session; + + /* ERRORS */ +session_not_found: + { + handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); + return NULL; + } +service_unavailable: + { + handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + return NULL; + } +} + +static gboolean +handle_teardown_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *request) +{ + GstRTSPSessionMedia *media; + GstRTSPSession *session; + GstRTSPMessage response = { 0 }; + GstRTSPStatusCode code; + + if (!(session = ensure_session (client, request))) + goto no_session; + /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, uri, client->factory); + media = gst_rtsp_session_get_media (session, uri); if (!media) goto not_found; gst_rtsp_session_media_stop (media); - gst_rtsp_session_pool_remove (client->pool, session); + gst_rtsp_session_pool_remove (client->session_pool, session); g_object_unref (session); /* remove the session id from the request, which will also remove it from the @@ -114,19 +188,14 @@ handle_teardown_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage code = GST_RTSP_STS_OK; gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); return FALSE; /* ERRORS */ -session_not_found: +no_session: { - handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); - return FALSE; - } -service_unavailable: - { - handle_generic_response (client, GST_RTSP_STS_OK, request); + /* error was sent already */ return FALSE; } not_found: @@ -139,24 +208,16 @@ not_found: static gboolean handle_pause_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *request) { - GstRTSPResult res; GstRTSPSessionMedia *media; GstRTSPSession *session; - gchar *sessid; GstRTSPMessage response = { 0 }; GstRTSPStatusCode code; - res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); - if (res == GST_RTSP_OK) { - /* we had a session in the request, find it again */ - if (!(session = gst_rtsp_session_pool_find (client->pool, sessid))) - goto session_not_found; - } - else - goto service_unavailable; + if (!(session = ensure_session (client, request))) + goto no_session; /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, uri, client->factory); + media = gst_rtsp_session_get_media (session, uri); if (!media) goto not_found; @@ -167,17 +228,12 @@ handle_pause_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *r code = GST_RTSP_STS_OK; gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); return FALSE; /* ERRORS */ -session_not_found: - { - handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); - return FALSE; - } -service_unavailable: +no_session: { return FALSE; } @@ -191,48 +247,25 @@ not_found: static gboolean handle_play_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *request) { - GstRTSPResult res; GstRTSPSessionMedia *media; GstRTSPSession *session; - gchar *sessid; GstRTSPMessage response = { 0 }; GstRTSPStatusCode code; - GstStateChangeReturn ret; GString *rtpinfo; guint n_streams, i; guint timestamp, seqnum; - res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); - if (res == GST_RTSP_OK) { - /* we had a session in the request, find it again */ - if (!(session = gst_rtsp_session_pool_find (client->pool, sessid))) - goto session_not_found; - } - else - goto service_unavailable; + if (!(session = ensure_session (client, request))) + goto no_session; /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, uri, client->factory); + media = gst_rtsp_session_get_media (session, uri); if (!media) goto not_found; - /* wait for paused to get the caps */ - ret = gst_rtsp_session_media_pause (media); - switch (ret) { - case GST_STATE_CHANGE_NO_PREROLL: - break; - case GST_STATE_CHANGE_SUCCESS: - break; - case GST_STATE_CHANGE_FAILURE: - goto service_unavailable; - case GST_STATE_CHANGE_ASYNC: - /* wait for paused state change to complete */ - ret = gst_element_get_state (media->pipeline, NULL, NULL, -1); - break; - } - /* grab RTPInfo from the payloaders now */ rtpinfo = g_string_new (""); + n_streams = gst_rtsp_media_n_streams (media->media); for (i = 0; i < n_streams; i++) { GstRTSPMediaStream *stream; @@ -259,7 +292,7 @@ handle_play_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *re gst_rtsp_message_add_header (&response, GST_RTSP_HDR_RTP_INFO, rtpinfo->str); g_string_free (rtpinfo, TRUE); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); /* start playing after sending the request */ gst_rtsp_session_media_play (media); @@ -268,14 +301,9 @@ handle_play_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *re return FALSE; /* ERRORS */ -session_not_found: - { - handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); - return FALSE; - } -service_unavailable: +no_session: { - handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + /* error was sent */ return FALSE; } not_found: @@ -321,16 +349,10 @@ handle_setup_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *r if (sscanf (pos, "%u", &streamid) != 1) goto bad_request; - /* find the media associated with the uri */ - if (client->factory == NULL) { - if ((client->factory = gst_rtsp_media_mapping_find_factory (client->mapping, uri)) == NULL) - goto not_found; - } - /* parse the transport */ res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_TRANSPORT, &transport, 0); if (res != GST_RTSP_OK) - goto unsupported_transports; + goto no_transport; transports = g_strsplit (transport, ",", 0); gst_rtsp_transport_new (&ct); @@ -348,9 +370,11 @@ handle_setup_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *r } g_strfreev (transports); + g_free (ct->destination); + ct->destination = g_strdup (inet_ntoa (client->address.sin_addr)); + /* we have not found anything usable, error out */ if (!have_transport) { - gst_rtsp_transport_free (ct); goto unsupported_transports; } @@ -369,28 +393,36 @@ handle_setup_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *r res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); if (res == GST_RTSP_OK) { /* we had a session in the request, find it again */ - if (!(session = gst_rtsp_session_pool_find (client->pool, sessid))) + if (!(session = gst_rtsp_session_pool_find (client->session_pool, sessid))) goto session_not_found; need_session = FALSE; } else { /* create a session if this fails we probably reached our session limit or * something. */ - if (!(session = gst_rtsp_session_pool_create (client->pool))) + if (!(session = gst_rtsp_session_pool_create (client->session_pool))) goto service_unavailable; need_session = TRUE; } + if (need_session) { + GstRTSPMedia *m; + + /* get a handle to the configuration of the media in the session */ + if ((m = find_media (client, uri, request))) { + media = gst_rtsp_session_manage_media (session, uri, m); + } + } /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, uri, client->factory); - if (!media) + if (!(media = gst_rtsp_session_get_media (session, uri))) goto not_found; /* get a handle to the stream in the media */ - stream = gst_rtsp_session_media_get_stream (media, streamid); + if (!(stream = gst_rtsp_session_media_get_stream (media, streamid))) + goto no_stream; /* setup the server transport from the client transport */ - st = gst_rtsp_session_stream_set_transport (stream, inet_ntoa (client->address.sin_addr), ct); + st = gst_rtsp_session_stream_set_transport (stream, ct); /* serialize the server transport */ trans_str = gst_rtsp_transport_as_text (st); @@ -401,11 +433,12 @@ handle_setup_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *r if (need_session) gst_rtsp_message_add_header (&response, GST_RTSP_HDR_SESSION, session->sessionid); + gst_rtsp_message_add_header (&response, GST_RTSP_HDR_TRANSPORT, trans_str); g_free (trans_str); g_object_unref (session); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); return TRUE; @@ -420,14 +453,25 @@ not_found: handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); return FALSE; } +no_stream: + { + handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); + return FALSE; + } session_not_found: { handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); return FALSE; } +no_transport: + { + handle_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, request); + return FALSE; + } unsupported_transports: { handle_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, request); + gst_rtsp_transport_free (ct); return FALSE; } service_unavailable: @@ -444,13 +488,9 @@ handle_describe_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage GstRTSPMessage response = { 0 }; GstRTSPResult res; GstSDPMessage *sdp; - guint n_streams, i; - gchar *sdptext; - GstRTSPMediaFactory *factory; + guint i; + gchar *str; GstRTSPMedia *media; - GstElement *pipeline; - GstStateChangeReturn ret; - /* check what kind of format is accepted, we don't really do anything with it * and always return SDP for now. */ @@ -465,193 +505,42 @@ handle_describe_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage break; } - /* find the factory for the uri first */ - if (!(factory = gst_rtsp_media_mapping_find_factory (client->mapping, uri))) - goto no_factory; - - /* prepare the media and add it to the pipeline */ - if (!(media = gst_rtsp_media_factory_construct (factory, uri))) + /* find the media object for the uri */ + if (!(media = find_media (client, uri, request))) goto no_media; - /* create a pipeline to preroll the media */ - pipeline = gst_pipeline_new ("client-describe-pipeline"); - - gst_bin_add (GST_BIN_CAST (pipeline), media->element); - - /* link fakesink to all stream pads and set the pipeline to PLAYING */ - n_streams = gst_rtsp_media_n_streams (media); - for (i = 0; i < n_streams; i++) { - GstRTSPMediaStream *stream; - GstElement *sink; - GstPad *sinkpad; - GstPadLinkReturn lret; - - stream = gst_rtsp_media_get_stream (media, i); - - sink = gst_element_factory_make ("fakesink", NULL); - gst_bin_add (GST_BIN (pipeline), sink); - - sinkpad = gst_element_get_static_pad (sink, "sink"); - lret = gst_pad_link (stream->srcpad, sinkpad); - if (lret != GST_PAD_LINK_OK) { - g_warning ("failed to link pad to sink: %d", lret); - } - gst_object_unref (sinkpad); - } - - /* now play and wait till we get the pads blocked. At that time the pipeline - * is prerolled and we have the caps on the streams too. */ - ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) - goto cant_play; - - /* wait for state change to complete */ - gst_element_get_state (pipeline, NULL, NULL, -1); - - /* we should now be able to construct the SDP message */ - gst_sdp_message_new (&sdp); - - /* some standard things first */ - gst_sdp_message_set_version (sdp, "0"); - gst_sdp_message_set_origin (sdp, "-", "1188340656180883", "1", "IN", "IP4", "127.0.0.1"); - gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer"); - gst_sdp_message_set_information (sdp, "rtsp-server"); - gst_sdp_message_add_time (sdp, "0", "0", NULL); - gst_sdp_message_add_attribute (sdp, "tool", "GStreamer"); - gst_sdp_message_add_attribute (sdp, "type", "broadcast"); - - for (i = 0; i < n_streams; i++) { - GstRTSPMediaStream *stream; - GstSDPMedia *smedia; - GstStructure *s; - const gchar *caps_str, *caps_enc, *caps_params; - gchar *tmp; - gint caps_pt, caps_rate; - guint n_fields, j; - gboolean first; - GString *fmtp; - - stream = gst_rtsp_media_get_stream (media, i); - gst_sdp_media_new (&smedia); - - s = gst_caps_get_structure (stream->caps, 0); - - /* get media type and payload for the m= line */ - caps_str = gst_structure_get_string (s, "media"); - gst_sdp_media_set_media (smedia, caps_str); - - gst_structure_get_int (s, "payload", &caps_pt); - tmp = g_strdup_printf ("%d", caps_pt); - gst_sdp_media_add_format (smedia, tmp); - g_free (tmp); - - gst_sdp_media_set_port_info (smedia, 0, 1); - gst_sdp_media_set_proto (smedia, "RTP/AVP"); - - /* for the c= line */ - gst_sdp_media_add_connection (smedia, "IN", "IP4", "127.0.0.1", 0, 0); - - /* get clock-rate, media type and params for the rtpmap attribute */ - gst_structure_get_int (s, "clock-rate", &caps_rate); - caps_enc = gst_structure_get_string (s, "encoding-name"); - caps_params = gst_structure_get_string (s, "encoding-params"); - - if (caps_params) - tmp = g_strdup_printf ("%d %s/%d/%s", caps_pt, caps_enc, caps_rate, - caps_params); - else - tmp = g_strdup_printf ("%d %s/%d", caps_pt, caps_enc, caps_rate); - - gst_sdp_media_add_attribute (smedia, "rtpmap", tmp); - g_free (tmp); - - /* the config uri */ - tmp = g_strdup_printf ("stream=%d", i); - gst_sdp_media_add_attribute (smedia, "control", tmp); - g_free (tmp); - - /* collect all other properties and add them to fmtp */ - fmtp = g_string_new (""); - g_string_append_printf (fmtp, "%d ", caps_pt); - first = TRUE; - n_fields = gst_structure_n_fields (s); - for (j = 0; j < n_fields; j++) { - const gchar *fname, *fval; - - fname = gst_structure_nth_field_name (s, j); - - /* filter out standard properties */ - if (!strcmp (fname, "media")) - continue; - if (!strcmp (fname, "payload")) - continue; - if (!strcmp (fname, "clock-rate")) - continue; - if (!strcmp (fname, "encoding-name")) - continue; - if (!strcmp (fname, "encoding-params")) - continue; - if (!strcmp (fname, "ssrc")) - continue; - if (!strcmp (fname, "clock-base")) - continue; - if (!strcmp (fname, "seqnum-base")) - continue; - - if ((fval = gst_structure_get_string (s, fname))) { - g_string_append_printf (fmtp, "%s%s=%s", first ? "":";", fname, fval); - first = FALSE; - } - } - if (!first) { - tmp = g_string_free (fmtp, FALSE); - gst_sdp_media_add_attribute (smedia, "fmtp", tmp); - g_free (tmp); - } - else { - g_string_free (fmtp, TRUE); - } - gst_sdp_message_add_media (sdp, smedia); - } - /* go back to NULL */ - gst_element_set_state (pipeline, GST_STATE_NULL); - - g_object_unref (factory); - - gst_object_unref (pipeline); - pipeline = NULL; + /* create an SDP for the media object */ + if (!(sdp = gst_rtsp_sdp_from_media (media))) + goto no_sdp; gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, gst_rtsp_status_as_text (GST_RTSP_STS_OK), request); gst_rtsp_message_add_header (&response, GST_RTSP_HDR_CONTENT_TYPE, "application/sdp"); + str = g_strdup_printf ("rtsp://%s:%u%s/", uri->host, uri->port, uri->abspath); + gst_rtsp_message_add_header (&response, GST_RTSP_HDR_CONTENT_BASE, str); + g_free (str); + /* add SDP to the response body */ - sdptext = gst_sdp_message_as_text (sdp); - gst_rtsp_message_take_body (&response, (guint8 *)sdptext, strlen (sdptext)); + str = gst_sdp_message_as_text (sdp); + gst_rtsp_message_take_body (&response, (guint8 *)str, strlen (str)); gst_sdp_message_free (sdp); - gst_rtsp_connection_send (client->connection, &response, NULL); + handle_response (client, &response); return TRUE; /* ERRORS */ -no_factory: - { - handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); - return FALSE; - } no_media: { - handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); - g_object_unref (factory); + /* error reply is already sent */ return FALSE; } -cant_play: +no_sdp: { handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); - gst_object_unref (pipeline); - g_object_unref (factory); + g_object_unref (media); return FALSE; } } @@ -661,7 +550,7 @@ handle_options_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage { GstRTSPMessage response = { 0 }; GstRTSPMethod options; - GString *str; + gchar *str; options = GST_RTSP_DESCRIBE | GST_RTSP_OPTIONS | @@ -670,38 +559,42 @@ handle_options_response (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage GST_RTSP_SETUP | GST_RTSP_TEARDOWN; - /* always return options.. */ - str = g_string_new ("OPTIONS"); - - if (options & GST_RTSP_DESCRIBE) - g_string_append (str, ", DESCRIBE"); - if (options & GST_RTSP_ANNOUNCE) - g_string_append (str, ", ANNOUNCE"); - if (options & GST_RTSP_GET_PARAMETER) - g_string_append (str, ", GET_PARAMETER"); - if (options & GST_RTSP_PAUSE) - g_string_append (str, ", PAUSE"); - if (options & GST_RTSP_PLAY) - g_string_append (str, ", PLAY"); - if (options & GST_RTSP_RECORD) - g_string_append (str, ", RECORD"); - if (options & GST_RTSP_REDIRECT) - g_string_append (str, ", REDIRECT"); - if (options & GST_RTSP_SETUP) - g_string_append (str, ", SETUP"); - if (options & GST_RTSP_SET_PARAMETER) - g_string_append (str, ", SET_PARAMETER"); - if (options & GST_RTSP_TEARDOWN) - g_string_append (str, ", TEARDOWN"); + str = gst_rtsp_options_as_text (options); gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, gst_rtsp_status_as_text (GST_RTSP_STS_OK), request); - gst_rtsp_message_add_header (&response, GST_RTSP_HDR_PUBLIC, str->str); + gst_rtsp_message_add_header (&response, GST_RTSP_HDR_PUBLIC, str); + g_free (str); - g_string_free (str, TRUE); + handle_response (client, &response); +} - gst_rtsp_connection_send (client->connection, &response, NULL); +/* remove duplicate and trailing '/' */ +static void +santize_uri (GstRTSPUrl *uri) +{ + gint i, len; + gchar *s, *d; + gboolean have_slash, prev_slash; + + s = d = uri->abspath; + len = strlen (uri->abspath); + + prev_slash = FALSE; + + for (i = 0; i < len; i++) { + have_slash = s[i] == '/'; + *d = s[i]; + if (!have_slash || !prev_slash) + d++; + prev_slash = have_slash; + } + len = d - uri->abspath; + /* don't remove the first slash if that's the only thing left */ + if (len > 1 && *(d-1) == '/') + d--; + *d = '\0'; } /* this function runs in a client specific thread and handles all rtsp messages @@ -740,6 +633,9 @@ handle_client (GstRTSPClient *client) continue; } + /* sanitize the uri */ + santize_uri (uri); + /* now see what is asked and dispatch to a dedicated handler */ switch (method) { case GST_RTSP_OPTIONS: @@ -845,11 +741,11 @@ gst_rtsp_client_set_session_pool (GstRTSPClient *client, GstRTSPSessionPool *poo { GstRTSPSessionPool *old; - old = client->pool; + old = client->session_pool; if (old != pool) { if (pool) g_object_ref (pool); - client->pool = pool; + client->session_pool = pool; if (old) g_object_unref (old); } @@ -868,7 +764,7 @@ gst_rtsp_client_get_session_pool (GstRTSPClient *client) { GstRTSPSessionPool *result; - if ((result = client->pool)) + if ((result = client->session_pool)) g_object_ref (result); return result; @@ -888,12 +784,12 @@ gst_rtsp_client_set_media_mapping (GstRTSPClient *client, GstRTSPMediaMapping *m { GstRTSPMediaMapping *old; - old = client->mapping; + old = client->media_mapping; if (old != mapping) { if (mapping) g_object_ref (mapping); - client->mapping = mapping; + client->media_mapping = mapping; if (old) g_object_unref (old); } @@ -912,13 +808,12 @@ gst_rtsp_client_get_media_mapping (GstRTSPClient *client) { GstRTSPMediaMapping *result; - if ((result = client->mapping)) + if ((result = client->media_mapping)) g_object_ref (result); return result; } - /** * gst_rtsp_client_attach: * @client: a #GstRTSPClient diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h index 11a375c..504fd1d 100644 --- a/gst/rtsp-server/rtsp-client.h +++ b/gst/rtsp-server/rtsp-client.h @@ -73,10 +73,8 @@ struct _GstRTSPClient { struct sockaddr_in address; GThread *thread; - GstRTSPSessionPool *pool; - - GstRTSPMediaFactory *factory; - GstRTSPMediaMapping *mapping; + GstRTSPSessionPool *session_pool; + GstRTSPMediaMapping *media_mapping; }; struct _GstRTSPClientClass { diff --git a/gst/rtsp-server/rtsp-media-factory.c b/gst/rtsp-server/rtsp-media-factory.c index f9df901..c927175 100644 --- a/gst/rtsp-server/rtsp-media-factory.c +++ b/gst/rtsp-server/rtsp-media-factory.c @@ -210,15 +210,6 @@ gst_rtsp_media_factory_construct (GstRTSPMediaFactory *factory, const GstRTSPUrl return res; } -static void -caps_notify (GstPad * pad, GParamSpec * unused, GstRTSPMediaStream * stream) -{ - if (stream->caps) - gst_caps_unref (stream->caps); - if ((stream->caps = GST_PAD_CAPS (pad))) - gst_caps_ref (stream->caps); -} - static GstElement * default_get_element (GstRTSPMediaFactory *factory, const GstRTSPUrl *url) { @@ -276,12 +267,13 @@ default_construct (GstRTSPMediaFactory *factory, const GstRTSPUrl *url) if (element == NULL) goto no_element; - media = g_object_new (GST_TYPE_RTSP_MEDIA, NULL); + /* create a new empty media */ + media = gst_rtsp_media_new (); media->element = element; /* try to find all the payloader elements, they should be named 'pay%d'. for - * each of the payloaders we will create a stream, collect the source pad and - * add a notify::caps on the pad. */ + * each of the payloaders we will create a stream and collect the source pad. + */ for (i = 0; ; i++) { gchar *name; @@ -297,7 +289,6 @@ default_construct (GstRTSPMediaFactory *factory, const GstRTSPUrl *url) /* create the stream */ stream = g_new0 (GstRTSPMediaStream, 1); stream->media = media; - stream->element = element; stream->payloader = pay; stream->idx = media->streams->len; @@ -305,16 +296,12 @@ default_construct (GstRTSPMediaFactory *factory, const GstRTSPUrl *url) /* ghost the pad of the payloader to the element */ stream->srcpad = gst_ghost_pad_new (name, pad); - gst_element_add_pad (stream->element, stream->srcpad); - - stream->caps_sig = g_signal_connect (pad, "notify::caps", (GCallback) caps_notify, stream); - gst_object_unref (pad); + gst_element_add_pad (media->element, stream->srcpad); + gst_object_unref (pay); + g_free (name); /* add stream now */ g_array_append_val (media->streams, stream); - gst_object_unref (pay); - - g_free (name); } return media; diff --git a/gst/rtsp-server/rtsp-media-factory.h b/gst/rtsp-server/rtsp-media-factory.h index 4c90ac7..472d325 100644 --- a/gst/rtsp-server/rtsp-media-factory.h +++ b/gst/rtsp-server/rtsp-media-factory.h @@ -56,22 +56,27 @@ struct _GstRTSPMediaFactory { /** * GstRTSPMediaFactoryClass: - * @get_element: Construct an return a #GstElement thast is a #GstBin containing - * the pipeline to use for the media. The bin should contain elements - * pay%d for each stream. The default implementation of this functions - * returns the bin created from the launch parameter. * @construct: the vmethod that will be called when the factory has to create the * #GstRTSPMedia for @url. The default implementation of this * function calls get_element to retrieve an element and then looks for * pay%d to create the streams. - + * @handle_message: Handle a bus message for @media created from @factory. + * @get_element: Construct an return a #GstElement thast is a #GstBin containing + * the pipeline to use for the media. The bin should contain elements + * pay%d for each stream. The default implementation of this functions + * returns the bin created from the launch parameter. + * * the #GstRTSPMediaFactory class structure. */ struct _GstRTSPMediaFactoryClass { GObjectClass parent_class; - GstElement * (*get_element) (GstRTSPMediaFactory *factory, const GstRTSPUrl *url); - GstRTSPMedia * (*construct) (GstRTSPMediaFactory *factory, const GstRTSPUrl *url); + GstRTSPMedia * (*construct) (GstRTSPMediaFactory *factory, const GstRTSPUrl *url); + void (*handle_message) (GstRTSPMediaFactory *factory, GstRTSPMedia *media, + GstMessage *message); + + GstElement * (*get_element) (GstRTSPMediaFactory *factory, const GstRTSPUrl *url); + }; GType gst_rtsp_media_factory_get_type (void); diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 9447675..2dc2c8c 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -65,6 +65,26 @@ gst_rtsp_media_finalize (GObject * obj) } /** + * gst_rtsp_media_new: + * + * Create a new #GstRTSPMedia instance. The #GstRTSPMedia object contains the + * element to produde RTP data for one or more related (audio/video/..) + * streams. + * + * Returns: a new #GstRTSPMedia object. + */ +GstRTSPMedia * +gst_rtsp_media_new (void) +{ + GstRTSPMedia *result; + + result = g_object_new (GST_TYPE_RTSP_MEDIA, NULL); + + return result; +} + + +/** * gst_rtsp_media_n_streams: * @media: a #GstRTSPMedia * @@ -102,3 +122,418 @@ gst_rtsp_media_get_stream (GstRTSPMedia *media, guint idx) return res; } +/* Allocate the udp ports and sockets */ +static gboolean +alloc_udp_ports (GstRTSPMediaStream * stream) +{ + GstStateChangeReturn ret; + GstElement *udpsrc0, *udpsrc1; + GstElement *udpsink0, *udpsink1; + gint tmp_rtp, tmp_rtcp; + guint count; + gint rtpport, rtcpport, sockfd; + + udpsrc0 = NULL; + udpsrc1 = NULL; + udpsink0 = NULL; + udpsink1 = NULL; + count = 0; + + /* Start with random port */ + tmp_rtp = 0; + + /* try to allocate 2 UDP ports, the RTP port should be an even + * number and the RTCP port should be the next (uneven) port */ +again: + udpsrc0 = gst_element_make_from_uri (GST_URI_SRC, "udp://0.0.0.0", NULL); + if (udpsrc0 == NULL) + goto no_udp_protocol; + g_object_set (G_OBJECT (udpsrc0), "port", tmp_rtp, NULL); + + ret = gst_element_set_state (udpsrc0, GST_STATE_PAUSED); + if (ret == GST_STATE_CHANGE_FAILURE) { + if (tmp_rtp != 0) { + tmp_rtp += 2; + if (++count > 20) + goto no_ports; + + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + + goto again; + } + goto no_udp_protocol; + } + + g_object_get (G_OBJECT (udpsrc0), "port", &tmp_rtp, NULL); + + /* check if port is even */ + if ((tmp_rtp & 1) != 0) { + /* port not even, close and allocate another */ + if (++count > 20) + goto no_ports; + + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + + tmp_rtp++; + goto again; + } + + /* allocate port+1 for RTCP now */ + udpsrc1 = gst_element_make_from_uri (GST_URI_SRC, "udp://0.0.0.0", NULL); + if (udpsrc1 == NULL) + goto no_udp_rtcp_protocol; + + /* set port */ + tmp_rtcp = tmp_rtp + 1; + g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, NULL); + + ret = gst_element_set_state (udpsrc1, GST_STATE_PAUSED); + /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ + if (ret == GST_STATE_CHANGE_FAILURE) { + + if (++count > 20) + goto no_ports; + + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + + gst_element_set_state (udpsrc1, GST_STATE_NULL); + gst_object_unref (udpsrc1); + + tmp_rtp += 2; + goto again; + } + + /* all fine, do port check */ + g_object_get (G_OBJECT (udpsrc0), "port", &rtpport, NULL); + g_object_get (G_OBJECT (udpsrc1), "port", &rtcpport, NULL); + + /* this should not happen... */ + if (rtpport != tmp_rtp || rtcpport != tmp_rtcp) + goto port_error; + + udpsink0 = gst_element_factory_make ("multiudpsink", NULL); + if (!udpsink0) + goto no_udp_protocol; + + g_object_get (G_OBJECT (udpsrc0), "sock", &sockfd, NULL); + g_object_set (G_OBJECT (udpsink0), "sockfd", sockfd, NULL); + g_object_set (G_OBJECT (udpsink0), "closefd", FALSE, NULL); + + udpsink1 = gst_element_factory_make ("multiudpsink", NULL); + if (!udpsink1) + goto no_udp_protocol; + + g_object_get (G_OBJECT (udpsrc1), "sock", &sockfd, NULL); + g_object_set (G_OBJECT (udpsink1), "sockfd", sockfd, NULL); + g_object_set (G_OBJECT (udpsink1), "closefd", FALSE, NULL); + g_object_set (G_OBJECT (udpsink1), "sync", FALSE, NULL); + g_object_set (G_OBJECT (udpsink1), "async", FALSE, NULL); + + /* we keep these elements, we configure all in configure_transport when the + * server told us to really use the UDP ports. */ + stream->udpsrc[0] = gst_object_ref (udpsrc0); + stream->udpsrc[1] = gst_object_ref (udpsrc1); + stream->udpsink[0] = gst_object_ref (udpsink0); + stream->udpsink[1] = gst_object_ref (udpsink1); + stream->server_port.min = rtpport; + stream->server_port.max = rtcpport; + + /* they are ours now */ + gst_object_sink (udpsrc0); + gst_object_sink (udpsrc1); + gst_object_sink (udpsink0); + gst_object_sink (udpsink1); + + return TRUE; + + /* ERRORS */ +no_udp_protocol: + { + goto cleanup; + } +no_ports: + { + goto cleanup; + } +no_udp_rtcp_protocol: + { + goto cleanup; + } +port_error: + { + goto cleanup; + } +cleanup: + { + if (udpsrc0) { + gst_element_set_state (udpsrc0, GST_STATE_NULL); + gst_object_unref (udpsrc0); + } + if (udpsrc1) { + gst_element_set_state (udpsrc1, GST_STATE_NULL); + gst_object_unref (udpsrc1); + } + if (udpsink0) { + gst_element_set_state (udpsink0, GST_STATE_NULL); + gst_object_unref (udpsink0); + } + if (udpsink1) { + gst_element_set_state (udpsink1, GST_STATE_NULL); + gst_object_unref (udpsink1); + } + return FALSE; + } +} + +static void +caps_notify (GstPad * pad, GParamSpec * unused, GstRTSPMediaStream * stream) +{ + gchar *capsstr; + + if (stream->caps) + gst_caps_unref (stream->caps); + if ((stream->caps = GST_PAD_CAPS (pad))) + gst_caps_ref (stream->caps); + + capsstr = gst_caps_to_string (stream->caps); + g_message ("stream %p received caps %s", stream, capsstr); + g_free (capsstr); +} + +/* prepare the pipeline objects to handle @stream in @media */ +static gboolean +setup_stream (GstRTSPMediaStream *stream, GstRTSPMedia *media) +{ + gchar *name; + GstPad *pad; + + alloc_udp_ports (stream); + + gst_bin_add (GST_BIN_CAST (media->pipeline), stream->udpsink[0]); + gst_bin_add (GST_BIN_CAST (media->pipeline), stream->udpsink[1]); + gst_bin_add (GST_BIN_CAST (media->pipeline), stream->udpsrc[1]); + + /* hook up the stream to the RTP session elements. */ + name = g_strdup_printf ("send_rtp_sink_%d", stream->idx); + stream->send_rtp_sink = gst_element_get_request_pad (media->rtpbin, name); + g_free (name); + name = g_strdup_printf ("send_rtp_src_%d", stream->idx); + stream->send_rtp_src = gst_element_get_static_pad (media->rtpbin, name); + g_free (name); + name = g_strdup_printf ("send_rtcp_src_%d", stream->idx); + stream->send_rtcp_src = gst_element_get_request_pad (media->rtpbin, name); + g_free (name); + name = g_strdup_printf ("recv_rtcp_sink_%d", stream->idx); + stream->recv_rtcp_sink = gst_element_get_request_pad (media->rtpbin, name); + g_free (name); + + /* link the RTP pad to the session manager */ + gst_pad_link (stream->srcpad, stream->send_rtp_sink); + + /* link udp elements */ + pad = gst_element_get_static_pad (stream->udpsink[0], "sink"); + gst_pad_link (stream->send_rtp_src, pad); + gst_object_unref (pad); + pad = gst_element_get_static_pad (stream->udpsink[1], "sink"); + gst_pad_link (stream->send_rtcp_src, pad); + gst_object_unref (pad); + pad = gst_element_get_static_pad (stream->udpsrc[1], "src"); + gst_pad_link (pad, stream->recv_rtcp_sink); + gst_object_unref (pad); + + /* we set and keep these to playing so that they don't cause NO_PREROLL return + * values */ + gst_element_set_state (stream->udpsrc[0], GST_STATE_PLAYING); + gst_element_set_state (stream->udpsrc[1], GST_STATE_PLAYING); + gst_element_set_locked_state (stream->udpsrc[0], TRUE); + gst_element_set_locked_state (stream->udpsrc[1], TRUE); + + /* be notified of caps changes */ + stream->caps_sig = g_signal_connect (stream->send_rtp_sink, "notify::caps", + (GCallback) caps_notify, stream); + + stream->prepared = TRUE; + + return TRUE; +} + + +/** + * gst_rtsp_media_prepare: + * @obj: a #GstRTSPMedia + * + * Prepare @media for streaming. This function will create the pipeline and + * other objects to manage the streaming. + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_prepare (GstRTSPMedia *media) +{ + GstStateChangeReturn ret; + guint i, n_streams; + + if (media->prepared) + goto was_prepared; + + media->pipeline = gst_pipeline_new ("media-pipeline"); + + gst_bin_add (GST_BIN_CAST (media->pipeline), media->element); + + media->rtpbin = gst_element_factory_make ("gstrtpbin", "rtpbin"); + + /* add stuf to the bin */ + gst_bin_add (GST_BIN (media->pipeline), media->rtpbin); + + ret = gst_element_set_state (media->pipeline, GST_STATE_READY); + + n_streams = gst_rtsp_media_n_streams (media); + for (i = 0; i < n_streams; i++) { + GstRTSPMediaStream *stream; + + stream = gst_rtsp_media_get_stream (media, i); + + setup_stream (stream, media); + } + + /* first go to PAUSED */ + ret = gst_element_set_state (media->pipeline, GST_STATE_PAUSED); + + switch (ret) { + case GST_STATE_CHANGE_SUCCESS: + break; + case GST_STATE_CHANGE_ASYNC: + break; + case GST_STATE_CHANGE_NO_PREROLL: + /* we need to go to PLAYING */ + g_message ("live media %p", media); + ret = gst_element_set_state (media->pipeline, GST_STATE_PLAYING); + break; + case GST_STATE_CHANGE_FAILURE: + goto state_failed; + } + + /* no wait for all pads to be prerolled */ + ret = gst_element_get_state (media->pipeline, NULL, NULL, -1); + + /* and back to PAUSED for live pipelines */ + ret = gst_element_set_state (media->pipeline, GST_STATE_PAUSED); + + g_message ("object %p is prerolled", media); + media->prepared = TRUE; + + return TRUE; + + /* OK */ +was_prepared: + { + return TRUE; + } + /* ERRORS */ +state_failed: + { + g_message ("state change failed for media %p", media); + return FALSE; + } +} + +gboolean +gst_rtsp_media_stream_add (GstRTSPMediaStream *stream, GstRTSPTransport *ct) +{ + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (ct != NULL, FALSE); + g_return_val_if_fail (stream->prepared, FALSE); + + g_message ("adding %s:%d", ct->destination, ct->client_port.min); + + g_signal_emit_by_name (stream->udpsink[0], "add", ct->destination, ct->client_port.min, NULL); + g_signal_emit_by_name (stream->udpsink[1], "add", ct->destination, ct->client_port.max, NULL); + + return TRUE; +} + +gboolean +gst_rtsp_media_stream_remove (GstRTSPMediaStream *stream, GstRTSPTransport *ct) +{ + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (ct != NULL, FALSE); + g_return_val_if_fail (stream->prepared, FALSE); + + g_message ("removing %s:%d", ct->destination, ct->client_port.min); + + g_signal_emit_by_name (stream->udpsink[0], "remove", ct->destination, ct->client_port.min, NULL); + g_signal_emit_by_name (stream->udpsink[1], "remove", ct->destination, ct->client_port.max, NULL); + + return TRUE; +} + +/** + * gst_rtsp_media_play: + * @media: a #GstRTSPMedia + * + * Tell the @media to start playing and streaming to the client. + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_media_play (GstRTSPMedia *media) +{ + GstStateChangeReturn ret; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_STATE_CHANGE_FAILURE); + g_return_val_if_fail (media->prepared, GST_STATE_CHANGE_FAILURE); + + g_message ("playing"); + ret = gst_element_set_state (media->pipeline, GST_STATE_PLAYING); + + return ret; +} + +/** + * gst_rtsp_media_pause: + * @media: a #GstRTSPMedia + * + * Tell the @media to pause. + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_media_pause (GstRTSPMedia *media) +{ + GstStateChangeReturn ret; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_STATE_CHANGE_FAILURE); + g_return_val_if_fail (media->prepared, GST_STATE_CHANGE_FAILURE); + + g_message ("paused"); + ret = gst_element_set_state (media->pipeline, GST_STATE_PAUSED); + + return ret; +} + +/** + * gst_rtsp_media_stop: + * @media: a #GstRTSPMedia + * + * Tell the @media to stop playing. After this call the media + * cannot be played or paused anymore + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_media_stop (GstRTSPMedia *media) +{ + GstStateChangeReturn ret; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_STATE_CHANGE_FAILURE); + g_return_val_if_fail (media->prepared, GST_STATE_CHANGE_FAILURE); + + g_message ("stop"); + ret = gst_element_set_state (media->pipeline, GST_STATE_NULL); + + return ret; +} + diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index 17fa4ee..6fc0dc1 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -42,10 +42,18 @@ typedef struct _GstRTSPMediaClass GstRTSPMediaClass; /** * GstRTSPMediaStream: * + * @media: the owner #GstRTSPMedia * @idx: the stream index - * @element: the toplevel element * @srcpad: the srcpad of the stream * @payloader: the payloader of the format + * @prepared: if the stream is prepared for streaming + * @server_port: the server udp ports + * @recv_rtp_sink: sinkpad for RTP buffers + * @recv_rtcp_sink: sinkpad for RTCP buffers + * @recv_rtp_src: srcpad for RTP buffers + * @recv_rtcp_src: srcpad for RTCP buffers + * @udpsrc: the udp source elements for RTP/RTCP + * @udpsink: the udp sink elements for RTP/RTCP * @caps_sig: the signal id for detecting caps * @caps: the caps of the stream * @@ -54,18 +62,38 @@ typedef struct _GstRTSPMediaClass GstRTSPMediaClass; struct _GstRTSPMediaStream { GstRTSPMedia *media; - guint idx; + guint idx; + + GstPad *srcpad; + GstElement *payloader; + gboolean prepared; + + GstRTSPRange server_port; + + /* pads on the rtpbin */ + GstPad *recv_rtcp_sink; + GstPad *send_rtp_sink; + GstPad *send_rtp_src; + GstPad *send_rtcp_src; - GstElement *element; - GstPad *srcpad; - GstElement *payloader; - gulong caps_sig; - GstCaps *caps; + /* sinks used for sending and receiving RTP and RTCP, they share + * sockets */ + GstElement *udpsrc[2]; + GstElement *udpsink[2]; + + /* the caps of the stream */ + gulong caps_sig; + GstCaps *caps; }; /** * GstRTSPMedia: - * @media: the owner #GstRTSPMedia + * @element: the data providing element + * @stream: the different streams provided by @element + * @prepared: if the media is prepared for streaming + * @pipeline: the toplevel pipeline + * @rtpbin: the rtpbin + * @multifdsink: multifdsink element for TCP transport * * A class that contains the GStreamer element along with a list of * #GstRTSPediaStream objects that can produce data. @@ -77,6 +105,16 @@ struct _GstRTSPMedia { GstElement *element; GArray *streams; + gboolean prepared; + + /* the pipeline for the media */ + GstElement *pipeline; + + /* RTP session manager */ + GstElement *rtpbin; + + /* for TCP transport */ + GstElement *multifdsink; }; struct _GstRTSPMediaClass { @@ -85,10 +123,24 @@ struct _GstRTSPMediaClass { GType gst_rtsp_media_get_type (void); +/* creating the media */ +GstRTSPMedia * gst_rtsp_media_new (void); + /* dealing with the media */ guint gst_rtsp_media_n_streams (GstRTSPMedia *media); GstRTSPMediaStream * gst_rtsp_media_get_stream (GstRTSPMedia *media, guint idx); +/* prepare the media for playback */ +gboolean gst_rtsp_media_prepare (GstRTSPMedia *media); + +/* add destinations to a stream */ +gboolean gst_rtsp_media_stream_add (GstRTSPMediaStream *stream, GstRTSPTransport *ct); +gboolean gst_rtsp_media_stream_remove (GstRTSPMediaStream *stream, GstRTSPTransport *ct); + +GstStateChangeReturn gst_rtsp_media_play (GstRTSPMedia *media); +GstStateChangeReturn gst_rtsp_media_pause (GstRTSPMedia *media); +GstStateChangeReturn gst_rtsp_media_stop (GstRTSPMedia *media); + G_END_DECLS #endif /* __GST_RTSP_MEDIA_H__ */ diff --git a/gst/rtsp-server/rtsp-sdp.c b/gst/rtsp-server/rtsp-sdp.c new file mode 100644 index 0000000..4a53b65 --- /dev/null +++ b/gst/rtsp-server/rtsp-sdp.c @@ -0,0 +1,145 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * + * 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. + */ +#include + +#include "rtsp-sdp.h" + +/** + * gst_rtsp_sdp_from_media: + * @media: a #GstRTSPMedia + * + * Create a new sdp message for @media. + * + * Returns: a new sdp message for @media. gst_sdp_message_free() after usage. + */ +GstSDPMessage * +gst_rtsp_sdp_from_media (GstRTSPMedia *media) +{ + GstSDPMessage *sdp; + guint i, n_streams; + + n_streams = gst_rtsp_media_n_streams (media); + + gst_sdp_message_new (&sdp); + + /* some standard things first */ + gst_sdp_message_set_version (sdp, "0"); + gst_sdp_message_set_origin (sdp, "-", "1188340656180883", "1", "IN", "IP4", "127.0.0.1"); + gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer"); + gst_sdp_message_set_information (sdp, "rtsp-server"); + gst_sdp_message_add_time (sdp, "0", "0", NULL); + gst_sdp_message_add_attribute (sdp, "tool", "GStreamer"); + gst_sdp_message_add_attribute (sdp, "type", "broadcast"); + + for (i = 0; i < n_streams; i++) { + GstRTSPMediaStream *stream; + GstSDPMedia *smedia; + GstStructure *s; + const gchar *caps_str, *caps_enc, *caps_params; + gchar *tmp; + gint caps_pt, caps_rate; + guint n_fields, j; + gboolean first; + GString *fmtp; + + stream = gst_rtsp_media_get_stream (media, i); + gst_sdp_media_new (&smedia); + + s = gst_caps_get_structure (stream->caps, 0); + + /* get media type and payload for the m= line */ + caps_str = gst_structure_get_string (s, "media"); + gst_sdp_media_set_media (smedia, caps_str); + + gst_structure_get_int (s, "payload", &caps_pt); + tmp = g_strdup_printf ("%d", caps_pt); + gst_sdp_media_add_format (smedia, tmp); + g_free (tmp); + + gst_sdp_media_set_port_info (smedia, 0, 1); + gst_sdp_media_set_proto (smedia, "RTP/AVP"); + + /* for the c= line */ + gst_sdp_media_add_connection (smedia, "IN", "IP4", "127.0.0.1", 0, 0); + + /* get clock-rate, media type and params for the rtpmap attribute */ + gst_structure_get_int (s, "clock-rate", &caps_rate); + caps_enc = gst_structure_get_string (s, "encoding-name"); + caps_params = gst_structure_get_string (s, "encoding-params"); + + if (caps_params) + tmp = g_strdup_printf ("%d %s/%d/%s", caps_pt, caps_enc, caps_rate, + caps_params); + else + tmp = g_strdup_printf ("%d %s/%d", caps_pt, caps_enc, caps_rate); + + gst_sdp_media_add_attribute (smedia, "rtpmap", tmp); + g_free (tmp); + + /* the config uri */ + tmp = g_strdup_printf ("stream=%d", i); + gst_sdp_media_add_attribute (smedia, "control", tmp); + g_free (tmp); + + /* collect all other properties and add them to fmtp */ + fmtp = g_string_new (""); + g_string_append_printf (fmtp, "%d ", caps_pt); + first = TRUE; + n_fields = gst_structure_n_fields (s); + for (j = 0; j < n_fields; j++) { + const gchar *fname, *fval; + + fname = gst_structure_nth_field_name (s, j); + + /* filter out standard properties */ + if (!strcmp (fname, "media")) + continue; + if (!strcmp (fname, "payload")) + continue; + if (!strcmp (fname, "clock-rate")) + continue; + if (!strcmp (fname, "encoding-name")) + continue; + if (!strcmp (fname, "encoding-params")) + continue; + if (!strcmp (fname, "ssrc")) + continue; + if (!strcmp (fname, "clock-base")) + continue; + if (!strcmp (fname, "seqnum-base")) + continue; + + if ((fval = gst_structure_get_string (s, fname))) { + g_string_append_printf (fmtp, "%s%s=%s", first ? "":";", fname, fval); + first = FALSE; + } + } + if (!first) { + tmp = g_string_free (fmtp, FALSE); + gst_sdp_media_add_attribute (smedia, "fmtp", tmp); + g_free (tmp); + } + else { + g_string_free (fmtp, TRUE); + } + gst_sdp_message_add_media (sdp, smedia); + } + + return sdp; +} diff --git a/gst/rtsp-server/rtsp-sdp.h b/gst/rtsp-server/rtsp-sdp.h new file mode 100644 index 0000000..4016eda --- /dev/null +++ b/gst/rtsp-server/rtsp-sdp.h @@ -0,0 +1,35 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * + * 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. + */ + +#include +#include + +#include "rtsp-media.h" + +#ifndef __GST_RTSP_SDP_H__ +#define __GST_RTSP_SDP_H__ + +G_BEGIN_DECLS + +/* creating SDP */ +GstSDPMessage * gst_rtsp_sdp_from_media (GstRTSPMedia *media); + +G_END_DECLS + +#endif /* __GST_RTSP_SDP_H__ */ diff --git a/gst/rtsp-server/rtsp-server.c b/gst/rtsp-server/rtsp-server.c index b34bc80..afbfe92 100644 --- a/gst/rtsp-server/rtsp-server.c +++ b/gst/rtsp-server/rtsp-server.c @@ -30,8 +30,8 @@ enum PROP_0, PROP_BACKLOG, PROP_PORT, - PROP_POOL, - PROP_MAPPING, + PROP_SESSION_POOL, + PROP_MEDIA_MAPPING, PROP_LAST }; @@ -78,23 +78,25 @@ gst_rtsp_server_class_init (GstRTSPServerClass * klass) g_param_spec_int ("port", "Port", "The port the server uses to listen on", 1, 65535, DEFAULT_PORT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** - * GstRTSPServer::pool + * GstRTSPServer::session-pool * * The session pool of the server. By default each server has a separate * session pool but sessions can be shared between servers by setting the same * session pool on multiple servers. */ - g_object_class_install_property (gobject_class, PROP_POOL, - g_param_spec_object ("pool", "Pool", "The session pool to use for client session", + g_object_class_install_property (gobject_class, PROP_SESSION_POOL, + g_param_spec_object ("session-pool", "Session Pool", + "The session pool to use for client session", GST_TYPE_RTSP_SESSION_POOL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** - * GstRTSPServer::mapping + * GstRTSPServer::media-mapping * * The media mapping to use for this server. By default the server has no * media mapping and thus cannot map urls to media streams. */ - g_object_class_install_property (gobject_class, PROP_MAPPING, - g_param_spec_object ("mapping", "Mapping", "The media mapping to use for client session", + g_object_class_install_property (gobject_class, PROP_MEDIA_MAPPING, + g_param_spec_object ("media-mapping", "Media Mapping", + "The media mapping to use for client session", GST_TYPE_RTSP_MEDIA_MAPPING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); klass->accept_client = gst_rtsp_server_accept_client; @@ -105,8 +107,8 @@ gst_rtsp_server_init (GstRTSPServer * server) { server->server_port = DEFAULT_PORT; server->backlog = DEFAULT_BACKLOG; - server->pool = gst_rtsp_session_pool_new (); - server->mapping = gst_rtsp_media_mapping_new (); + server->session_pool = gst_rtsp_session_pool_new (); + server->media_mapping = gst_rtsp_media_mapping_new (); } /** @@ -207,12 +209,12 @@ gst_rtsp_server_set_session_pool (GstRTSPServer *server, GstRTSPSessionPool *poo g_return_if_fail (GST_IS_RTSP_SERVER (server)); - old = server->pool; + old = server->session_pool; if (old != pool) { if (pool) g_object_ref (pool); - server->pool = pool; + server->session_pool = pool; if (old) g_object_unref (old); } @@ -235,7 +237,7 @@ gst_rtsp_server_get_session_pool (GstRTSPServer *server) g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL); - if ((result = server->pool)) + if ((result = server->session_pool)) g_object_ref (result); return result; @@ -255,12 +257,12 @@ gst_rtsp_server_set_media_mapping (GstRTSPServer *server, GstRTSPMediaMapping *m g_return_if_fail (GST_IS_RTSP_SERVER (server)); - old = server->mapping; + old = server->media_mapping; if (old != mapping) { if (mapping) g_object_ref (mapping); - server->mapping = mapping; + server->media_mapping = mapping; if (old) g_object_unref (old); } @@ -283,7 +285,7 @@ gst_rtsp_server_get_media_mapping (GstRTSPServer *server) g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL); - if ((result = server->mapping)) + if ((result = server->media_mapping)) g_object_ref (result); return result; @@ -302,10 +304,10 @@ gst_rtsp_server_get_property (GObject *object, guint propid, case PROP_BACKLOG: g_value_set_int (value, gst_rtsp_server_get_backlog (server)); break; - case PROP_POOL: + case PROP_SESSION_POOL: g_value_take_object (value, gst_rtsp_server_get_session_pool (server)); break; - case PROP_MAPPING: + case PROP_MEDIA_MAPPING: g_value_take_object (value, gst_rtsp_server_get_media_mapping (server)); break; default: @@ -326,10 +328,10 @@ gst_rtsp_server_set_property (GObject *object, guint propid, case PROP_BACKLOG: gst_rtsp_server_set_backlog (server, g_value_get_int (value)); break; - case PROP_POOL: + case PROP_SESSION_POOL: gst_rtsp_server_set_session_pool (server, g_value_get_object (value)); break; - case PROP_MAPPING: + case PROP_MEDIA_MAPPING: gst_rtsp_server_set_media_mapping (server, g_value_get_object (value)); break; default: @@ -444,10 +446,10 @@ gst_rtsp_server_accept_client (GstRTSPServer *server, GIOChannel *channel) client = gst_rtsp_client_new (); /* set the session pool that this client should use */ - gst_rtsp_client_set_session_pool (client, server->pool); + gst_rtsp_client_set_session_pool (client, server->session_pool); /* set the session pool that this client should use */ - gst_rtsp_client_set_media_mapping (client, server->mapping); + gst_rtsp_client_set_media_mapping (client, server->media_mapping); /* accept connections for that client, this function returns after accepting * the connection and will run the remainder of the communication with the diff --git a/gst/rtsp-server/rtsp-server.h b/gst/rtsp-server/rtsp-server.h index 277b841..e719873 100644 --- a/gst/rtsp-server/rtsp-server.h +++ b/gst/rtsp-server/rtsp-server.h @@ -58,22 +58,21 @@ struct _GstRTSPServer { GObject parent; /* server information */ - gint server_port; - gint backlog; - gchar *host; - struct sockaddr_in server_sin; + gint server_port; + gint backlog; + gchar *host; + struct sockaddr_in server_sin; - /* socket */ - GstPollFD server_sock; - - GIOChannel *io_channel; - GSource *io_watch; + /* socket and channels */ + GstPollFD server_sock; + GIOChannel *io_channel; + GSource *io_watch; /* sessions on this server */ - GstRTSPSessionPool *pool; + GstRTSPSessionPool *session_pool; /* media mapper for this server */ - GstRTSPMediaMapping *mapping; + GstRTSPMediaMapping *media_mapping; }; /** diff --git a/gst/rtsp-server/rtsp-session.c b/gst/rtsp-server/rtsp-session.c index b59dee0..a5efc79 100644 --- a/gst/rtsp-server/rtsp-session.c +++ b/gst/rtsp-server/rtsp-session.c @@ -16,6 +16,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#include #include "rtsp-session.h" @@ -45,12 +46,6 @@ gst_rtsp_session_free_stream (GstRTSPSessionStream *stream) { if (stream->client_trans) gst_rtsp_transport_free (stream->client_trans); - g_free (stream->destination); - if (stream->server_trans) - gst_rtsp_transport_free (stream->server_trans); - - if (stream->udpsrc[0]) - gst_object_unref (stream->udpsrc[0]); g_free (stream); } @@ -60,18 +55,11 @@ gst_rtsp_session_free_media (GstRTSPSessionMedia *media) { GList *walk; - gst_element_set_state (media->pipeline, GST_STATE_NULL); - - if (media->factory) - g_object_unref (media->factory); - for (walk = media->streams; walk; walk = g_list_next (walk)) { GstRTSPSessionStream *stream = (GstRTSPSessionStream *) walk->data; gst_rtsp_session_free_stream (stream); } - if (media->pipeline) - gst_object_unref (media->pipeline); g_list_free (media->streams); } @@ -96,60 +84,62 @@ gst_rtsp_session_finalize (GObject * obj) } /** + * gst_rtsp_session_manage_media: + * @sess: a #GstRTSPSession + * @url: the url for the media + * @obj: a #GstRTSPMediaObject + * + * Manage the media object @obj in @sess. @url will be used to retrieve this + * media object from the session with gst_rtsp_session_get_media(). + * + * Returns: a new @GstRTSPSessionMedia object. + */ +GstRTSPSessionMedia * +gst_rtsp_session_manage_media (GstRTSPSession *sess, const GstRTSPUrl *uri, + GstRTSPMedia *media) +{ + GstRTSPSessionMedia *result; + + result = g_new0 (GstRTSPSessionMedia, 1); + result->media = media; + result->url = gst_rtsp_url_copy ((GstRTSPUrl *)uri); + + sess->medias = g_list_prepend (sess->medias, result); + + g_message ("manage new media %p in session %p", media, sess); + + return result; +} + +/** * gst_rtsp_session_get_media: * @sess: a #GstRTSPSession * @url: the url for the media - * @factory: a #GstRTSPMediaFactory * - * Get or create the session information for @factory. + * Get the session media of the @url. * - * Returns: the configuration for @factory in @sess. + * Returns: the configuration for @url in @sess. */ GstRTSPSessionMedia * -gst_rtsp_session_get_media (GstRTSPSession *sess, const GstRTSPUrl *url, GstRTSPMediaFactory *factory) +gst_rtsp_session_get_media (GstRTSPSession *sess, const GstRTSPUrl *url) { GstRTSPSessionMedia *result; GList *walk; + g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL); + g_return_val_if_fail (url != NULL, NULL); + result = NULL; for (walk = sess->medias; walk; walk = g_list_next (walk)) { result = (GstRTSPSessionMedia *) walk->data; - if (result->factory == factory) + if (strcmp (result->url->abspath, url->abspath) == 0) break; result = NULL; } - if (result == NULL) { - result = g_new0 (GstRTSPSessionMedia, 1); - result->factory = factory; - result->pipeline = gst_pipeline_new ("pipeline"); - - /* construct media and add to the pipeline */ - result->media = gst_rtsp_media_factory_construct (factory, url); - if (result->media == NULL) - goto no_media; - - gst_bin_add (GST_BIN_CAST (result->pipeline), result->media->element); - - result->rtpbin = gst_element_factory_make ("gstrtpbin", "rtpbin"); - - /* add stuf to the bin */ - gst_bin_add (GST_BIN (result->pipeline), result->rtpbin); - - gst_element_set_state (result->pipeline, GST_STATE_READY); - - sess->medias = g_list_prepend (sess->medias, result); - } return result; - - /* ERRORS */ -no_media: - { - gst_rtsp_session_free_media (result); - return NULL; - } } /** @@ -205,198 +195,21 @@ gst_rtsp_session_new (const gchar *sessionid) return result; } -static gboolean -alloc_udp_ports (GstRTSPSessionStream * stream) -{ - GstStateChangeReturn ret; - GstElement *udpsrc0, *udpsrc1; - GstElement *udpsink0, *udpsink1; - gint tmp_rtp, tmp_rtcp; - guint count; - gint rtpport, rtcpport, sockfd; - gchar *name; - - udpsrc0 = NULL; - udpsrc1 = NULL; - udpsink0 = NULL; - udpsink1 = NULL; - count = 0; - - /* Start with random port */ - tmp_rtp = 0; - - /* try to allocate 2 UDP ports, the RTP port should be an even - * number and the RTCP port should be the next (uneven) port */ -again: - udpsrc0 = gst_element_make_from_uri (GST_URI_SRC, "udp://0.0.0.0", NULL); - if (udpsrc0 == NULL) - goto no_udp_protocol; - g_object_set (G_OBJECT (udpsrc0), "port", tmp_rtp, NULL); - - ret = gst_element_set_state (udpsrc0, GST_STATE_PAUSED); - if (ret == GST_STATE_CHANGE_FAILURE) { - if (tmp_rtp != 0) { - tmp_rtp += 2; - if (++count > 20) - goto no_ports; - - gst_element_set_state (udpsrc0, GST_STATE_NULL); - gst_object_unref (udpsrc0); - - goto again; - } - goto no_udp_protocol; - } - - g_object_get (G_OBJECT (udpsrc0), "port", &tmp_rtp, NULL); - - /* check if port is even */ - if ((tmp_rtp & 1) != 0) { - /* port not even, close and allocate another */ - if (++count > 20) - goto no_ports; - - gst_element_set_state (udpsrc0, GST_STATE_NULL); - gst_object_unref (udpsrc0); - - tmp_rtp++; - goto again; - } - - /* allocate port+1 for RTCP now */ - udpsrc1 = gst_element_make_from_uri (GST_URI_SRC, "udp://0.0.0.0", NULL); - if (udpsrc1 == NULL) - goto no_udp_rtcp_protocol; - - /* set port */ - tmp_rtcp = tmp_rtp + 1; - g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, NULL); - - ret = gst_element_set_state (udpsrc1, GST_STATE_PAUSED); - /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ - if (ret == GST_STATE_CHANGE_FAILURE) { - - if (++count > 20) - goto no_ports; - - gst_element_set_state (udpsrc0, GST_STATE_NULL); - gst_object_unref (udpsrc0); - - gst_element_set_state (udpsrc1, GST_STATE_NULL); - gst_object_unref (udpsrc1); - - tmp_rtp += 2; - goto again; - } - - /* all fine, do port check */ - g_object_get (G_OBJECT (udpsrc0), "port", &rtpport, NULL); - g_object_get (G_OBJECT (udpsrc1), "port", &rtcpport, NULL); - - /* this should not happen... */ - if (rtpport != tmp_rtp || rtcpport != tmp_rtcp) - goto port_error; - - name = g_strdup_printf ("udp://%s:%d", stream->destination, stream->client_trans->client_port.min); - udpsink0 = gst_element_make_from_uri (GST_URI_SINK, name, NULL); - g_free (name); - - if (!udpsink0) - goto no_udp_protocol; - - g_object_get (G_OBJECT (udpsrc0), "sock", &sockfd, NULL); - g_object_set (G_OBJECT (udpsink0), "sockfd", sockfd, NULL); - g_object_set (G_OBJECT (udpsink0), "closefd", FALSE, NULL); - - name = g_strdup_printf ("udp://%s:%d", stream->destination, stream->client_trans->client_port.max); - udpsink1 = gst_element_make_from_uri (GST_URI_SINK, name, NULL); - g_free (name); - - if (!udpsink1) - goto no_udp_protocol; - - g_object_get (G_OBJECT (udpsrc1), "sock", &sockfd, NULL); - g_object_set (G_OBJECT (udpsink1), "sockfd", sockfd, NULL); - g_object_set (G_OBJECT (udpsink1), "closefd", FALSE, NULL); - g_object_set (G_OBJECT (udpsink1), "sync", FALSE, NULL); - g_object_set (G_OBJECT (udpsink1), "async", FALSE, NULL); - - - /* we keep these elements, we configure all in configure_transport when the - * server told us to really use the UDP ports. */ - stream->udpsrc[0] = gst_object_ref (udpsrc0); - stream->udpsrc[1] = gst_object_ref (udpsrc1); - stream->udpsink[0] = gst_object_ref (udpsink0); - stream->udpsink[1] = gst_object_ref (udpsink1); - stream->server_trans->server_port.min = rtpport; - stream->server_trans->server_port.max = rtcpport; - - /* they are ours now */ - gst_object_sink (udpsrc0); - gst_object_sink (udpsrc1); - gst_object_sink (udpsink0); - gst_object_sink (udpsink1); - - return TRUE; - - /* ERRORS */ -no_udp_protocol: - { - goto cleanup; - } -no_ports: - { - goto cleanup; - } -no_udp_rtcp_protocol: - { - goto cleanup; - } -port_error: - { - goto cleanup; - } -cleanup: - { - if (udpsrc0) { - gst_element_set_state (udpsrc0, GST_STATE_NULL); - gst_object_unref (udpsrc0); - } - if (udpsrc1) { - gst_element_set_state (udpsrc1, GST_STATE_NULL); - gst_object_unref (udpsrc1); - } - if (udpsink0) { - gst_element_set_state (udpsink0, GST_STATE_NULL); - gst_object_unref (udpsink0); - } - if (udpsink1) { - gst_element_set_state (udpsink1, GST_STATE_NULL); - gst_object_unref (udpsink1); - } - return FALSE; - } -} - - /** * gst_rtsp_session_stream_init_udp: * @stream: a #GstRTSPSessionStream * @ct: a client #GstRTSPTransport * * Set @ct as the client transport and create and return a matching server - * transport. After this call the needed ports and elements will be created and - * initialized. + * transport. * * Returns: a server transport or NULL if something went wrong. */ GstRTSPTransport * gst_rtsp_session_stream_set_transport (GstRTSPSessionStream *stream, - const gchar *destination, GstRTSPTransport *ct) + GstRTSPTransport *ct) { GstRTSPTransport *st; - GstPad *pad; - gchar *name; GstRTSPSessionMedia *media; media = stream->media; @@ -410,49 +223,18 @@ gst_rtsp_session_stream_set_transport (GstRTSPSessionStream *stream, st->client_port = ct->client_port; /* keep track of the transports */ - g_free (stream->destination); - stream->destination = g_strdup (destination); if (stream->client_trans) gst_rtsp_transport_free (stream->client_trans); stream->client_trans = ct; - if (stream->server_trans) - gst_rtsp_transport_free (stream->server_trans); - stream->server_trans = st; - alloc_udp_ports (stream); - - gst_bin_add (GST_BIN (media->pipeline), stream->udpsink[0]); - gst_bin_add (GST_BIN (media->pipeline), stream->udpsink[1]); - gst_bin_add (GST_BIN (media->pipeline), stream->udpsrc[1]); - - /* hook up the stream to the RTP session elements. */ - name = g_strdup_printf ("send_rtp_sink_%d", stream->idx); - stream->send_rtp_sink = gst_element_get_request_pad (media->rtpbin, name); - g_free (name); - name = g_strdup_printf ("send_rtp_src_%d", stream->idx); - stream->send_rtp_src = gst_element_get_static_pad (media->rtpbin, name); - g_free (name); - name = g_strdup_printf ("send_rtcp_src_%d", stream->idx); - stream->send_rtcp_src = gst_element_get_request_pad (media->rtpbin, name); - g_free (name); - name = g_strdup_printf ("recv_rtcp_sink_%d", stream->idx); - stream->recv_rtcp_sink = gst_element_get_request_pad (media->rtpbin, name); - g_free (name); - - gst_pad_link (stream->media_stream->srcpad, stream->send_rtp_sink); - pad = gst_element_get_static_pad (stream->udpsink[0], "sink"); - gst_pad_link (stream->send_rtp_src, pad); - gst_object_unref (pad); - pad = gst_element_get_static_pad (stream->udpsink[1], "sink"); - gst_pad_link (stream->send_rtcp_src, pad); - gst_object_unref (pad); - pad = gst_element_get_static_pad (stream->udpsrc[1], "src"); - gst_pad_link (pad, stream->recv_rtcp_sink); - gst_object_unref (pad); + st->server_port.min = stream->media_stream->server_port.min; + st->server_port.max = stream->media_stream->server_port.max; + stream->server_trans = st; return st; } + /** * gst_rtsp_session_media_play: * @media: a #GstRTSPSessionMedia @@ -465,8 +247,15 @@ GstStateChangeReturn gst_rtsp_session_media_play (GstRTSPSessionMedia *media) { GstStateChangeReturn ret; + GstRTSPSessionStream *stream; + GList *walk; + + for (walk = media->streams; walk; walk = g_list_next (walk)) { + stream = (GstRTSPSessionStream *) walk->data; - ret = gst_element_set_state (media->pipeline, GST_STATE_PLAYING); + gst_rtsp_media_stream_add (stream->media_stream, stream->client_trans); + } + ret = gst_rtsp_media_play (media->media); return ret; } @@ -484,7 +273,7 @@ gst_rtsp_session_media_pause (GstRTSPSessionMedia *media) { GstStateChangeReturn ret; - ret = gst_element_set_state (media->pipeline, GST_STATE_PAUSED); + ret = gst_rtsp_media_pause (media->media); return ret; } @@ -503,7 +292,7 @@ gst_rtsp_session_media_stop (GstRTSPSessionMedia *media) { GstStateChangeReturn ret; - ret = gst_element_set_state (media->pipeline, GST_STATE_NULL); + ret = gst_rtsp_media_stop (media->media); return ret; } diff --git a/gst/rtsp-server/rtsp-session.h b/gst/rtsp-server/rtsp-session.h index 6055197..9905753 100644 --- a/gst/rtsp-server/rtsp-session.h +++ b/gst/rtsp-server/rtsp-session.h @@ -22,7 +22,6 @@ #include #include "rtsp-media.h" -#include "rtsp-media-factory.h" #ifndef __GST_RTSP_SESSION_H__ #define __GST_RTSP_SESSION_H__ @@ -60,46 +59,23 @@ struct _GstRTSPSessionStream GstRTSPMediaStream *media_stream; /* client and server transports */ - gchar *destination; GstRTSPTransport *client_trans; GstRTSPTransport *server_trans; - - /* pads on the rtpbin */ - GstPad *recv_rtcp_sink; - GstPad *send_rtp_sink; - GstPad *send_rtp_src; - GstPad *send_rtcp_src; - - /* sinks used for sending and receiving RTP and RTCP, they share sockets */ - GstElement *udpsrc[2]; - GstElement *udpsink[2]; }; /** * GstRTSPSessionMedia: * - * State of a client session regarding a specific media. The media is identified - * with the media factory. The media is typically composed of multiple streams, - * such as an audio and video stream. + * State of a client session regarding a specific media. */ struct _GstRTSPSessionMedia { - /* the owner session */ - GstRTSPSession *session; - - /* the media we are handling */ - GstRTSPMediaFactory *factory; + /* the url of the media */ + GstRTSPUrl *url; /* the pipeline for the media */ - GstElement *pipeline; GstRTSPMedia *media; - /* RTP session manager */ - GstElement *rtpbin; - - /* for TCP transport */ - GstElement *fdsink; - /* configuration for the different streams */ GList *streams; }; @@ -109,7 +85,7 @@ struct _GstRTSPSessionMedia * * Session information kept by the server for a specific client. * One client session, identified with a session id, can handle multiple medias - * identified with the media factory. + * identified with the media object. */ struct _GstRTSPSession { GObject parent; @@ -127,8 +103,11 @@ GType gst_rtsp_session_get_type (void); GstRTSPSession * gst_rtsp_session_new (const gchar *sessionid); -GstRTSPSessionMedia * gst_rtsp_session_get_media (GstRTSPSession *sess, const GstRTSPUrl *url, - GstRTSPMediaFactory *factory); +GstRTSPSessionMedia * gst_rtsp_session_manage_media (GstRTSPSession *sess, + const GstRTSPUrl *uri, + GstRTSPMedia *media); +GstRTSPSessionMedia * gst_rtsp_session_get_media (GstRTSPSession *sess, + const GstRTSPUrl *uri); GstStateChangeReturn gst_rtsp_session_media_play (GstRTSPSessionMedia *media); GstStateChangeReturn gst_rtsp_session_media_pause (GstRTSPSessionMedia *media); @@ -138,7 +117,6 @@ GstRTSPSessionStream * gst_rtsp_session_media_get_stream (GstRTSPSessionMedi guint idx); GstRTSPTransport * gst_rtsp_session_stream_set_transport (GstRTSPSessionStream *stream, - const gchar *destination, GstRTSPTransport *ct); G_END_DECLS -- 2.7.4