diff options
author | Wim Taymans <wim.taymans@collabora.co.uk> | 2009-01-08 13:18:55 +0100 |
---|---|---|
committer | Wim Taymans <wim@wtay.(none)> | 2009-01-08 13:18:55 +0100 |
commit | 78893957870ab6146d3bcf9add9a94e111803d2f (patch) | |
tree | 15323fe00725b3a9d1f3a9527c8e0178a1b19651 /gst/rtsp-server | |
parent | c91ec684e973e15699e8f6e08945ee0cf8df4571 (diff) |
Split in library and example program
Diffstat (limited to 'gst/rtsp-server')
-rw-r--r-- | gst/rtsp-server/Makefile.am | 28 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-client.c | 812 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-client.h | 87 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-media.c | 266 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-media.h | 79 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-server.c | 232 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-server.h | 86 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-session-pool.c | 165 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-session-pool.h | 69 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-session.c | 507 | ||||
-rw-r--r-- | gst/rtsp-server/rtsp-session.h | 138 |
11 files changed, 2469 insertions, 0 deletions
diff --git a/gst/rtsp-server/Makefile.am b/gst/rtsp-server/Makefile.am new file mode 100644 index 0000000..752b55b --- /dev/null +++ b/gst/rtsp-server/Makefile.am @@ -0,0 +1,28 @@ +public_headers = \ + rtsp-server.h \ + rtsp-client.h \ + rtsp-media.h + +c_sources = \ + rtsp-server.c \ + rtsp-client.c \ + rtsp-media.c \ + rtsp-session-pool.c \ + rtsp-session.c + +lib_LTLIBRARIES = \ + libgstrtspserver.la + +libgstrtspserver_la_SOURCES = \ + $(c_sources) + +libgstrtspserver_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstrtspserver_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstrtspserver_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \ + -lgstrtp-@GST_MAJORMINOR@ -lgstrtsp-@GST_MAJORMINOR@ \ + -lgstsdp-@GST_MAJORMINOR@ $(GST_LIBS) $(LIBM) +libgstrtspserver_la_LIBTOOLFLAGS = --tag=disable-static + +libgstrtspserver_includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/rtsp-server +libgstrtspserver_include_HEADERS = $(public_headers) diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c new file mode 100644 index 0000000..9bfb6f8 --- /dev/null +++ b/gst/rtsp-server/rtsp-client.c @@ -0,0 +1,812 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <sys/ioctl.h> + +#include <gst/sdp/gstsdpmessage.h> + +#include "rtsp-client.h" + +#undef DEBUG + +G_DEFINE_TYPE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT); + +static void +gst_rtsp_client_class_init (GstRTSPClientClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); +} + +static void +gst_rtsp_client_init (GstRTSPClient * client) +{ +} + +/** + * gst_rtsp_client_new: + * + * Create a new #GstRTSPClient instance. + */ +GstRTSPClient * +gst_rtsp_client_new (void) +{ + GstRTSPClient *result; + + result = g_object_new (GST_TYPE_RTSP_CLIENT, NULL); + + return result; +} + +static void +handle_generic_response (GstRTSPClient *client, GstRTSPStatusCode code, + GstRTSPMessage *request) +{ + GstRTSPMessage response = { 0 }; + + gst_rtsp_message_init_response (&response, code, + gst_rtsp_status_as_text (code), request); + + gst_rtsp_connection_send (client->connection, &response, NULL); +} + +static gboolean +handle_teardown_response (GstRTSPClient *client, const gchar *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; + + /* get a handle to the configuration of the media in the session */ + media = gst_rtsp_session_get_media (session, client->media); + + gst_rtsp_session_media_stop (media); + + gst_rtsp_session_pool_remove (client->pool, session); + g_object_unref (session); + + /* remove the session id from the request, which will also remove it from the + * response */ + gst_rtsp_message_remove_header (request, GST_RTSP_HDR_SESSION, -1); + + /* construct the response now */ + 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); + + return FALSE; + + /* ERRORS */ +session_not_found: + { + handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); + return FALSE; + } +service_unavailable: + { + return FALSE; + } +} + +static gboolean +handle_pause_response (GstRTSPClient *client, const gchar *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; + + /* get a handle to the configuration of the media in the session */ + media = gst_rtsp_session_get_media (session, client->media); + + gst_rtsp_session_media_pause (media); + g_object_unref (session); + + /* construct the response now */ + 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); + + return FALSE; + + /* ERRORS */ +session_not_found: + { + handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); + return FALSE; + } +service_unavailable: + { + return FALSE; + } +} + +static gboolean +handle_play_response (GstRTSPClient *client, const gchar *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; + + /* get a handle to the configuration of the media in the session */ + media = gst_rtsp_session_get_media (session, client->media); + + /* 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: + 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 (client->media); + for (i = 0; i < n_streams; i++) { + GstRTSPMediaStream *stream; + + stream = gst_rtsp_media_get_stream (client->media, i); + + g_object_get (G_OBJECT (stream->payloader), "seqnum", &seqnum, NULL); + g_object_get (G_OBJECT (stream->payloader), "timestamp", ×tamp, NULL); + + if (i > 0) + g_string_append (rtpinfo, ", "); + g_string_append_printf (rtpinfo, "url=%s/stream=%d;seq=%u;rtptime=%u", uri, i, seqnum, timestamp); + } + + /* construct the response now */ + code = GST_RTSP_STS_OK; + gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request); + + /* add the RTP-Info header */ + 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); + + /* start playing after sending the request */ + gst_rtsp_session_media_play (media); + g_object_unref (session); + + return FALSE; + + /* ERRORS */ +session_not_found: + { + handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); + return FALSE; + } +service_unavailable: + { + handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + return FALSE; + } +} + +static gboolean +handle_setup_response (GstRTSPClient *client, const gchar *uri, GstRTSPMessage *request) +{ + GstRTSPResult res; + gchar *sessid; + gchar *transport; + gchar **transports; + gboolean have_transport; + GstRTSPTransport *ct, *st; + GstRTSPSession *session; + gint i; + GstRTSPLowerTrans supported; + GstRTSPMessage response = { 0 }; + GstRTSPStatusCode code; + GstRTSPSessionStream *stream; + gchar *trans_str, *pos; + guint streamid; + GstRTSPSessionMedia *media; + gboolean need_session; + + /* find the media associated with the uri */ + if (client->media == NULL) { + if ((client->media = gst_rtsp_media_new (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; + + transports = g_strsplit (transport, ",", 0); + gst_rtsp_transport_new (&ct); + + /* loop through the transports, try to parse */ + have_transport = FALSE; + for (i = 0; transports[i]; i++) { + + gst_rtsp_transport_init (ct); + res = gst_rtsp_transport_parse (transports[i], ct); + if (res == GST_RTSP_OK) { + have_transport = TRUE; + break; + } + } + g_strfreev (transports); + + /* we have not found anything usable, error out */ + if (!have_transport) { + gst_rtsp_transport_free (ct); + goto unsupported_transports; + } + + /* we have a valid transport, check if we can handle it */ + if (ct->trans != GST_RTSP_TRANS_RTP) + goto unsupported_transports; + if (ct->profile != GST_RTSP_PROFILE_AVP) + goto unsupported_transports; + supported = GST_RTSP_LOWER_TRANS_UDP | + GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP; + if (!(ct->lower_transport & supported)) + goto unsupported_transports; + + /* a setup request creates a session for a client, check if the client already + * sent a session id to us */ + 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; + 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))) + goto service_unavailable; + need_session = TRUE; + } + + /* get a handle to the configuration of the media in the session */ + media = gst_rtsp_session_get_media (session, client->media); + + /* parse the stream we need to configure */ + if (!(pos = strstr (uri, "stream="))) + goto bad_request; + + pos += strlen ("stream="); + if (sscanf (pos, "%u", &streamid) != 1) + goto bad_request; + + /* get a handle to the stream in the media */ + stream = gst_rtsp_session_get_stream (media, streamid); + + /* setup the server transport from the client transport */ + st = gst_rtsp_session_stream_set_transport (stream, inet_ntoa (client->address.sin_addr), ct); + + /* serialize the server transport */ + trans_str = gst_rtsp_transport_as_text (st); + + /* construct the response now */ + code = GST_RTSP_STS_OK; + gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request); + + 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); + + return TRUE; + + /* ERRORS */ +not_found: + { + handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); + return FALSE; + } +bad_request: + { + handle_generic_response (client, GST_RTSP_STS_BAD_REQUEST, request); + return FALSE; + } +session_not_found: + { + handle_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, request); + return FALSE; + } +unsupported_transports: + { + handle_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, request); + return FALSE; + } +service_unavailable: + { + handle_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, request); + return FALSE; + } +} + +static gboolean +handle_describe_response (GstRTSPClient *client, const gchar *uri, GstRTSPMessage *request) +{ + GstRTSPMessage response = { 0 }; + GstSDPMessage *sdp; + guint n_streams, i; + gchar *sdptext; + GstRTSPMedia *media; + GstElement *pipeline = NULL; + + /* check what kind of format is accepted */ + + + /* for the describe we must generate an SDP */ + if (!(media = gst_rtsp_media_new (uri))) + goto no_media; + + /* create a pipeline if we have to */ + if (pipeline == NULL) { + pipeline = gst_pipeline_new ("client-pipeline"); + } + + /* prepare the media into the pipeline */ + if (!gst_rtsp_media_prepare (media, GST_BIN (pipeline))) + goto no_media; + + /* 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; + + 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"); + gst_pad_link (stream->srcpad, sinkpad); + 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. */ + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + /* 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 (media); + + gst_object_unref (pipeline); + pipeline = NULL; + + gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), request); + + /* add SDP to the response body */ + sdptext = gst_sdp_message_as_text (sdp); + gst_rtsp_message_take_body (&response, (guint8 *)sdptext, strlen (sdptext)); + gst_sdp_message_free (sdp); + + gst_rtsp_connection_send (client->connection, &response, NULL); + + return TRUE; + + /* ERRORS */ +no_media: + { + handle_generic_response (client, GST_RTSP_STS_NOT_FOUND, request); + return FALSE; + } +} + +static void +handle_options_response (GstRTSPClient *client, const gchar *uri, GstRTSPMessage *request) +{ + GstRTSPMessage response = { 0 }; + GstRTSPMethod options; + GString *str; + + gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), request); + + options = GST_RTSP_DESCRIBE | + GST_RTSP_OPTIONS | + // GST_RTSP_PAUSE | + GST_RTSP_PLAY | + 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"); + + gst_rtsp_message_add_header (&response, GST_RTSP_HDR_PUBLIC, str->str); + + g_string_free (str, TRUE); + + gst_rtsp_connection_send (client->connection, &response, NULL); +} + +/* this function runs in a client specific thread and handles all rtsp messages + * with the client */ +static gpointer +handle_client (GstRTSPClient *client) +{ + GstRTSPMessage request = { 0 }; + GstRTSPResult res; + GstRTSPMethod method; + const gchar *uri; + GstRTSPVersion version; + + while (TRUE) { + /* start by waiting for a message from the client */ + res = gst_rtsp_connection_receive (client->connection, &request, NULL); + if (res < 0) + goto receive_failed; + +#ifdef DEBUG + gst_rtsp_message_dump (&request); +#endif + + gst_rtsp_message_parse_request (&request, &method, &uri, &version); + + if (version != GST_RTSP_VERSION_1_0) { + /* we can only handle 1.0 requests */ + handle_generic_response (client, GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED, &request); + continue; + } + + /* now see what is asked and dispatch to a dedicated handler */ + switch (method) { + case GST_RTSP_OPTIONS: + handle_options_response (client, uri, &request); + break; + case GST_RTSP_DESCRIBE: + handle_describe_response (client, uri, &request); + break; + case GST_RTSP_SETUP: + handle_setup_response (client, uri, &request); + break; + case GST_RTSP_PLAY: + handle_play_response (client, uri, &request); + break; + case GST_RTSP_PAUSE: + handle_pause_response (client, uri, &request); + break; + case GST_RTSP_TEARDOWN: + handle_teardown_response (client, uri, &request); + break; + case GST_RTSP_ANNOUNCE: + case GST_RTSP_GET_PARAMETER: + case GST_RTSP_RECORD: + case GST_RTSP_REDIRECT: + case GST_RTSP_SET_PARAMETER: + handle_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, &request); + break; + case GST_RTSP_INVALID: + default: + handle_generic_response (client, GST_RTSP_STS_BAD_REQUEST, &request); + break; + } + } + g_object_unref (client); + return NULL; + + /* ERRORS */ +receive_failed: + { + g_print ("receive failed, disconnect client %p\n", client); + gst_rtsp_connection_close (client->connection); + g_object_unref (client); + return NULL; + } +} + +/* called when we need to accept a new request from a client */ +static gboolean +client_accept (GstRTSPClient *client, GIOChannel *source) +{ + /* a new client connected. */ + int server_sock_fd; + unsigned int address_len; + GstRTSPConnection *conn; + + conn = client->connection; + + server_sock_fd = g_io_channel_unix_get_fd (source); + + address_len = sizeof (client->address); + memset (&client->address, 0, address_len); + + conn->fd.fd = accept (server_sock_fd, (struct sockaddr *) &client->address, + &address_len); + if (conn->fd.fd == -1) + goto accept_failed; + + g_print ("added new client %p ip %s with fd %d\n", client, + inet_ntoa (client->address.sin_addr), conn->fd.fd); + + /* FIXME some hackery, we need to have a connection method to accept server + * connections */ + gst_poll_add_fd (conn->fdset, &conn->fd); + + return TRUE; + + /* ERRORS */ +accept_failed: + { + g_error ("Could not accept client on server socket %d: %s (%d)", + server_sock_fd, g_strerror (errno), errno); + return FALSE; + } +} + +/** + * gst_rtsp_client_set_session_pool: + * @client: a #GstRTSPClient + * @pool: a #GstRTSPSessionPool + * + * Set @pool as the sessionpool for @client which it will use to find + * or allocate sessions. + */ +void +gst_rtsp_client_set_session_pool (GstRTSPClient *client, GstRTSPSessionPool *pool) +{ + GstRTSPSessionPool *old; + + old = client->pool; + if (pool) + g_object_ref (pool); + client->pool = pool; + if (old) + g_object_unref (old); +} + +/** + * gst_rtsp_client_get_session_pool: + * @client: a #GstRTSPClient + * + * Get the #GstRTSPSessionPool object that @client uses to manage its sessions. + * + * Returns: a #GstRTSPSessionPool, unref after usage. + */ +GstRTSPSessionPool * +gst_rtsp_client_get_session_pool (GstRTSPClient *client) +{ + GstRTSPSessionPool *result; + + if ((result = client->pool)) + g_object_ref (result); + + return result; +} + + +/** + * gst_rtsp_client_attach: + * @client: a #GstRTSPClient + * @context: a #GMainContext + * + * Attaches @client to @context. When the mainloop for @context is run, the + * client will be dispatched. + * + * This function should be called when the client properties and urls are fully + * configured and the client is ready to start. + * + * Returns: %TRUE if the client could be accepted. + */ +gboolean +gst_rtsp_client_accept (GstRTSPClient *client, GIOChannel *source) +{ + gst_rtsp_connection_create (NULL, &client->connection); + + if (!client_accept (client, source)) + goto accept_failed; + + /* client accepted, spawn a thread for the client */ + g_object_ref (client); + client->thread = g_thread_create ((GThreadFunc)handle_client, client, TRUE, NULL); + + return TRUE; + + /* ERRORS */ +accept_failed: + { + gst_rtsp_connection_close (client->connection); + return FALSE; + } +} diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h new file mode 100644 index 0000000..da09684 --- /dev/null +++ b/gst/rtsp-server/rtsp-client.h @@ -0,0 +1,87 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <arpa/inet.h> + +#include <gst/gst.h> +#include <gst/rtsp/gstrtspconnection.h> + +#ifndef __GST_RTSP_CLIENT_H__ +#define __GST_RTSP_CLIENT_H__ + +#include "rtsp-media.h" +#include "rtsp-session-pool.h" + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_CLIENT (gst_rtsp_client_get_type ()) +#define GST_IS_RTSP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_CLIENT)) +#define GST_IS_RTSP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_CLIENT)) +#define GST_RTSP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass)) +#define GST_RTSP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClient)) +#define GST_RTSP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass)) +#define GST_RTSP_CLIENT_CAST(obj) ((GstRTSPClient*)(obj)) +#define GST_RTSP_CLIENT_CLASS_CAST(klass) ((GstRTSPClientClass*)(klass)) + +typedef struct _GstRTSPClient GstRTSPClient; +typedef struct _GstRTSPClientClass GstRTSPClientClass; + +struct _GstRTSPClient { + GObject parent; + + GstRTSPConnection *connection; + struct sockaddr_in address; + + GstRTSPMedia *media; + + GstRTSPSessionPool *pool; + + GThread *thread; +}; + +struct _GstRTSPClientClass { + GObjectClass parent_class; +}; + +GType gst_rtsp_client_get_type (void); + +GstRTSPClient * gst_rtsp_client_new (void); + +void gst_rtsp_client_set_session_pool (GstRTSPClient *client, + GstRTSPSessionPool *pool); +GstRTSPSessionPool * gst_rtsp_client_get_session_pool (GstRTSPClient *client); + +gboolean gst_rtsp_client_accept (GstRTSPClient *client, + GIOChannel *source); + +G_END_DECLS + +#endif /* __GST_RTSP_CLIENT_H__ */ diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c new file mode 100644 index 0000000..c021734 --- /dev/null +++ b/gst/rtsp-server/rtsp-media.c @@ -0,0 +1,266 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include "rtsp-media.h" + +G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT); + +static void gst_rtsp_media_finalize (GObject * obj); + +static void +gst_rtsp_media_class_init (GstRTSPMediaClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gst_rtsp_media_finalize; +} + +static void +gst_rtsp_media_init (GstRTSPMedia * media) +{ + media->streams = g_array_new (FALSE, TRUE, sizeof (GstRTSPMediaStream *)); +} + +static void +gst_rtsp_media_stream_free (GstRTSPMediaStream *stream) +{ +} + +static void +gst_rtsp_media_finalize (GObject * obj) +{ + GstRTSPMedia *media; + guint i; + + media = GST_RTSP_MEDIA (obj); + + g_free (media->location); + gst_rtsp_url_free (media->url); + + for (i = 0; i < media->streams->len; i++) { + GstRTSPMediaStream *stream; + + stream = g_array_index (media->streams, GstRTSPMediaStream *, i); + + gst_rtsp_media_stream_free (stream); + } + g_array_free (media->streams, TRUE); + + G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj); +} + +/** + * gst_rtsp_media_new: + * @location: the URL of the media + * + * Create a new #GstRTSPMedia instance. + * + * Returns: a new #GstRTSPMedia object or %NULL when location did not contain a + * valid or understood URL. + */ +GstRTSPMedia * +gst_rtsp_media_new (const gchar *location) +{ + GstRTSPMedia *result; + GstRTSPUrl *url; + + url = NULL; + + if (gst_rtsp_url_parse (location, &url) != GST_RTSP_OK) + goto invalid_url; + + result = g_object_new (GST_TYPE_RTSP_MEDIA, NULL); + result->location = g_strdup (location); + result->url = url; + + return result; + + /* ERRORS */ +invalid_url: + { + return NULL; + } +} + +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); +} + +/** + * + * STREAMING CONFIGURATION + * + * gst_rtsp_media_prepare: + * @media: a #GstRTSPMedia + * @bin: the parent bin to create the elements in. + * + * Prepare the media object so that it creates its streams. Implementations + * should crate the needed gstreamer elements and add them to @bin. No state + * changes should be performed on them yet. + * + * One or more GstRTSPMediaStream objects should be added to @media with the + * srcpad member set to a source pad that produces buffer of type + * application/x-rtp. + * + * Returns: %TRUE if the media could be prepared. + */ +gboolean +gst_rtsp_media_prepare (GstRTSPMedia *media, GstBin *bin) +{ + GstRTSPMediaStream *stream; + GstElement *pay, *element; + GstPad * pad; + gint i; + + /* if we're already prepared we must exit */ + g_return_val_if_fail (media->prepared == FALSE, FALSE); + + g_print ("%s\n", media->url->abspath); + + if (g_str_has_prefix (media->url->abspath, "/camera")) { + /* live */ + element = gst_parse_launch ("( " + "v4l2src ! video/x-raw-yuv,width=352,height=288,framerate=15/1 ! " + "queue ! videorate ! ffmpegcolorspace ! " + "x264enc bitrate=300 ! rtph264pay name=pay0 pt=96 " + "alsasrc ! audio/x-raw-int,rate=8000 ! queue ! " + "amrnbenc ! rtpamrpay name=pay1 pt=97 " + ")", NULL); + } + else if (g_str_has_prefix (media->url->abspath, "/h264")) { + /* transcode h264 */ + element = gst_parse_launch ("( uridecodebin " + "uri=file:///home/cschalle/Videos/mi2.avi ! " + "x264enc bitrate=300 ! rtph264pay name=pay0 )", NULL); + } + else if (g_str_has_prefix (media->url->abspath, "/theora")) { + /* transcode theora */ + element = gst_parse_launch ("( uridecodebin " + "uri=file:///home/wim/data/mi2.avi ! " + "theoraenc ! rtptheorapay name=pay0 )", NULL); + } + else if (g_str_has_prefix (media->url->abspath, "/macpclinux")) { + /* theora/vorbis */ + element = gst_parse_launch ("( filesrc " + "location=/home/cschalle/Videos/mac_pc_linux_2.ogg ! oggdemux name=d ! " + "queue ! theoraparse ! rtptheorapay name=pay0 " + "d. ! queue ! vorbisparse ! rtpvorbispay name=pay1 )", NULL); + } + else if (g_str_has_prefix (media->url->abspath, "/rtspproxy")) { + /* proxy RTSP transcode */ + element = gst_parse_launch ("( uridecodebin " + "uri=rtsp://ia300135.us.archive.org:554/0/items/uncovered_interviews/uncovered_interviews_3_256kb.mp4 ! " + "x264enc bitrate=1800 ! rtph264pay name=pay0 )", NULL); + } + else if (g_str_has_prefix (media->url->abspath, "/httpproxy")) { + /* proxy HTTP transcode */ + element = gst_parse_launch ("( uridecodebin " + "uri=http://movies.apple.com/movies/fox/maxpayne/maxpayne-tlre_h480.mov name=d " + "d. ! queue ! x264enc bitrate=1800 ! rtph264pay name=pay0 pt=96 " + "d. ! queue ! faac ! rtpmp4gpay name=pay1 pt=97 )", NULL); + } + else + return FALSE; + + gst_bin_add (bin, element); + + for (i = 0; ; i++) { + gchar *name; + + name = g_strdup_printf ("pay%d", i); + + if (!(pay = gst_bin_get_by_name (GST_BIN (element), name))) { + g_free (name); + break; + } + + /* create the stream */ + stream = g_new0 (GstRTSPMediaStream, 1); + stream->media = media; + stream->element = element; + stream->payloader = pay; + stream->idx = media->streams->len; + + pad = gst_element_get_static_pad (pay, "src"); + + 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); + + /* add stream now */ + g_array_append_val (media->streams, stream); + gst_object_unref (pay); + + g_free (name); + } + + media->prepared = TRUE; + + return TRUE; +} + +/** + * gst_rtsp_media_n_streams: + * @media: a #GstRTSPMedia + * + * Get the number of streams in this media. + * + * Returns: The number of streams. + */ +guint +gst_rtsp_media_n_streams (GstRTSPMedia *media) +{ + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0); + g_return_val_if_fail (media->prepared, 0); + + return media->streams->len; +} + +/** + * gst_rtsp_media_get_stream: + * @media: a #GstRTSPMedia + * @idx: the stream index + * + * Retrieve the stream with index @idx from @media. + * + * Returns: the #GstRTSPMediaStream at index @idx. + */ +GstRTSPMediaStream * +gst_rtsp_media_get_stream (GstRTSPMedia *media, guint idx) +{ + GstRTSPMediaStream *res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + g_return_val_if_fail (media->prepared, 0); + g_return_val_if_fail (idx < media->streams->len, NULL); + + res = g_array_index (media->streams, GstRTSPMediaStream *, idx); + + return res; +} + diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h new file mode 100644 index 0000000..bf4bede --- /dev/null +++ b/gst/rtsp-server/rtsp-media.h @@ -0,0 +1,79 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <gst/gst.h> +#include <gst/rtsp/gstrtspurl.h> + +#ifndef __GST_RTSP_MEDIA_H__ +#define __GST_RTSP_MEDIA_H__ + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_MEDIA (gst_rtsp_media_get_type ()) +#define GST_IS_RTSP_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA)) +#define GST_IS_RTSP_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA)) +#define GST_RTSP_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass)) +#define GST_RTSP_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMedia)) +#define GST_RTSP_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass)) +#define GST_RTSP_MEDIA_CAST(obj) ((GstRTSPMedia*)(obj)) +#define GST_RTSP_MEDIA_CLASS_CAST(klass) ((GstRTSPMediaClass*)(klass)) + +typedef struct _GstRTSPMedia GstRTSPMedia; +typedef struct _GstRTSPMediaStream GstRTSPMediaStream; +typedef struct _GstRTSPMediaClass GstRTSPMediaClass; + +struct _GstRTSPMediaStream { + GstRTSPMedia *media; + + guint idx; + gchar *name; + + GstElement *element; + GstPad *srcpad; + GstElement *payloader; + gulong caps_sig; + GstCaps *caps; +}; + +struct _GstRTSPMedia { + GObject parent; + + gchar *location; + GstRTSPUrl *url; + + gboolean prepared; + GArray *streams; +}; + +struct _GstRTSPMediaClass { + GObjectClass parent_class; +}; + +GType gst_rtsp_media_get_type (void); + +GstRTSPMedia * gst_rtsp_media_new (const gchar *name); + +gboolean gst_rtsp_media_prepare (GstRTSPMedia *media, GstBin *bin); + +guint gst_rtsp_media_n_streams (GstRTSPMedia *media); +GstRTSPMediaStream * gst_rtsp_media_get_stream (GstRTSPMedia *media, guint idx); + +G_END_DECLS + +#endif /* __GST_RTSP_MEDIA_H__ */ diff --git a/gst/rtsp-server/rtsp-server.c b/gst/rtsp-server/rtsp-server.c new file mode 100644 index 0000000..5242752 --- /dev/null +++ b/gst/rtsp-server/rtsp-server.c @@ -0,0 +1,232 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <sys/ioctl.h> + +#include "rtsp-server.h" +#include "rtsp-client.h" + +#define TCP_BACKLOG 5 +#define DEFAULT_PORT 1554 + +G_DEFINE_TYPE (GstRTSPServer, gst_rtsp_server, G_TYPE_OBJECT); + +static void +gst_rtsp_server_class_init (GstRTSPServerClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); +} + +static void +gst_rtsp_server_init (GstRTSPServer * server) +{ + server->server_port = DEFAULT_PORT; + server->pool = gst_rtsp_session_pool_new (); +} + +/** + * gst_rtsp_server_new: + * + * Create a new #GstRTSPServer instance. + */ +GstRTSPServer * +gst_rtsp_server_new (void) +{ + GstRTSPServer *result; + + result = g_object_new (GST_TYPE_RTSP_SERVER, NULL); + + return result; +} + +static gboolean +gst_rtsp_server_sink_init_send (GstRTSPServer * server) +{ + int ret; + + /* create server socket */ + if ((server->server_sock.fd = socket (AF_INET, SOCK_STREAM, 0)) == -1) + goto no_socket; + + GST_DEBUG_OBJECT (server, "opened sending server socket with fd %d", + server->server_sock.fd); + + /* make address reusable */ + ret = 1; + if (setsockopt (server->server_sock.fd, SOL_SOCKET, SO_REUSEADDR, + (void *) &ret, sizeof (ret)) < 0) + goto reuse_failed; + + /* keep connection alive; avoids SIGPIPE during write */ + ret = 1; + if (setsockopt (server->server_sock.fd, SOL_SOCKET, SO_KEEPALIVE, + (void *) &ret, sizeof (ret)) < 0) + goto keepalive_failed; + + /* name the socket */ + memset (&server->server_sin, 0, sizeof (server->server_sin)); + server->server_sin.sin_family = AF_INET; /* network socket */ + server->server_sin.sin_port = htons (server->server_port); /* on port */ + server->server_sin.sin_addr.s_addr = htonl (INADDR_ANY); /* for hosts */ + + /* bind it */ + GST_DEBUG_OBJECT (server, "binding server socket to address"); + ret = bind (server->server_sock.fd, (struct sockaddr *) &server->server_sin, + sizeof (server->server_sin)); + if (ret) + goto bind_failed; + + /* set the server socket to nonblocking */ + fcntl (server->server_sock.fd, F_SETFL, O_NONBLOCK); + + GST_DEBUG_OBJECT (server, "listening on server socket %d with queue of %d", + server->server_sock.fd, TCP_BACKLOG); + if (listen (server->server_sock.fd, TCP_BACKLOG) == -1) + goto listen_failed; + + GST_DEBUG_OBJECT (server, + "listened on server socket %d, returning from connection setup", + server->server_sock.fd); + + return TRUE; + + /* ERRORS */ +no_socket: + { + GST_ERROR_OBJECT (server, "failed to create socket: %s", g_strerror (errno)); + return FALSE; + } +reuse_failed: + { + if (server->server_sock.fd >= 0) { + close (server->server_sock.fd); + server->server_sock.fd = -1; + } + GST_ERROR_OBJECT (server, "failed to reuse socket: %s", g_strerror (errno)); + return FALSE; + } +keepalive_failed: + { + if (server->server_sock.fd >= 0) { + close (server->server_sock.fd); + server->server_sock.fd = -1; + } + GST_ERROR_OBJECT (server, "failed to configure keepalive socket: %s", g_strerror (errno)); + return FALSE; + } +listen_failed: + { + if (server->server_sock.fd >= 0) { + close (server->server_sock.fd); + server->server_sock.fd = -1; + } + GST_ERROR_OBJECT (server, "failed to listen on socket: %s", g_strerror (errno)); + return FALSE; + } +bind_failed: + { + if (server->server_sock.fd >= 0) { + close (server->server_sock.fd); + server->server_sock.fd = -1; + } + GST_ERROR_OBJECT (server, "failed to bind on socket: %s", g_strerror (errno)); + return FALSE; + } +} + +/* called when an event is available on our server socket */ +static gboolean +server_dispatch (GIOChannel *source, GIOCondition condition, GstRTSPServer *server) +{ + if (condition & G_IO_IN) { + GstRTSPClient *client; + + /* a new client connected, create a session to handle the client. */ + client = gst_rtsp_client_new (); + + /* set the session pool that this client should use */ + gst_rtsp_client_set_session_pool (client, server->pool); + + /* accept connections for that client, this function returns after accepting + * the connection and will run the remainder of the communication with the + * client asyncronously. */ + if (!gst_rtsp_client_accept (client, source)) + goto accept_failed; + + /* can unref the client now, when the request is finished, it will be + * unreffed async. */ + gst_object_unref (client); + } + else { + g_print ("received unknown event %08x", condition); + } + return TRUE; + + /* ERRORS */ +accept_failed: + { + g_error ("Could not accept client on server socket %d: %s (%d)", + server->server_sock.fd, g_strerror (errno), errno); + return FALSE; + } +} + +/** + * gst_rtsp_server_attach: + * @server: a #GstRTSPServer + * @context: a #GMainContext + * + * Attaches @server to @context. When the mainloop for @context is run, the + * server will be dispatched. + * + * This function should be called when the server properties and urls are fully + * configured and the server is ready to start. + * + * Returns: the ID (greater than 0) for the source within the GMainContext. + */ +guint +gst_rtsp_server_attach (GstRTSPServer *server, GMainContext *context) +{ + guint res; + + if (!gst_rtsp_server_sink_init_send (server)) + goto init_failed; + + /* create IO channel for the socket */ + server->io_channel = g_io_channel_unix_new (server->server_sock.fd); + + /* create a watch for reads (new connections) and possible errors */ + server->io_watch = g_io_create_watch (server->io_channel, G_IO_IN | + G_IO_ERR | G_IO_HUP | G_IO_NVAL); + + /* configure the callback */ + g_source_set_callback (server->io_watch, (GSourceFunc) server_dispatch, server, NULL); + + res = g_source_attach (server->io_watch, context); + + return res; + + /* ERRORS */ +init_failed: + { + return 0; + } +} diff --git a/gst/rtsp-server/rtsp-server.h b/gst/rtsp-server/rtsp-server.h new file mode 100644 index 0000000..93bc820 --- /dev/null +++ b/gst/rtsp-server/rtsp-server.h @@ -0,0 +1,86 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <arpa/inet.h> + +#include <gst/gst.h> + +#include "rtsp-session-pool.h" + +#ifndef __GST_RTSP_SERVER_H__ +#define __GST_RTSP_SERVER_H__ + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_SERVER (gst_rtsp_server_get_type ()) +#define GST_IS_RTSP_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SERVER)) +#define GST_IS_RTSP_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SERVER)) +#define GST_RTSP_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServerClass)) +#define GST_RTSP_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServer)) +#define GST_RTSP_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SERVER, GstRTSPServerClass)) +#define GST_RTSP_SERVER_CAST(obj) ((GstRTSPServer*)(obj)) +#define GST_RTSP_SERVER_CLASS_CAST(klass) ((GstRTSPServerClass*)(klass)) + +typedef struct _GstRTSPServer GstRTSPServer; +typedef struct _GstRTSPServerClass GstRTSPServerClass; + +struct _GstRTSPServer { + GObject parent; + + /* server information */ + int server_port; + gchar *host; + struct sockaddr_in server_sin; + + /* socket */ + GstPollFD server_sock; + + GIOChannel *io_channel; + GSource *io_watch; + + /* sessions on this server */ + GstRTSPSessionPool *pool; +}; + +struct _GstRTSPServerClass { + GObjectClass parent_class; +}; + +GType gst_rtsp_server_get_type (void); + +GstRTSPServer * gst_rtsp_server_new (void); + +guint gst_rtsp_server_attach (GstRTSPServer *server, + GMainContext *context); + +G_END_DECLS + +#endif /* __GST_RTSP_SERVER_H__ */ diff --git a/gst/rtsp-server/rtsp-session-pool.c b/gst/rtsp-server/rtsp-session-pool.c new file mode 100644 index 0000000..716e888 --- /dev/null +++ b/gst/rtsp-server/rtsp-session-pool.c @@ -0,0 +1,165 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include "rtsp-session-pool.h" + +#undef DEBUG + +G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT); + +static void +gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); +} + +static void +gst_rtsp_session_pool_init (GstRTSPSessionPool * pool) +{ + pool->lock = g_mutex_new (); + pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} + +/** + * gst_rtsp_session_pool_new: + * + * Create a new #GstRTSPSessionPool instance. + */ +GstRTSPSessionPool * +gst_rtsp_session_pool_new (void) +{ + GstRTSPSessionPool *result; + + result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL); + + return result; +} + +/** + * gst_rtsp_session_pool_find: + * @pool: the pool to search + * @sessionid: the session id + * + * Find the session with @sessionid in @pool. + * + * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did + * not exist. g_object_unref() after usage. + */ +GstRTSPSession * +gst_rtsp_session_pool_find (GstRTSPSessionPool *pool, const gchar *sessionid) +{ + GstRTSPSession *result; + + g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL); + g_return_val_if_fail (sessionid != NULL, NULL); + + g_mutex_lock (pool->lock); + result = g_hash_table_lookup (pool->sessions, sessionid); + if (result) + g_object_ref (result); + g_mutex_unlock (pool->lock); + + return result; +} + +static gchar * +create_session_id (void) +{ + gchar id[16]; + gint i; + + for (i = 0; i < 16; i++) { + id[i] = g_random_int_range ('a', 'z'); + } + + return g_strndup (id, 16); +} + +/** + * gst_rtsp_session_pool_create: + * @pool: a #GstRTSPSessionPool + * + * Create a new #GstRTSPSession object in @pool. + * + * Returns: a new #GstRTSPSession. + */ +GstRTSPSession * +gst_rtsp_session_pool_create (GstRTSPSessionPool *pool) +{ + GstRTSPSession *result = NULL; + gchar *id; + + g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL); + + do { + /* start by creating a new random session id, we assume that this is random + * enough to not cause a collision, which we will check later */ + id = create_session_id (); + + g_mutex_lock (pool->lock); + /* check if the sessionid existed */ + result = g_hash_table_lookup (pool->sessions, id); + if (result) { + /* found, retry with a different session id*/ + result = NULL; + } + else { + /* not found, create session and insert it in the pool */ + result = gst_rtsp_session_new (id); + /* take additional ref for the pool */ + g_object_ref (result); + g_hash_table_insert (pool->sessions, result->sessionid, result); + } + g_mutex_unlock (pool->lock); + + g_free (id); + } while (result == NULL); + + return result; +} + +/** + * gst_rtsp_session_pool_remove: + * @pool: a #GstRTSPSessionPool + * @sess: a #GstRTSPSession + * + * Remove @sess from @pool and Clean it up. + * + * Returns: a new #GstRTSPSession. + */ +void +gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, GstRTSPSession *sess) +{ + gboolean found; + + g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool)); + g_return_if_fail (GST_IS_RTSP_SESSION (sess)); + + g_mutex_lock (pool->lock); + found = g_hash_table_remove (pool->sessions, sess); + g_mutex_unlock (pool->lock); + + if (found) { + g_object_unref (sess); + } +} + diff --git a/gst/rtsp-server/rtsp-session-pool.h b/gst/rtsp-server/rtsp-session-pool.h new file mode 100644 index 0000000..9336647 --- /dev/null +++ b/gst/rtsp-server/rtsp-session-pool.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <gst/gst.h> + +#ifndef __GST_RTSP_SESSION_POOL_H__ +#define __GST_RTSP_SESSION_POOL_H__ + +#include "rtsp-session.h" + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_SESSION_POOL (gst_rtsp_session_pool_get_type ()) +#define GST_IS_RTSP_SESSION_POOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SESSION_POOL)) +#define GST_IS_RTSP_SESSION_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SESSION_POOL)) +#define GST_RTSP_SESSION_POOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolClass)) +#define GST_RTSP_SESSION_POOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPool)) +#define GST_RTSP_SESSION_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolClass)) +#define GST_RTSP_SESSION_POOL_CAST(obj) ((GstRTSPSessionPool*)(obj)) +#define GST_RTSP_SESSION_POOL_CLASS_CAST(klass) ((GstRTSPSessionPoolClass*)(klass)) + +typedef struct _GstRTSPSessionPool GstRTSPSessionPool; +typedef struct _GstRTSPSessionPoolClass GstRTSPSessionPoolClass; + +/** + * GstRTSPSessionPool: + * + * An object that keeps track of the active sessions. + */ +struct _GstRTSPSessionPool { + GObject parent; + + GMutex *lock; + GHashTable *sessions; +}; + +struct _GstRTSPSessionPoolClass { + GObjectClass parent_class; +}; + +GType gst_rtsp_session_pool_get_type (void); + +GstRTSPSessionPool * gst_rtsp_session_pool_new (void); + +GstRTSPSession * gst_rtsp_session_pool_find (GstRTSPSessionPool *pool, + const gchar *sessionid); +GstRTSPSession * gst_rtsp_session_pool_create (GstRTSPSessionPool *pool); +void gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, + GstRTSPSession *sess); + +G_END_DECLS + +#endif /* __GST_RTSP_SESSION_POOL_H__ */ diff --git a/gst/rtsp-server/rtsp-session.c b/gst/rtsp-server/rtsp-session.c new file mode 100644 index 0000000..05ca1f3 --- /dev/null +++ b/gst/rtsp-server/rtsp-session.c @@ -0,0 +1,507 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include "rtsp-session.h" + +#undef DEBUG + +static void gst_rtsp_session_finalize (GObject * obj); + +G_DEFINE_TYPE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT); + +static void +gst_rtsp_session_class_init (GstRTSPSessionClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gst_rtsp_session_finalize; +} + +static void +gst_rtsp_session_init (GstRTSPSession * session) +{ +} + +static void +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); +} + +static void +gst_rtsp_session_free_media (GstRTSPSessionMedia *media) +{ + GList *walk; + + gst_element_set_state (media->pipeline, GST_STATE_NULL); + + if (media->media) + g_object_unref (media->media); + + 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); +} + +static void +gst_rtsp_session_finalize (GObject * obj) +{ + GstRTSPSession *session; + GList *walk; + + session = GST_RTSP_SESSION (obj); + + g_free (session->sessionid); + + for (walk = session->medias; walk; walk = g_list_next (walk)) { + GstRTSPSessionMedia *media = (GstRTSPSessionMedia *) walk->data; + + gst_rtsp_session_free_media (media); + } + g_list_free (session->medias); + + G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj); +} + +/** + * gst_rtsp_session_get_media: + * @sess: a #GstRTSPSession + * @media: a #GstRTSPSessionMedia + * + * Get or create the session information for @media. + * + * Returns: the configuration for @media in @sess. + */ +GstRTSPSessionMedia * +gst_rtsp_session_get_media (GstRTSPSession *sess, GstRTSPMedia *media) +{ + GstRTSPSessionMedia *result; + GList *walk; + + result = NULL; + + for (walk = sess->medias; walk; walk = g_list_next (walk)) { + result = (GstRTSPSessionMedia *) walk->data; + + if (result->media == media) + break; + + result = NULL; + } + if (result == NULL) { + result = g_new0 (GstRTSPSessionMedia, 1); + result->media = media; + result->pipeline = gst_pipeline_new ("pipeline"); + + /* prepare media into the pipeline */ + if (!gst_rtsp_media_prepare (media, GST_BIN (result->pipeline))) + goto no_media; + + 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 FALSE; + } +} + +/** + * gst_rtsp_session_get_stream: + * @media: a #GstRTSPSessionMedia + * @idx: the stream index + * + * Get a previously created or create a new #GstRTSPSessionStream at @idx. + * + * Returns: a #GstRTSPSessionStream that is valid until the session of @media + * is unreffed. + */ +GstRTSPSessionStream * +gst_rtsp_session_get_stream (GstRTSPSessionMedia *media, guint idx) +{ + GstRTSPSessionStream *result; + GList *walk; + + result = NULL; + + for (walk = media->streams; walk; walk = g_list_next (walk)) { + result = (GstRTSPSessionStream *) walk->data; + + if (result->idx == idx) + break; + + result = NULL; + } + if (result == NULL) { + result = g_new0 (GstRTSPSessionStream, 1); + result->idx = idx; + result->media = media; + result->media_stream = gst_rtsp_media_get_stream (media->media, idx); + + media->streams = g_list_prepend (media->streams, result); + } + return result; +} + +/** + * gst_rtsp_session_new: + * + * Create a new #GstRTSPSession instance. + */ +GstRTSPSession * +gst_rtsp_session_new (const gchar *sessionid) +{ + GstRTSPSession *result; + + result = g_object_new (GST_TYPE_RTSP_SESSION, NULL); + result->sessionid = g_strdup (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. + * + * 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 *st; + GstPad *pad; + gchar *name; + GstRTSPSessionMedia *media; + + media = stream->media; + + /* prepare the server transport */ + gst_rtsp_transport_new (&st); + + st->trans = ct->trans; + st->profile = ct->profile; + st->lower_transport = ct->lower_transport; + 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); + + return st; +} + +/** + * gst_rtsp_session_media_play: + * @media: a #GstRTSPSessionMedia + * + * Tell the media object @media to start playing and streaming to the client. + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_session_media_play (GstRTSPSessionMedia *media) +{ + GstStateChangeReturn ret; + + ret = gst_element_set_state (media->pipeline, GST_STATE_PLAYING); + + return ret; +} + +/** + * gst_rtsp_session_media_pause: + * @media: a #GstRTSPSessionMedia + * + * Tell the media object @media to pause. + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_session_media_pause (GstRTSPSessionMedia *media) +{ + GstStateChangeReturn ret; + + ret = gst_element_set_state (media->pipeline, GST_STATE_PAUSED); + + return ret; +} + +/** + * gst_rtsp_session_media_stop: + * @media: a #GstRTSPSessionMedia + * + * Tell the media object @media to stop playing. After this call the media + * cannot be played or paused anymore + * + * Returns: a #GstStateChangeReturn + */ +GstStateChangeReturn +gst_rtsp_session_media_stop (GstRTSPSessionMedia *media) +{ + GstStateChangeReturn ret; + + ret = gst_element_set_state (media->pipeline, GST_STATE_NULL); + + return ret; +} + + diff --git a/gst/rtsp-server/rtsp-session.h b/gst/rtsp-server/rtsp-session.h new file mode 100644 index 0000000..5ad4b3b --- /dev/null +++ b/gst/rtsp-server/rtsp-session.h @@ -0,0 +1,138 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans <wim.taymans 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. + */ + +#include <gst/gst.h> + +#include <gst/rtsp/gstrtsptransport.h> + +#include "rtsp-media.h" + +#ifndef __GST_RTSP_SESSION_H__ +#define __GST_RTSP_SESSION_H__ + +G_BEGIN_DECLS + +#define GST_TYPE_RTSP_SESSION (gst_rtsp_session_get_type ()) +#define GST_IS_RTSP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SESSION)) +#define GST_IS_RTSP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SESSION)) +#define GST_RTSP_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SESSION, GstRTSPSessionClass)) +#define GST_RTSP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SESSION, GstRTSPSession)) +#define GST_RTSP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SESSION, GstRTSPSessionClass)) +#define GST_RTSP_SESSION_CAST(obj) ((GstRTSPSession*)(obj)) +#define GST_RTSP_SESSION_CLASS_CAST(klass) ((GstRTSPSessionClass*)(klass)) + +typedef struct _GstRTSPSession GstRTSPSession; +typedef struct _GstRTSPSessionClass GstRTSPSessionClass; + +typedef struct _GstRTSPSessionStream GstRTSPSessionStream; +typedef struct _GstRTSPSessionMedia GstRTSPSessionMedia; + +/** + * GstRTSPSessionStream: + * + * Configuration of a stream. + */ +struct _GstRTSPSessionStream +{ + guint idx; + + /* the owner media */ + GstRTSPSessionMedia *media; + + 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. + */ +struct _GstRTSPSessionMedia +{ + /* the owner session */ + GstRTSPSession *session; + + /* the media we are handling */ + GstRTSPMedia *media; + + /* the pipeline for the media */ + GstElement *pipeline; + + /* RTP session manager */ + GstElement *rtpbin; + + /* for TCP transport */ + GstElement *fdsink; + + /* configuration for the different streams */ + GList *streams; +}; + +/** + * GstRTSPSession: + * + * Session information kept by the server for a specific client. + */ +struct _GstRTSPSession { + GObject parent; + + gchar *sessionid; + + GList *medias; +}; + +struct _GstRTSPSessionClass { + GObjectClass parent_class; +}; + +GType gst_rtsp_session_get_type (void); + +GstRTSPSession * gst_rtsp_session_new (const gchar *sessionid); + +GstRTSPSessionMedia * gst_rtsp_session_get_media (GstRTSPSession *sess, + GstRTSPMedia *media); +GstRTSPSessionStream * gst_rtsp_session_get_stream (GstRTSPSessionMedia *media, + guint idx); + +GstStateChangeReturn gst_rtsp_session_media_play (GstRTSPSessionMedia *media); +GstStateChangeReturn gst_rtsp_session_media_pause (GstRTSPSessionMedia *media); +GstStateChangeReturn gst_rtsp_session_media_stop (GstRTSPSessionMedia *media); + +GstRTSPTransport * gst_rtsp_session_stream_set_transport (GstRTSPSessionStream *stream, + const gchar *destination, + GstRTSPTransport *ct); + +G_END_DECLS + +#endif /* __GST_RTSP_SESSION_H__ */ |