summaryrefslogtreecommitdiff
path: root/gst/rtsp-server
diff options
context:
space:
mode:
authorWim Taymans <wim.taymans@collabora.co.uk>2009-01-08 13:18:55 +0100
committerWim Taymans <wim@wtay.(none)>2009-01-08 13:18:55 +0100
commit78893957870ab6146d3bcf9add9a94e111803d2f (patch)
tree15323fe00725b3a9d1f3a9527c8e0178a1b19651 /gst/rtsp-server
parentc91ec684e973e15699e8f6e08945ee0cf8df4571 (diff)
Split in library and example program
Diffstat (limited to 'gst/rtsp-server')
-rw-r--r--gst/rtsp-server/Makefile.am28
-rw-r--r--gst/rtsp-server/rtsp-client.c812
-rw-r--r--gst/rtsp-server/rtsp-client.h87
-rw-r--r--gst/rtsp-server/rtsp-media.c266
-rw-r--r--gst/rtsp-server/rtsp-media.h79
-rw-r--r--gst/rtsp-server/rtsp-server.c232
-rw-r--r--gst/rtsp-server/rtsp-server.h86
-rw-r--r--gst/rtsp-server/rtsp-session-pool.c165
-rw-r--r--gst/rtsp-server/rtsp-session-pool.h69
-rw-r--r--gst/rtsp-server/rtsp-session.c507
-rw-r--r--gst/rtsp-server/rtsp-session.h138
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", &timestamp, 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__ */