summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/Makefile.am2
-rw-r--r--examples/test-record.c68
-rw-r--r--gst/rtsp-server/rtsp-client.c372
-rw-r--r--gst/rtsp-server/rtsp-client.h8
-rw-r--r--gst/rtsp-server/rtsp-media-factory.c70
-rw-r--r--gst/rtsp-server/rtsp-media-factory.h4
-rw-r--r--gst/rtsp-server/rtsp-media.c776
-rw-r--r--gst/rtsp-server/rtsp-media.h10
-rw-r--r--gst/rtsp-server/rtsp-session-media.c3
-rw-r--r--gst/rtsp-server/rtsp-stream.c236
-rw-r--r--gst/rtsp-server/rtsp-stream.h5
11 files changed, 1468 insertions, 86 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 6d13ed6737..ef33adf75b 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,7 +1,7 @@
noinst_PROGRAMS = test-video test-ogg test-mp4 test-readme \
test-launch test-sdp test-uri test-auth \
test-multicast test-multicast2 test-appsrc \
- test-video-rtx
+ test-video-rtx test-record
#INCLUDES = -I$(top_srcdir) -I$(srcdir)
diff --git a/examples/test-record.c b/examples/test-record.c
new file mode 100644
index 0000000000..676ae982e5
--- /dev/null
+++ b/examples/test-record.c
@@ -0,0 +1,68 @@
+/* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/gst.h>
+
+#include <gst/rtsp-server/rtsp-server.h>
+
+int
+main (int argc, char *argv[])
+{
+ GMainLoop *loop;
+ GstRTSPServer *server;
+ GstRTSPMountPoints *mounts;
+ GstRTSPMediaFactory *factory;
+
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ /* create a server instance */
+ server = gst_rtsp_server_new ();
+
+ /* get the mount points for this server, every server has a default object
+ * that be used to map uri mount points to media factories */
+ mounts = gst_rtsp_server_get_mount_points (server);
+
+ /* make a media factory for a test stream. The default media factory can use
+ * gst-launch syntax to create pipelines.
+ * any launch line works as long as it contains elements named depay%d. Each
+ * element with depay%d names will be a stream */
+ factory = gst_rtsp_media_factory_new ();
+ gst_rtsp_media_factory_set_record (factory, TRUE);
+ gst_rtsp_media_factory_set_launch (factory,
+ "( decodebin name=depay0 ! autovideosink decodebin name=depay1 ! autoaudiosink )");
+
+ /* attach the test factory to the /test url */
+ gst_rtsp_mount_points_add_factory (mounts, "/test", factory);
+
+ /* don't need the ref to the mapper anymore */
+ g_object_unref (mounts);
+
+ /* attach the server to the default maincontext */
+ gst_rtsp_server_attach (server, NULL);
+
+ /* start serving */
+ g_print ("stream ready at rtsp://127.0.0.1:8554/test\n");
+ g_main_loop_run (loop);
+
+ return 0;
+}
diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c
index c6b582c143..d69ef0a34f 100644
--- a/gst/rtsp-server/rtsp-client.c
+++ b/gst/rtsp-server/rtsp-client.c
@@ -1,5 +1,7 @@
/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -123,6 +125,8 @@ enum
SIGNAL_GET_PARAMETER_REQUEST,
SIGNAL_HANDLE_RESPONSE,
SIGNAL_SEND_MESSAGE,
+ SIGNAL_ANNOUNCE_REQUEST,
+ SIGNAL_RECORD_REQUEST,
SIGNAL_LAST
};
@@ -138,6 +142,8 @@ static void gst_rtsp_client_set_property (GObject * object, guint propid,
static void gst_rtsp_client_finalize (GObject * obj);
static GstSDPMessage *create_sdp (GstRTSPClient * client, GstRTSPMedia * media);
+static gboolean handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPMedia * media, GstSDPMessage * sdp);
static gboolean default_configure_client_media (GstRTSPClient * client,
GstRTSPMedia * media, GstRTSPStream * stream, GstRTSPContext * ctx);
static gboolean default_configure_client_transport (GstRTSPClient * client,
@@ -167,6 +173,7 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass)
gobject_class->finalize = gst_rtsp_client_finalize;
klass->create_sdp = create_sdp;
+ klass->handle_sdp = handle_sdp;
klass->configure_client_media = default_configure_client_media;
klass->configure_client_transport = default_configure_client_transport;
klass->params_set = default_params_set;
@@ -266,6 +273,18 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass)
send_message), NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_POINTER);
+ gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST] =
+ g_signal_new ("announce-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, announce_request),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST] =
+ g_signal_new ("record-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, record_request),
+ NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
tunnels =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
g_mutex_init (&tunnels_lock);
@@ -606,8 +625,6 @@ find_media (GstRTSPClient * client, GstRTSPContext * ctx, gchar * path,
path_len = strlen (path);
if (!paths_are_equal (priv->path, path, path_len)) {
- GstRTSPThread *thread;
-
/* remove any previously cached values before we try to construct a new
* media for uri */
clean_cached_media (client, TRUE);
@@ -618,14 +635,18 @@ find_media (GstRTSPClient * client, GstRTSPContext * ctx, gchar * path,
ctx->media = media;
- thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
- GST_RTSP_THREAD_TYPE_MEDIA, ctx);
- if (thread == NULL)
- goto no_thread;
+ if (!gst_rtsp_media_is_record (media)) {
+ GstRTSPThread *thread;
+
+ thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
+ GST_RTSP_THREAD_TYPE_MEDIA, ctx);
+ if (thread == NULL)
+ goto no_thread;
- /* prepare the media */
- if (!(gst_rtsp_media_prepare (media, thread)))
- goto no_prepare;
+ /* prepare the media */
+ if (!gst_rtsp_media_prepare (media, thread))
+ goto no_prepare;
+ }
/* now keep track of the uri and the media */
priv->path = g_strndup (path, path_len);
@@ -1124,6 +1145,9 @@ handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
ctx->sessmedia = sessmedia;
ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
+ if (gst_rtsp_media_is_record (media))
+ goto record_media;
+
/* the session state must be playing or ready */
rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
@@ -1213,6 +1237,12 @@ unsuspend_failed:
send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
return FALSE;
}
+record_media:
+ {
+ GST_ERROR ("client %p: RECORD media does not support PLAY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ return FALSE;
+ }
}
static void
@@ -1400,8 +1430,8 @@ no_address:
}
static GstRTSPTransport *
-make_server_transport (GstRTSPClient * client, GstRTSPContext * ctx,
- GstRTSPTransport * ct)
+make_server_transport (GstRTSPClient * client, GstRTSPMedia * media,
+ GstRTSPContext * ctx, GstRTSPTransport * ct)
{
GstRTSPTransport *st;
GInetAddress *addr;
@@ -1413,6 +1443,8 @@ make_server_transport (GstRTSPClient * client, GstRTSPContext * ctx,
st->trans = ct->trans;
st->profile = ct->profile;
st->lower_transport = ct->lower_transport;
+ st->mode_play = ct->mode_play;
+ st->mode_record = ct->mode_record;
addr = g_inet_address_new_from_string (ct->destination);
@@ -1443,7 +1475,8 @@ make_server_transport (GstRTSPClient * client, GstRTSPContext * ctx,
break;
}
- gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);
+ if (!gst_rtsp_media_is_record (media))
+ gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);
return st;
}
@@ -1824,6 +1857,11 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
if (!parse_transport (transport, stream, ct))
goto unsupported_transports;
+ /* TODO: Add support for PLAY,RECORD media */
+ if ((ct->mode_play && gst_rtsp_media_is_record (media)) ||
+ (ct->mode_record && !gst_rtsp_media_is_record (media)))
+ goto unsupported_mode;
+
/* parse the keymgmt */
if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_KEYMGMT,
&keymgmt, 0) == GST_RTSP_OK) {
@@ -1877,7 +1915,7 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
}
/* create and serialize the server transport */
- st = make_server_transport (client, ctx, ct);
+ st = make_server_transport (client, media, ctx, ct);
trans_str = gst_rtsp_transport_as_text (st);
gst_rtsp_transport_free (st);
@@ -1989,6 +2027,14 @@ unsupported_client_transport:
send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
goto cleanup_transport;
}
+unsupported_mode:
+ {
+ GST_ERROR ("client %p: unsupported mode (media record: %d, mode play: %d"
+ ", mode record: %d)", client, gst_rtsp_media_is_record (media),
+ ct->mode_play, ct->mode_record);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_transport;
+ }
keymgmt_error:
{
GST_ERROR ("client %p: keymgmt error", client);
@@ -2102,6 +2148,9 @@ handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx)
if (!(media = find_media (client, ctx, path, NULL)))
goto no_media;
+ if (gst_rtsp_media_is_record (media))
+ goto record_media;
+
/* create an SDP for the media object on this client */
if (!(sdp = klass->create_sdp (client, media)))
goto no_sdp;
@@ -2161,6 +2210,14 @@ no_media:
/* error reply is already sent */
return FALSE;
}
+record_media:
+ {
+ GST_ERROR ("client %p: RECORD media does not support DESCRIBE", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ g_free (path);
+ g_object_unref (media);
+ return FALSE;
+ }
no_sdp:
{
GST_ERROR ("client %p: can't create SDP", client);
@@ -2172,6 +2229,291 @@ no_sdp:
}
static gboolean
+handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx, GstRTSPMedia * media,
+ GstSDPMessage * sdp)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPThread *thread;
+
+ /* create an SDP for the media object */
+ if (!gst_rtsp_media_handle_sdp (media, sdp))
+ goto unhandled_sdp;
+
+ thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
+ GST_RTSP_THREAD_TYPE_MEDIA, ctx);
+ if (thread == NULL)
+ goto no_thread;
+
+ /* prepare the media */
+ if (!gst_rtsp_media_prepare (media, thread))
+ goto no_prepare;
+
+ return TRUE;
+
+ /* ERRORS */
+unhandled_sdp:
+ {
+ GST_ERROR ("client %p: could not handle SDP", client);
+ return FALSE;
+ }
+no_thread:
+ {
+ GST_ERROR ("client %p: can't create thread", client);
+ return FALSE;
+ }
+no_prepare:
+ {
+ GST_ERROR ("client %p: can't prepare media", client);
+ return FALSE;
+ }
+}
+
+static gboolean
+handle_announce_request (GstRTSPClient * client, GstRTSPContext * ctx)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPClientClass *klass;
+ GstSDPResult sres;
+ GstSDPMessage *sdp;
+ GstRTSPMedia *media;
+ gchar *path, *cont = NULL;
+ guint8 *data;
+ guint size;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ if (!priv->mount_points)
+ goto no_mount_points;
+
+ if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
+ goto no_path;
+
+ /* check if reply is SDP */
+ gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_CONTENT_TYPE, &cont,
+ 0);
+ /* could not be set but since the request returned OK, we assume it
+ * was SDP, else check it. */
+ if (cont) {
+ if (!g_ascii_strcasecmp (cont, "application/sdp") == 0)
+ goto wrong_content_type;
+ }
+
+ /* get message body and parse as SDP */
+ gst_rtsp_message_get_body (ctx->request, &data, &size);
+ if (data == NULL || size == 0)
+ goto no_message;
+
+ GST_DEBUG ("client %p: parse SDP...", client);
+ gst_sdp_message_new (&sdp);
+ sres = gst_sdp_message_parse_buffer (data, size, sdp);
+ if (sres != GST_SDP_OK)
+ goto sdp_parse_failed;
+
+ if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
+ goto no_path;
+
+ /* find the media object for the uri */
+ if (!(media = find_media (client, ctx, path, NULL)))
+ goto no_media;
+
+ if (!gst_rtsp_media_is_record (media))
+ goto play_media;
+
+ /* Tell client subclass about the media */
+ if (!klass->handle_sdp (client, ctx, media, sdp))
+ goto unhandled_sdp;
+
+ /* we suspend after the announce */
+ gst_rtsp_media_suspend (media);
+ g_object_unref (media);
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST],
+ 0, ctx);
+
+ return TRUE;
+
+no_uri:
+ {
+ GST_ERROR ("client %p: no uri", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+no_mount_points:
+ {
+ GST_ERROR ("client %p: no mount points configured", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+no_path:
+ {
+ GST_ERROR ("client %p: can't find path for url", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+wrong_content_type:
+ {
+ GST_ERROR ("client %p: unknown content type", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ g_free (path);
+ return FALSE;
+ }
+no_message:
+ {
+ GST_ERROR ("client %p: can't find SDP message", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ g_free (path);
+ return FALSE;
+ }
+sdp_parse_failed:
+ {
+ GST_ERROR ("client %p: failed to parse SDP message", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ g_free (path);
+ return FALSE;
+ }
+no_media:
+ {
+ GST_ERROR ("client %p: no media", client);
+ g_free (path);
+ /* error reply is already sent */
+ return FALSE;
+ }
+play_media:
+ {
+ GST_ERROR ("client %p: PLAY media does not support ANNOUNCE", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ g_free (path);
+ g_object_unref (media);
+ return FALSE;
+ }
+unhandled_sdp:
+ {
+ GST_ERROR ("client %p: can't handle SDP", client);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_MEDIA_TYPE, ctx);
+ g_free (path);
+ g_object_unref (media);
+ return FALSE;
+ }
+}
+
+static gboolean
+handle_record_request (GstRTSPClient * client, GstRTSPContext * ctx)
+{
+ GstRTSPSession *session;
+ GstRTSPClientClass *klass;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPUrl *uri;
+ GstRTSPState rtspstate;
+ gchar *path;
+ gint matched;
+
+ if (!(session = ctx->session))
+ goto no_session;
+
+ if (!(uri = ctx->uri))
+ goto no_uri;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, uri);
+
+ /* get a handle to the configuration of the media in the session */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ if (!sessmedia)
+ goto not_found;
+
+ if (path[matched] != '\0')
+ goto no_aggregate;
+
+ g_free (path);
+
+ ctx->sessmedia = sessmedia;
+ ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
+
+ if (!gst_rtsp_media_is_record (media))
+ goto play_media;
+
+ /* the session state must be playing or ready */
+ rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
+ if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
+ goto invalid_state;
+
+ /* in play we first unsuspend, media could be suspended from SDP or PAUSED */
+ if (!gst_rtsp_media_unsuspend (media))
+ goto unsuspend_failed;
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ /* start playing after sending the response */
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);
+
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST], 0,
+ ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+no_session:
+ {
+ GST_ERROR ("client %p: no session", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ return FALSE;
+ }
+no_uri:
+ {
+ GST_ERROR ("client %p: no uri supplied", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+not_found:
+ {
+ GST_ERROR ("client %p: media not found", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+no_aggregate:
+ {
+ GST_ERROR ("client %p: no aggregate path %s", client, path);
+ send_generic_response (client,
+ GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
+ g_free (path);
+ return FALSE;
+ }
+play_media:
+ {
+ GST_ERROR ("client %p: PLAY media does not support RECORD", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ return FALSE;
+ }
+invalid_state:
+ {
+ GST_ERROR ("client %p: not PLAYING or READY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ return FALSE;
+ }
+unsuspend_failed:
+ {
+ GST_ERROR ("client %p: unsuspend failed", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ return FALSE;
+ }
+}
+
+static gboolean
handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
GstRTSPMethod options;
@@ -2456,7 +2798,11 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
handle_get_param_request (client, ctx);
break;
case GST_RTSP_ANNOUNCE:
+ handle_announce_request (client, ctx);
+ break;
case GST_RTSP_RECORD:
+ handle_record_request (client, ctx);
+ break;
case GST_RTSP_REDIRECT:
if (priv->watch != NULL)
gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h
index 4e9519a101..5137cc62aa 100644
--- a/gst/rtsp-server/rtsp-client.h
+++ b/gst/rtsp-server/rtsp-client.h
@@ -121,8 +121,14 @@ struct _GstRTSPClientClass {
GstRTSPMessage * response);
void (*send_message) (GstRTSPClient * client, GstRTSPContext *ctx,
GstRTSPMessage * response);
+
+ gboolean (*handle_sdp) (GstRTSPClient *client, GstRTSPContext *ctx, GstRTSPMedia *media, GstSDPMessage *sdp);
+
+ void (*announce_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*record_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+
/*< private >*/
- gpointer _gst_reserved[GST_PADDING_LARGE-2];
+ gpointer _gst_reserved[GST_PADDING_LARGE-5];
};
GType gst_rtsp_client_get_type (void);
diff --git a/gst/rtsp-server/rtsp-media-factory.c b/gst/rtsp-server/rtsp-media-factory.c
index 6e0da84b39..34a0ac84e6 100644
--- a/gst/rtsp-server/rtsp-media-factory.c
+++ b/gst/rtsp-server/rtsp-media-factory.c
@@ -1,5 +1,7 @@
/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -51,6 +53,7 @@ struct _GstRTSPMediaFactoryPrivate
GstRTSPPermissions *permissions;
gchar *launch;
gboolean shared;
+ gboolean record;
GstRTSPSuspendMode suspend_mode;
gboolean eos_shutdown;
GstRTSPProfile profiles;
@@ -72,6 +75,7 @@ struct _GstRTSPMediaFactoryPrivate
#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
GST_RTSP_LOWER_TRANS_TCP
#define DEFAULT_BUFFER_SIZE 0x80000
+#define DEFAULT_RECORD FALSE
enum
{
@@ -83,6 +87,7 @@ enum
PROP_PROFILES,
PROP_PROTOCOLS,
PROP_BUFFER_SIZE,
+ PROP_RECORD,
PROP_LAST
};
@@ -181,6 +186,14 @@ gst_rtsp_media_factory_class_init (GstRTSPMediaFactoryClass * klass)
"The kernel UDP buffer size to use", 0, G_MAXUINT,
DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /* FIXME: Should this be a flag property to allow RECORD and PLAY?
+ * Or just another boolean PLAY property that default to TRUE?
+ */
+ g_object_class_install_property (gobject_class, PROP_RECORD,
+ g_param_spec_boolean ("record", "Record",
+ "If media from this factory is for PLAY or RECORD", DEFAULT_RECORD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
gst_rtsp_media_factory_signals[SIGNAL_MEDIA_CONSTRUCTED] =
g_signal_new ("media-constructed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaFactoryClass,
@@ -273,6 +286,9 @@ gst_rtsp_media_factory_get_property (GObject * object, guint propid,
g_value_set_uint (value,
gst_rtsp_media_factory_get_buffer_size (factory));
break;
+ case PROP_RECORD:
+ g_value_set_boolean (value, gst_rtsp_media_factory_is_record (factory));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@@ -309,6 +325,9 @@ gst_rtsp_media_factory_set_property (GObject * object, guint propid,
gst_rtsp_media_factory_set_buffer_size (factory,
g_value_get_uint (value));
break;
+ case PROP_RECORD:
+ gst_rtsp_media_factory_set_record (factory, g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@@ -1136,6 +1155,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
GstRTSPAddressPool *pool;
GstRTSPPermissions *perms;
GstClockTime rtx_time;
+ gboolean record;
/* configure the sharedness */
GST_RTSP_MEDIA_FACTORY_LOCK (factory);
@@ -1146,6 +1166,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
profiles = priv->profiles;
protocols = priv->protocols;
rtx_time = priv->rtx_time;
+ record = priv->record;
GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
gst_rtsp_media_set_suspend_mode (media, suspend_mode);
@@ -1155,6 +1176,7 @@ default_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media)
gst_rtsp_media_set_profiles (media, profiles);
gst_rtsp_media_set_protocols (media, protocols);
gst_rtsp_media_set_retransmission_time (media, rtx_time);
+ gst_rtsp_media_set_record (media, record);
if ((pool = gst_rtsp_media_factory_get_address_pool (factory))) {
gst_rtsp_media_set_address_pool (media, pool);
@@ -1199,3 +1221,51 @@ gst_rtsp_media_factory_create_element (GstRTSPMediaFactory * factory,
return result;
}
+
+/**
+ * gst_rtsp_media_factory_set_record:
+ * @factory: a #GstRTSPMediaFactory
+ * @record: the new value
+ *
+ * Configure if this factory creates media for PLAY or RECORD methods.
+ */
+void
+gst_rtsp_media_factory_set_record (GstRTSPMediaFactory * factory,
+ gboolean record)
+{
+ GstRTSPMediaFactoryPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory));
+
+ priv = factory->priv;
+
+ GST_RTSP_MEDIA_FACTORY_LOCK (factory);
+ priv->record = record;
+ GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
+}
+
+/**
+ * gst_rtsp_media_factory_is_record:
+ * @factory: a #GstRTSPMediaFactory
+ *
+ * Get if media created from this factory can be used for PLAY or RECORD
+ * methods.
+ *
+ * Returns: %TRUE if the media will be record between clients.
+ */
+gboolean
+gst_rtsp_media_factory_is_record (GstRTSPMediaFactory * factory)
+{
+ GstRTSPMediaFactoryPrivate *priv;
+ gboolean result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY (factory), FALSE);
+
+ priv = factory->priv;
+
+ GST_RTSP_MEDIA_FACTORY_LOCK (factory);
+ result = priv->record;
+ GST_RTSP_MEDIA_FACTORY_UNLOCK (factory);
+
+ return result;
+}
diff --git a/gst/rtsp-server/rtsp-media-factory.h b/gst/rtsp-server/rtsp-media-factory.h
index ce069d7f79..288d111567 100644
--- a/gst/rtsp-server/rtsp-media-factory.h
+++ b/gst/rtsp-server/rtsp-media-factory.h
@@ -145,6 +145,10 @@ void gst_rtsp_media_factory_set_retransmission_time (GstRTSPMed
GstClockTime time);
GstClockTime gst_rtsp_media_factory_get_retransmission_time (GstRTSPMediaFactory * factory);
+void gst_rtsp_media_factory_set_record (GstRTSPMediaFactory *factory,
+ gboolean record);
+gboolean gst_rtsp_media_factory_is_record (GstRTSPMediaFactory *factory);
+
/* creating the media from the factory and a url */
GstRTSPMedia * gst_rtsp_media_factory_construct (GstRTSPMediaFactory *factory,
const GstRTSPUrl *url);
diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c
index eddbf41a0c..9b64569df7 100644
--- a/gst/rtsp-server/rtsp-media.c
+++ b/gst/rtsp-server/rtsp-media.c
@@ -1,5 +1,7 @@
/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -61,12 +63,22 @@
* Last reviewed on 2013-07-11 (1.0.0)
*/
+#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>
+#include <gst/sdp/gstmikey.h>
+#include <gst/rtp/gstrtppayloads.h>
+
+#define AES_128_KEY_LEN 16
+#define AES_256_KEY_LEN 32
+
+#define HMAC_32_KEY_LEN 4
+#define HMAC_80_KEY_LEN 10
+
#include "rtsp-media.h"
#define GST_RTSP_MEDIA_GET_PRIVATE(obj) \
@@ -89,6 +101,7 @@ struct _GstRTSPMediaPrivate
guint buffer_size;
GstRTSPAddressPool *pool;
gboolean blocked;
+ gboolean record;
GstElement *element;
GRecMutex state_lock; /* locking order: state lock, lock */
@@ -135,6 +148,7 @@ struct _GstRTSPMediaPrivate
#define DEFAULT_EOS_SHUTDOWN FALSE
#define DEFAULT_BUFFER_SIZE 0x80000
#define DEFAULT_TIME_PROVIDER FALSE
+#define DEFAULT_RECORD FALSE
/* define to dump received RTCP packets */
#undef DUMP_STATS
@@ -151,6 +165,7 @@ enum
PROP_BUFFER_SIZE,
PROP_ELEMENT,
PROP_TIME_PROVIDER,
+ PROP_RECORD,
PROP_LAST
};
@@ -189,6 +204,7 @@ static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop);
static GstElement *default_create_rtpbin (GstRTSPMedia * media);
static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
GstSDPInfo * info);
+static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
static gboolean wait_preroll (GstRTSPMedia * media);
@@ -277,6 +293,11 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
"Use a NetTimeProvider for clients",
DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_RECORD,
+ g_param_spec_boolean ("record", "Record",
+ "If this media pipeline can be used for PLAY or RECORD",
+ DEFAULT_RECORD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL,
@@ -320,6 +341,7 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
klass->query_stop = default_query_stop;
klass->create_rtpbin = default_create_rtpbin;
klass->setup_sdp = default_setup_sdp;
+ klass->handle_sdp = default_handle_sdp;
}
static void
@@ -342,6 +364,7 @@ gst_rtsp_media_init (GstRTSPMedia * media)
priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
priv->buffer_size = DEFAULT_BUFFER_SIZE;
priv->time_provider = DEFAULT_TIME_PROVIDER;
+ priv->record = DEFAULT_RECORD;
}
static void
@@ -412,6 +435,9 @@ gst_rtsp_media_get_property (GObject * object, guint propid,
case PROP_TIME_PROVIDER:
g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media));
break;
+ case PROP_RECORD:
+ g_value_set_boolean (value, gst_rtsp_media_is_record (media));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@@ -452,6 +478,9 @@ gst_rtsp_media_set_property (GObject * object, guint propid,
case PROP_TIME_PROVIDER:
gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value));
break;
+ case PROP_RECORD:
+ gst_rtsp_media_set_record (media, g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@@ -1300,6 +1329,9 @@ _next_available_pt (GList * payloads)
*
* Collect all dynamic elements, named dynpay\%d, and add them to
* the list of dynamic elements.
+ *
+ * Find all depayloader elements, they should be named depay\%d in the
+ * element of @media, and create #GstRTSPStreams for them.
*/
void
gst_rtsp_media_collect_streams (GstRTSPMedia * media)
@@ -1348,6 +1380,21 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
have_elem = TRUE;
}
g_free (name);
+
+ name = g_strdup_printf ("depay%d", i);
+ if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
+ GST_INFO ("found stream %d with depayloader %p", i, elem);
+
+ /* take the pad of the payloader */
+ pad = gst_element_get_static_pad (elem, "sink");
+ /* create the stream */
+ gst_rtsp_media_create_stream (media, elem, pad);
+ gst_object_unref (pad);
+ gst_object_unref (elem);
+
+ have_elem = TRUE;
+ }
+ g_free (name);
}
}
@@ -1355,10 +1402,10 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
* gst_rtsp_media_create_stream:
* @media: a #GstRTSPMedia
* @payloader: a #GstElement
- * @srcpad: a source #GstPad
+ * @pad: a #GstPad
*
- * Create a new stream in @media that provides RTP data on @srcpad.
- * @srcpad should be a pad of an element inside @media->element.
+ * Create a new stream in @media that provides RTP data on @pad.
+ * @pad should be a pad of an element inside @media->element.
*
* Returns: (transfer none): a new #GstRTSPStream that remains valid for as long
* as @media exists.
@@ -1369,15 +1416,13 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
{
GstRTSPMediaPrivate *priv;
GstRTSPStream *stream;
- GstPad *srcpad;
+ GstPad *ghostpad;
gchar *name;
gint idx;
- gint i, n;
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
g_return_val_if_fail (GST_IS_PAD (pad), NULL);
- g_return_val_if_fail (GST_PAD_IS_SRC (pad), NULL);
priv = media->priv;
@@ -1386,13 +1431,17 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
GST_DEBUG ("media %p: creating stream with index %d", media, idx);
- name = g_strdup_printf ("src_%u", idx);
- srcpad = gst_ghost_pad_new (name, pad);
- gst_pad_set_active (srcpad, TRUE);
- gst_element_add_pad (priv->element, srcpad);
+ if (GST_PAD_IS_SRC (pad))
+ name = g_strdup_printf ("src_%u", idx);
+ else
+ name = g_strdup_printf ("sink_%u", idx);
+
+ ghostpad = gst_ghost_pad_new (name, pad);
+ gst_pad_set_active (ghostpad, TRUE);
+ gst_element_add_pad (priv->element, ghostpad);
g_free (name);
- stream = gst_rtsp_stream_new (idx, payloader, srcpad);
+ stream = gst_rtsp_stream_new (idx, payloader, ghostpad);
if (priv->pool)
gst_rtsp_stream_set_address_pool (stream, priv->pool);
gst_rtsp_stream_set_profiles (stream, priv->profiles);
@@ -1401,23 +1450,28 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
g_ptr_array_add (priv->streams, stream);
- if (priv->payloads)
- g_list_free (priv->payloads);
- priv->payloads = _find_payload_types (media);
+ if (GST_PAD_IS_SRC (pad)) {
+ gint i, n;
- n = priv->streams->len;
- for (i = 0; i < n; i++) {
- GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
- guint rtx_pt = _next_available_pt (priv->payloads);
+ if (priv->payloads)
+ g_list_free (priv->payloads);
+ priv->payloads = _find_payload_types (media);
- if (rtx_pt == 0) {
- GST_WARNING ("Ran out of space of dynamic payload types");
- break;
- }
+ n = priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ guint rtx_pt = _next_available_pt (priv->payloads);
- gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);
+ if (rtx_pt == 0) {
+ GST_WARNING ("Ran out of space of dynamic payload types");
+ break;
+ }
- priv->payloads = g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt));
+ gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);
+
+ priv->payloads =
+ g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt));
+ }
}
g_mutex_unlock (&priv->lock);
@@ -1716,16 +1770,21 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
goto not_prepared;
/* Update the seekable state of the pipeline in case it changed */
- query = gst_query_new_seeking (GST_FORMAT_TIME);
- if (gst_element_query (priv->pipeline, query)) {
- GstFormat format;
- gboolean seekable;
- gint64 start, end;
-
- gst_query_parse_seeking (query, &format, &seekable, &start, &end);
- priv->seekable = seekable;
+ if (gst_rtsp_media_is_record (media)) {
+ /* TODO: Seeking for RECORD? */
+ priv->seekable = FALSE;
+ } else {
+ query = gst_query_new_seeking (GST_FORMAT_TIME);
+ if (gst_element_query (priv->pipeline, query)) {
+ GstFormat format;
+ gboolean seekable;
+ gint64 start, end;
+
+ gst_query_parse_seeking (query, &format, &seekable, &start, &end);
+ priv->seekable = seekable;
+ }
+ gst_query_unref (query);
}
- gst_query_unref (query);
if (!priv->seekable)
goto not_seekable;
@@ -1898,7 +1957,28 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message)
switch (type) {
case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old, new, pending;
+
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->pipeline))
+ break;
+
+ gst_message_parse_state_changed (message, &old, &new, &pending);
+
+ GST_DEBUG ("%p: went from %s to %s (pending %s)", media,
+ gst_element_state_get_name (old), gst_element_state_get_name (new),
+ gst_element_state_get_name (pending));
+ if (gst_rtsp_media_is_record (media)
+ && old == GST_STATE_READY && new == GST_STATE_PAUSED) {
+ GST_INFO ("%p: went to PAUSED, prepared now", media);
+ collect_media_stats (media);
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ }
+
break;
+ }
case GST_MESSAGE_BUFFERING:
{
gint percent;
@@ -2203,8 +2283,10 @@ start_preroll (GstRTSPMedia * media)
* seeking query in preroll instead */
priv->seekable = FALSE;
priv->is_live = TRUE;
- /* start blocked to make sure nothing goes to the sink */
- media_streams_set_blocked (media, TRUE);
+ if (!gst_rtsp_media_is_record (media)) {
+ /* start blocked to make sure nothing goes to the sink */
+ media_streams_set_blocked (media, TRUE);
+ }
ret = set_state (media, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE)
goto state_failed;
@@ -2830,6 +2912,583 @@ no_setup_sdp:
}
}
+static const gchar *
+rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name,
+ gint pt)
+{
+ guint i;
+
+ for (i = 0;; i++) {
+ const gchar *attr;
+ gint val;
+
+ if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL)
+ break;
+
+ if (sscanf (attr, "%d ", &val) != 1)
+ continue;
+
+ if (val == pt)
+ return attr;
+ }
+ return NULL;
+}
+
+#define PARSE_INT(p, del, res) \
+G_STMT_START { \
+ gchar *t = p; \
+ p = strstr (p, del); \
+ if (p == NULL) \
+ res = -1; \
+ else { \
+ *p = '\0'; \
+ p++; \
+ res = atoi (t); \
+ } \
+} G_STMT_END
+
+#define PARSE_STRING(p, del, res) \
+G_STMT_START { \
+ gchar *t = p; \
+ p = strstr (p, del); \
+ if (p == NULL) { \
+ res = NULL; \
+ p = t; \
+ } \
+ else { \
+ *p = '\0'; \
+ p++; \
+ res = t; \
+ } \
+} G_STMT_END
+
+#define SKIP_SPACES(p) \
+ while (*p && g_ascii_isspace (*p)) \
+ p++;
+
+/* rtpmap contains:
+ *
+ * <payload> <encoding_name>/<clock_rate>[/<encoding_params>]
+ */
+static gboolean
+parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name,
+ gint * rate, gchar ** params)
+{
+ gchar *p, *t;
+
+ p = (gchar *) rtpmap;
+
+ PARSE_INT (p, " ", *payload);
+ if (*payload == -1)
+ return FALSE;
+
+ SKIP_SPACES (p);
+ if (*p == '\0')
+ return FALSE;
+
+ PARSE_STRING (p, "/", *name);
+ if (*name == NULL) {
+ GST_DEBUG ("no rate, name %s", p);
+ /* no rate, assume -1 then, this is not supposed to happen but RealMedia
+ * streams seem to omit the rate. */
+ *name = p;
+ *rate = -1;
+ return TRUE;
+ }
+
+ t = p;
+ p = strstr (p, "/");
+ if (p == NULL) {
+ *rate = atoi (t);
+ return TRUE;
+ }
+ *p = '\0';
+ p++;
+ *rate = atoi (t);
+
+ t = p;
+ if (*p == '\0')
+ return TRUE;
+ *params = t;
+
+ return TRUE;
+}
+
+/*
+ * Mapping of caps to and from SDP fields:
+ *
+ * a=rtpmap:<payload> <encoding_name>/<clock_rate>[/<encoding_params>]
+ * a=fmtp:<payload> <param>[=<value>];...
+ */
+static GstCaps *
+media_to_caps (gint pt, const GstSDPMedia * media)
+{
+ GstCaps *caps;
+ const gchar *rtpmap;
+ const gchar *fmtp;
+ gchar *name = NULL;
+ gint rate = -1;
+ gchar *params = NULL;
+ gchar *tmp;
+ GstStructure *s;
+ gint payload = 0;
+ gboolean ret;
+
+ /* get and parse rtpmap */
+ rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt);
+
+ if (rtpmap) {
+ ret = parse_rtpmap (rtpmap, &payload, &name, &rate, &params);
+ if (!ret) {
+ g_warning ("error parsing rtpmap, ignoring");
+ rtpmap = NULL;
+ }
+ }
+ /* dynamic payloads need rtpmap or we fail */
+ if (rtpmap == NULL && pt >= 96)
+ goto no_rtpmap;
+
+ /* check if we have a rate, if not, we need to look up the rate from the
+ * default rates based on the payload types. */
+ if (rate == -1) {
+ const GstRTPPayloadInfo *info;
+
+ if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) {
+ /* dynamic types, use media and encoding_name */
+ tmp = g_ascii_strdown (media->media, -1);
+ info = gst_rtp_payload_info_for_name (tmp, name);
+ g_free (tmp);
+ } else {
+ /* static types, use payload type */
+ info = gst_rtp_payload_info_for_pt (pt);
+ }
+
+ if (info) {
+ if ((rate = info->clock_rate) == 0)
+ rate = -1;
+ }
+ /* we fail if we cannot find one */
+ if (rate == -1)
+ goto no_rate;
+ }
+
+ tmp = g_ascii_strdown (media->media, -1);
+ caps = gst_caps_new_simple ("application/x-unknown",
+ "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL);
+ g_free (tmp);
+ s = gst_caps_get_structure (caps, 0);
+
+ gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL);
+
+ /* encoding name must be upper case */
+ if (name != NULL) {
+ tmp = g_ascii_strup (name, -1);
+ gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL);
+ g_free (tmp);
+ }
+
+ /* params must be lower case */
+ if (params != NULL) {
+ tmp = g_ascii_strdown (params, -1);
+ gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL);
+ g_free (tmp);
+ }
+
+ /* parse optional fmtp: field */
+ if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) {
+ gchar *p;
+ gint payload = 0;
+
+ p = (gchar *) fmtp;
+
+ /* p is now of the format <payload> <param>[=<value>];... */
+ PARSE_INT (p, " ", payload);
+ if (payload != -1 && payload == pt) {
+ gchar **pairs;
+ gint i;
+
+ /* <param>[=<value>] are separated with ';' */
+ pairs = g_strsplit (p, ";", 0);
+ for (i = 0; pairs[i]; i++) {
+ gchar *valpos;
+ const gchar *val, *key;
+
+ /* the key may not have a '=', the value can have other '='s */
+ valpos = strstr (pairs[i], "=");
+ if (valpos) {
+ /* we have a '=' and thus a value, remove the '=' with \0 */
+ *valpos = '\0';
+ /* value is everything between '=' and ';'. We split the pairs at ;
+ * boundaries so we can take the remainder of the value. Some servers
+ * put spaces around the value which we strip off here. Alternatively
+ * we could strip those spaces in the depayloaders should these spaces
+ * actually carry any meaning in the future. */
+ val = g_strstrip (valpos + 1);
+ } else {
+ /* simple <param>;.. is translated into <param>=1;... */
+ val = "1";
+ }
+ /* strip the key of spaces, convert key to lowercase but not the value. */
+ key = g_strstrip (pairs[i]);
+ if (strlen (key) > 1) {
+ tmp = g_ascii_strdown (key, -1);
+ gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL);
+ g_free (tmp);
+ }
+ }
+ g_strfreev (pairs);
+ }
+ }
+ return caps;
+
+ /* ERRORS */
+no_rtpmap:
+ {
+ g_warning ("rtpmap type not given for dynamic payload %d", pt);
+ return NULL;
+ }
+no_rate:
+ {
+ g_warning ("rate unknown for payload type %d", pt);
+ return NULL;
+ }
+}
+
+static gboolean
+parse_keymgmt (const gchar * keymgmt, GstCaps * caps)
+{
+ gboolean res = FALSE;
+ gchar *p, *kmpid;
+ gsize size;
+ guchar *data;
+ GstMIKEYMessage *msg;
+ const GstMIKEYPayload *payload;
+ const gchar *srtp_cipher;
+ const gchar *srtp_auth;
+
+ p = (gchar *) keymgmt;
+
+ SKIP_SPACES (p);
+ if (*p == '\0')
+ return FALSE;
+
+ PARSE_STRING (p, " ", kmpid);
+ if (!g_str_equal (kmpid, "mikey"))
+ return FALSE;
+
+ data = g_base64_decode (p, &size);
+ if (data == NULL)
+ return FALSE;
+
+ msg = gst_mikey_message_new_from_data (data, size, NULL, NULL);
+ g_free (data);
+ if (msg == NULL)
+ return FALSE;
+
+ srtp_cipher = "aes-128-icm";
+ srtp_auth = "hmac-sha1-80";
+
+ /* check the Security policy if any */
+ if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) {
+ GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload;
+ guint len, i;
+
+ if (p->proto != GST_MIKEY_SEC_PROTO_SRTP)
+ goto done;
+
+ len = gst_mikey_payload_sp_get_n_params (payload);
+ for (i = 0; i < len; i++) {
+ const GstMIKEYPayloadSPParam *param =
+ gst_mikey_payload_sp_get_param (payload, i);
+
+ switch (param->type) {
+ case GST_MIKEY_SP_SRTP_ENC_ALG:
+ switch (param->val[0]) {
+ case 0:
+ srtp_cipher = "null";
+ break;
+ case 2:
+ case 1:
+ srtp_cipher = "aes-128-icm";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_ENC_KEY_LEN:
+ switch (param->val[0]) {
+ case AES_128_KEY_LEN:
+ srtp_cipher = "aes-128-icm";
+ break;
+ case AES_256_KEY_LEN:
+ srtp_cipher = "aes-256-icm";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_AUTH_ALG:
+ switch (param->val[0]) {
+ case 0:
+ srtp_auth = "null";
+ break;
+ case 2:
+ case 1:
+ srtp_auth = "hmac-sha1-80";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN:
+ switch (param->val[0]) {
+ case HMAC_32_KEY_LEN:
+ srtp_auth = "hmac-sha1-32";
+ break;
+ case HMAC_80_KEY_LEN:
+ srtp_auth = "hmac-sha1-80";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_SRTP_ENC:
+ break;
+ case GST_MIKEY_SP_SRTP_SRTCP_ENC:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0)))
+ goto done;
+ else {
+ GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload;
+ const GstMIKEYPayload *sub;
+ GstMIKEYPayloadKeyData *pkd;
+ GstBuffer *buf;
+
+ if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL)
+ goto done;
+
+ if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0)))
+ goto done;
+
+ if (sub->type != GST_MIKEY_PT_KEY_DATA)
+ goto done;
+
+ pkd = (GstMIKEYPayloadKeyData *) sub;
+ buf =
+ gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len),
+ pkd->key_len);
+ gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL);
+ }
+
+ gst_caps_set_simple (caps,
+ "srtp-cipher", G_TYPE_STRING, srtp_cipher,
+ "srtp-auth", G_TYPE_STRING, srtp_auth,
+ "srtcp-cipher", G_TYPE_STRING, srtp_cipher,
+ "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL);
+
+ res = TRUE;
+done:
+ gst_mikey_message_unref (msg);
+
+ return res;
+}
+
+/*
+ * Mapping SDP attributes to caps
+ *
+ * prepend 'a-' to IANA registered sdp attributes names
+ * (ie: not prefixed with 'x-') in order to avoid
+ * collision with gstreamer standard caps properties names
+ */
+static void
+sdp_attributes_to_caps (GArray * attributes, GstCaps * caps)
+{
+ if (attributes->len > 0) {
+ GstStructure *s;
+ guint i;
+
+ s = gst_caps_get_structure (caps, 0);
+
+ for (i = 0; i < attributes->len; i++) {
+ GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i);
+ gchar *tofree, *key;
+
+ key = attr->key;
+
+ /* skip some of the attribute we already handle */
+ if (!strcmp (key, "fmtp"))
+ continue;
+ if (!strcmp (key, "rtpmap"))
+ continue;
+ if (!strcmp (key, "control"))
+ continue;
+ if (!strcmp (key, "range"))
+ continue;
+ if (g_str_equal (key, "key-mgmt")) {
+ parse_keymgmt (attr->value, caps);
+ continue;
+ }
+
+ /* string must be valid UTF8 */
+ if (!g_utf8_validate (attr->value, -1, NULL))
+ continue;
+
+ if (!g_str_has_prefix (key, "x-"))
+ tofree = key = g_strdup_printf ("a-%s", key);
+ else
+ tofree = NULL;
+
+ GST_DEBUG ("adding caps: %s=%s", key, attr->value);
+ gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL);
+ g_free (tofree);
+ }
+ }
+}
+
+static gboolean
+default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
+{
+ GstRTSPMediaPrivate *priv = media->priv;
+ gint i, medias_len;
+
+ medias_len = gst_sdp_message_medias_len (sdp);
+ if (medias_len != priv->streams->len) {
+ GST_ERROR ("%p: Media has more or less streams than SDP (%d /= %d)", media,
+ priv->streams->len, medias_len);
+ return FALSE;
+ }
+
+ for (i = 0; i < medias_len; i++) {
+ const gchar *proto, *media_type;
+ const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
+ GstRTSPStream *stream;
+ gint j, formats_len;
+ const gchar *control;
+ GstRTSPProfile profile, profiles;
+
+ stream = g_ptr_array_index (priv->streams, i);
+
+ /* TODO: Should we do something with the other SDP information? */
+
+ /* get proto */
+ proto = gst_sdp_media_get_proto (sdp_media);
+ if (proto == NULL) {
+ GST_ERROR ("%p: SDP media %d has no proto", media, i);
+ return FALSE;
+ }
+
+ if (g_str_equal (proto, "RTP/AVP")) {
+ media_type = "application/x-rtp";
+ profile = GST_RTSP_PROFILE_AVP;
+ } else if (g_str_equal (proto, "RTP/SAVP")) {
+ media_type = "application/x-srtp";
+ profile = GST_RTSP_PROFILE_SAVP;
+ } else if (g_str_equal (proto, "RTP/AVPF")) {
+ media_type = "application/x-rtp";
+ profile = GST_RTSP_PROFILE_AVPF;
+ } else if (g_str_equal (proto, "RTP/SAVPF")) {
+ media_type = "application/x-srtp";
+ profile = GST_RTSP_PROFILE_SAVPF;
+ } else {
+ GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
+ return FALSE;
+ }
+
+ profiles = gst_rtsp_stream_get_profiles (stream);
+ if ((profiles & profile) == 0) {
+ GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
+ return FALSE;
+ }
+
+ formats_len = gst_sdp_media_formats_len (sdp_media);
+ for (j = 0; j < formats_len; j++) {
+ gint pt;
+ GstCaps *caps;
+ GstStructure *s;
+
+ pt = atoi (gst_sdp_media_get_format (sdp_media, j));
+
+ GST_DEBUG (" looking at %d pt: %d", j, pt);
+
+ /* convert caps */
+ caps = media_to_caps (pt, sdp_media);
+ if (caps == NULL) {
+ GST_WARNING (" skipping pt %d without caps", pt);
+ continue;
+ }
+
+ /* do some tweaks */
+ GST_DEBUG ("mapping sdp session level attributes to caps");
+ sdp_attributes_to_caps (sdp->attributes, caps);
+ GST_DEBUG ("mapping sdp media level attributes to caps");
+ sdp_attributes_to_caps (sdp_media->attributes, caps);
+
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_set_name (s, media_type);
+
+ gst_rtsp_stream_set_pt_map (stream, pt, caps);
+ gst_caps_unref (caps);
+ }
+
+ control = gst_sdp_media_get_attribute_val (sdp_media, "control");
+ if (control)
+ gst_rtsp_stream_set_control (stream, control);
+
+ }
+
+ return TRUE;
+}
+
+/**
+ * gst_rtsp_media_handle_sdp:
+ * @media: a #GstRTSPMedia
+ * @sdp: (transfer none): a #GstSDPMessage
+ *
+ * Configure an SDP on @media for receiving streams
+ *
+ * Returns: TRUE on success.
+ */
+gboolean
+gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
+{
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMediaClass *klass;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (sdp != NULL, FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ if (!klass->handle_sdp)
+ goto no_handle_sdp;
+
+ res = klass->handle_sdp (media, sdp);
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return res;
+
+ /* ERRORS */
+no_handle_sdp:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_ERROR ("no handle_sdp function");
+ g_critical ("no handle_sdp vmethod function set");
+ return FALSE;
+ }
+}
+
static void
do_set_seqnum (GstRTSPStream * stream)
{
@@ -3214,3 +3873,50 @@ error_status:
return FALSE;
}
}
+
+/**
+ * gst_rtsp_media_set_record:
+ * @media: a #GstRTSPMedia
+ * @record: the new value
+ *
+ * Set or unset if the pipeline for @media can be used for PLAY or RECORD
+ * methods.
+ */
+void
+gst_rtsp_media_set_record (GstRTSPMedia * media, gboolean record)
+{
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->record = record;
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * gst_rtsp_media_is_record:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media can be used for PLAY or RECORD methods.
+ *
+ * Returns: %TRUE if the media can be record between clients.
+ */
+gboolean
+gst_rtsp_media_is_record (GstRTSPMedia * media)
+{
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->record;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+}
diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h
index 7c7515b62c..6b4922b9d0 100644
--- a/gst/rtsp-server/rtsp-media.h
+++ b/gst/rtsp-server/rtsp-media.h
@@ -149,8 +149,10 @@ struct _GstRTSPMediaClass {
void (*target_state) (GstRTSPMedia *media, GstState state);
void (*new_state) (GstRTSPMedia *media, GstState state);
+ gboolean (*handle_sdp) (GstRTSPMedia *media, GstSDPMessage *sdp);
+
/*< private >*/
- gpointer _gst_reserved[GST_PADDING_LARGE];
+ gpointer _gst_reserved[GST_PADDING_LARGE-1];
};
GType gst_rtsp_media_get_type (void);
@@ -170,6 +172,9 @@ GstRTSPPermissions * gst_rtsp_media_get_permissions (GstRTSPMedia *media);
void gst_rtsp_media_set_shared (GstRTSPMedia *media, gboolean shared);
gboolean gst_rtsp_media_is_shared (GstRTSPMedia *media);
+void gst_rtsp_media_set_record (GstRTSPMedia *media, gboolean record);
+gboolean gst_rtsp_media_is_record (GstRTSPMedia *media);
+
void gst_rtsp_media_set_reusable (GstRTSPMedia *media, gboolean reusable);
gboolean gst_rtsp_media_is_reusable (GstRTSPMedia *media);
@@ -209,6 +214,9 @@ gboolean gst_rtsp_media_unsuspend (GstRTSPMedia *media);
gboolean gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
GstSDPInfo * info);
+gboolean gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
+
+
/* creating streams */
void gst_rtsp_media_collect_streams (GstRTSPMedia *media);
GstRTSPStream * gst_rtsp_media_create_stream (GstRTSPMedia *media,
diff --git a/gst/rtsp-server/rtsp-session-media.c b/gst/rtsp-server/rtsp-session-media.c
index e319241aa9..0fffbcab2e 100644
--- a/gst/rtsp-server/rtsp-session-media.c
+++ b/gst/rtsp-server/rtsp-session-media.c
@@ -1,5 +1,7 @@
/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -144,6 +146,7 @@ gst_rtsp_session_media_new (const gchar * path, GstRTSPMedia * media)
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
status = gst_rtsp_media_get_status (media);
g_return_val_if_fail (status == GST_RTSP_MEDIA_STATUS_PREPARED || status ==
GST_RTSP_MEDIA_STATUS_SUSPENDED, NULL);
diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c
index d2e043290b..6c7cacf8b0 100644
--- a/gst/rtsp-server/rtsp-stream.c
+++ b/gst/rtsp-server/rtsp-stream.c
@@ -1,5 +1,7 @@
/* GStreamer
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -71,7 +73,8 @@ struct _GstRTSPStreamPrivate
{
GMutex lock;
guint idx;
- GstPad *srcpad;
+ /* Only one pad is ever set */
+ GstPad *srcpad, *sinkpad;
GstElement *payloader;
guint buffer_size;
gboolean is_joined;
@@ -82,6 +85,7 @@ struct _GstRTSPStreamPrivate
/* pads on the rtpbin */
GstPad *send_rtp_sink;
+ GstPad *recv_rtp_src;
GstPad *recv_sink[2];
GstPad *send_src[2];
@@ -153,6 +157,9 @@ struct _GstRTSPStreamPrivate
/* stream blocking */
gulong blocked_id;
gboolean blocking;
+
+ /* pt->caps map for RECORD streams */
+ GHashTable *ptmap;
};
#define DEFAULT_CONTROL NULL
@@ -253,6 +260,8 @@ gst_rtsp_stream_init (GstRTSPStream * stream)
priv->keys = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) gst_caps_unref);
+ priv->ptmap = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) gst_caps_unref);
}
static void
@@ -283,11 +292,15 @@ gst_rtsp_stream_finalize (GObject * obj)
g_object_unref (priv->rtxsend);
gst_object_unref (priv->payloader);
- gst_object_unref (priv->srcpad);
+ if (priv->srcpad)
+ gst_object_unref (priv->srcpad);
+ if (priv->sinkpad)
+ gst_object_unref (priv->sinkpad);
g_free (priv->control);
g_mutex_clear (&priv->lock);
g_hash_table_unref (priv->keys);
+ g_hash_table_destroy (priv->ptmap);
G_OBJECT_CLASS (gst_rtsp_stream_parent_class)->finalize (obj);
}
@@ -337,29 +350,32 @@ gst_rtsp_stream_set_property (GObject * object, guint propid,
/**
* gst_rtsp_stream_new:
* @idx: an index
- * @srcpad: a #GstPad
+ * @pad: a #GstPad
* @payloader: a #GstElement
*
* Create a new media stream with index @idx that handles RTP data on
- * @srcpad and has a payloader element @payloader.
+ * @pad and has a payloader element @payloader if @pad is a source pad
+ * or a depayloader element @payloader if @pad is a sink pad.
*
* Returns: (transfer full): a new #GstRTSPStream
*/
GstRTSPStream *
-gst_rtsp_stream_new (guint idx, GstElement * payloader, GstPad * srcpad)
+gst_rtsp_stream_new (guint idx, GstElement * payloader, GstPad * pad)
{
GstRTSPStreamPrivate *priv;
GstRTSPStream *stream;
g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
- g_return_val_if_fail (GST_IS_PAD (srcpad), NULL);
- g_return_val_if_fail (GST_PAD_IS_SRC (srcpad), NULL);
+ g_return_val_if_fail (GST_IS_PAD (pad), NULL);
stream = g_object_new (GST_TYPE_RTSP_STREAM, NULL);
priv = stream->priv;
priv->idx = idx;
priv->payloader = gst_object_ref (payloader);
- priv->srcpad = gst_object_ref (srcpad);
+ if (GST_PAD_IS_SRC (pad))
+ priv->srcpad = gst_object_ref (pad);
+ else
+ priv->sinkpad = gst_object_ref (pad);
return stream;
}
@@ -416,10 +432,32 @@ gst_rtsp_stream_get_srcpad (GstRTSPStream * stream)
{
g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ if (!stream->priv->srcpad)
+ return NULL;
+
return gst_object_ref (stream->priv->srcpad);
}
/**
+ * gst_rtsp_stream_get_sinkpad:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the sinkpad associated with @stream.
+ *
+ * Returns: (transfer full): the sinkpad. Unref after usage.
+ */
+GstPad *
+gst_rtsp_stream_get_sinkpad (GstRTSPStream * stream)
+{
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ if (!stream->priv->sinkpad)
+ return NULL;
+
+ return gst_object_ref (stream->priv->sinkpad);
+}
+
+/**
* gst_rtsp_stream_get_control:
* @stream: a #GstRTSPStream
*
@@ -975,11 +1013,12 @@ different_address:
}
static gboolean
-alloc_ports_one_family (GstRTSPAddressPool * pool, gint buffer_size,
- GSocketFamily family, GstElement * udpsrc_out[2],
+alloc_ports_one_family (GstRTSPStream * stream, GstRTSPAddressPool * pool,
+ gint buffer_size, GSocketFamily family, GstElement * udpsrc_out[2],
GstElement * udpsink_out[2], GstRTSPRange * server_port_out,
GstRTSPAddress ** server_addr_out)
{
+ GstRTSPStreamPrivate *priv = stream->priv;
GstStateChangeReturn ret;
GstElement *udpsrc0, *udpsrc1;
GstElement *udpsink0, *udpsink1;
@@ -1148,6 +1187,11 @@ again:
g_object_set (G_OBJECT (udpsink1), "close-socket", FALSE, NULL);
g_object_set (G_OBJECT (udpsink1), multisink_socket, rtcp_socket, NULL);
g_object_set (G_OBJECT (udpsink1), "sync", FALSE, NULL);
+ /* Needs to be async for RECORD streams, otherwise we will never go to
+ * PLAYING because the sinks will wait for data while the udpsrc can't
+ * provide data with timestamps in PAUSED. */
+ if (priv->sinkpad)
+ g_object_set (G_OBJECT (udpsink0), "async", FALSE, NULL);
g_object_set (G_OBJECT (udpsink1), "async", FALSE, NULL);
g_object_set (G_OBJECT (udpsink0), "auto-multicast", FALSE, NULL);
g_object_set (G_OBJECT (udpsink0), "loop", FALSE, NULL);
@@ -1227,11 +1271,13 @@ alloc_ports (GstRTSPStream * stream)
{
GstRTSPStreamPrivate *priv = stream->priv;
- priv->have_ipv4 = alloc_ports_one_family (priv->pool, priv->buffer_size,
+ priv->have_ipv4 =
+ alloc_ports_one_family (stream, priv->pool, priv->buffer_size,
G_SOCKET_FAMILY_IPV4, priv->udpsrc_v4, priv->udpsink,
&priv->server_port_v4, &priv->server_addr_v4);
- priv->have_ipv6 = alloc_ports_one_family (priv->pool, priv->buffer_size,
+ priv->have_ipv6 =
+ alloc_ports_one_family (stream, priv->pool, priv->buffer_size,
G_SOCKET_FAMILY_IPV6, priv->udpsrc_v6, priv->udpsink,
&priv->server_port_v6, &priv->server_addr_v6);
@@ -1746,7 +1792,7 @@ request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
}
static GstElement *
-request_rtcp_decoder (GstElement * rtpbin, guint session,
+request_rtp_rtcp_decoder (GstElement * rtpbin, guint session,
GstRTSPStream * stream)
{
GstRTSPStreamPrivate *priv = stream->priv;
@@ -1809,6 +1855,101 @@ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPStream * stream)
}
/**
+ * gst_rtsp_stream_set_pt_map:
+ * @stream: a #GstRTSPStream
+ * @pt: the pt
+ * @caps: a #GstCaps
+ *
+ * Configure a pt map between @pt and @caps.
+ */
+void
+gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps)
+{
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ g_hash_table_insert (priv->ptmap, GINT_TO_POINTER (pt), gst_caps_ref (caps));
+ g_mutex_unlock (&priv->lock);
+}
+
+static GstCaps *
+request_pt_map (GstElement * rtpbin, guint session, guint pt,
+ GstRTSPStream * stream)
+{
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstCaps *caps = NULL;
+
+ g_mutex_lock (&priv->lock);
+
+ if (priv->idx == session) {
+ caps = g_hash_table_lookup (priv->ptmap, GINT_TO_POINTER (pt));
+ if (caps) {
+ GST_DEBUG ("Stream %p, pt %u: caps %" GST_PTR_FORMAT, stream, pt, caps);
+ gst_caps_ref (caps);
+ } else {
+ GST_DEBUG ("Stream %p, pt %u: no caps", stream, pt);
+ }
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ return caps;
+}
+
+static void
+pad_added (GstElement * rtpbin, GstPad * pad, GstRTSPStream * stream)
+{
+ GstRTSPStreamPrivate *priv = stream->priv;
+ gchar *name;
+ GstPadLinkReturn ret;
+ guint sessid;
+
+ GST_DEBUG ("Stream %p added pad %s:%s for pad %s:%s", stream,
+ GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));
+
+ name = gst_pad_get_name (pad);
+ if (sscanf (name, "recv_rtp_src_%u", &sessid) != 1) {
+ g_free (name);
+ return;
+ }
+ g_free (name);
+
+ if (priv->idx != sessid)
+ return;
+
+ if (gst_pad_is_linked (priv->sinkpad)) {
+ GST_WARNING ("Stream %p: Pad %s:%s is linked already", stream,
+ GST_DEBUG_PAD_NAME (priv->sinkpad));
+ return;
+ }
+
+ /* link the RTP pad to the session manager, it should not really fail unless
+ * this is not really an RTP pad */
+ ret = gst_pad_link (pad, priv->sinkpad);
+ if (ret != GST_PAD_LINK_OK)
+ goto link_failed;
+ priv->recv_rtp_src = gst_object_ref (pad);
+
+ return;
+
+/* ERRORS */
+link_failed:
+ {
+ GST_ERROR ("Stream %p: Failed to link pads %s:%s and %s:%s", stream,
+ GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));
+ }
+}
+
+static void
+on_npt_stop (GstElement * rtpbin, guint session, guint ssrc,
+ GstRTSPStream * stream)
+{
+ /* TODO: What to do here other than this? */
+ GST_DEBUG ("Stream %p: Got EOS", stream);
+ gst_pad_send_event (stream->priv->sinkpad, gst_event_new_eos ());
+}
+
+/**
* gst_rtsp_stream_join_bin:
* @stream: a #GstRTSPStream
* @bin: (transfer none): a #GstBin to join
@@ -1861,37 +2002,52 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
(GCallback) request_rtp_encoder, stream);
g_signal_connect (rtpbin, "request-rtcp-encoder",
(GCallback) request_rtcp_encoder, stream);
+ g_signal_connect (rtpbin, "request-rtp-decoder",
+ (GCallback) request_rtp_rtcp_decoder, stream);
g_signal_connect (rtpbin, "request-rtcp-decoder",
- (GCallback) request_rtcp_decoder, stream);
+ (GCallback) request_rtp_rtcp_decoder, stream);
}
- if (priv->rtx_time > 0) {
+ if (priv->rtx_time > 0 && priv->srcpad) {
/* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
g_signal_connect (rtpbin, "request-aux-sender",
(GCallback) request_aux_sender, stream);
}
+ if (priv->sinkpad) {
+ g_signal_connect (rtpbin, "request-pt-map",
+ (GCallback) request_pt_map, stream);
+ }
/* get a pad for sending RTP */
name = g_strdup_printf ("send_rtp_sink_%u", idx);
priv->send_rtp_sink = gst_element_get_request_pad (rtpbin, name);
g_free (name);
- /* link the RTP pad to the session manager, it should not really fail unless
- * this is not really an RTP pad */
- ret = gst_pad_link (priv->srcpad, priv->send_rtp_sink);
- if (ret != GST_PAD_LINK_OK)
- goto link_failed;
+
+ if (priv->srcpad) {
+ /* link the RTP pad to the session manager, it should not really fail unless
+ * this is not really an RTP pad */
+ ret = gst_pad_link (priv->srcpad, priv->send_rtp_sink);
+ if (ret != GST_PAD_LINK_OK)
+ goto link_failed;
+ } else {
+ /* Need to connect our sinkpad from here */
+ g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added, stream);
+ /* EOS */
+ g_signal_connect (rtpbin, "on-npt-stop", (GCallback) on_npt_stop, stream);
+ }
/* get pads from the RTP session element for sending and receiving
* RTP/RTCP*/
name = g_strdup_printf ("send_rtp_src_%u", idx);
priv->send_src[0] = gst_element_get_static_pad (rtpbin, name);
g_free (name);
- name = g_strdup_printf ("send_rtcp_src_%u", idx);
- priv->send_src[1] = gst_element_get_request_pad (rtpbin, name);
- g_free (name);
name = g_strdup_printf ("recv_rtp_sink_%u", idx);
priv->recv_sink[0] = gst_element_get_request_pad (rtpbin, name);
g_free (name);
+
+ name = g_strdup_printf ("send_rtcp_src_%u", idx);
+ priv->send_src[1] = gst_element_get_request_pad (rtpbin, name);
+ g_free (name);
name = g_strdup_printf ("recv_rtcp_sink_%u", idx);
priv->recv_sink[1] = gst_element_get_request_pad (rtpbin, name);
g_free (name);
@@ -2002,10 +2158,12 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
gst_object_unref (pad);
if (priv->udpsrc_v4[i]) {
- /* we set and keep these to playing so that they don't cause NO_PREROLL return
- * values */
- gst_element_set_state (priv->udpsrc_v4[i], GST_STATE_PLAYING);
- gst_element_set_locked_state (priv->udpsrc_v4[i], TRUE);
+ if (priv->srcpad) {
+ /* we set and keep these to playing so that they don't cause NO_PREROLL return
+ * values. This is only relevant for PLAY pipelines */
+ gst_element_set_state (priv->udpsrc_v4[i], GST_STATE_PLAYING);
+ gst_element_set_locked_state (priv->udpsrc_v4[i], TRUE);
+ }
/* add udpsrc */
gst_bin_add (bin, priv->udpsrc_v4[i]);
@@ -2018,8 +2176,10 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
}
if (priv->udpsrc_v6[i]) {
- gst_element_set_state (priv->udpsrc_v6[i], GST_STATE_PLAYING);
- gst_element_set_locked_state (priv->udpsrc_v6[i], TRUE);
+ if (priv->srcpad) {
+ gst_element_set_state (priv->udpsrc_v6[i], GST_STATE_PLAYING);
+ gst_element_set_locked_state (priv->udpsrc_v6[i], TRUE);
+ }
gst_bin_add (bin, priv->udpsrc_v6[i]);
/* and link to the funnel v6 */
@@ -2128,7 +2288,13 @@ gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
GST_INFO ("stream %p leaving bin", stream);
- gst_pad_unlink (priv->srcpad, priv->send_rtp_sink);
+ if (priv->srcpad) {
+ gst_pad_unlink (priv->srcpad, priv->send_rtp_sink);
+ } else if (priv->recv_rtp_src) {
+ gst_pad_unlink (priv->recv_rtp_src, priv->sinkpad);
+ gst_object_unref (priv->recv_rtp_src);
+ priv->recv_rtp_src = NULL;
+ }
g_signal_handler_disconnect (priv->send_src[0], priv->caps_sig);
gst_element_release_request_pad (rtpbin, priv->send_rtp_sink);
gst_object_unref (priv->send_rtp_sink);
@@ -2464,10 +2630,12 @@ update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL);
g_free (host);
- /* we set and keep these to playing so that they don't cause NO_PREROLL return
- * values */
- gst_element_set_state (source->udpsrc[i], GST_STATE_PLAYING);
- gst_element_set_locked_state (source->udpsrc[i], TRUE);
+ if (priv->srcpad) {
+ /* we set and keep these to playing so that they don't cause NO_PREROLL return
+ * values. This is only relevant for PLAY pipelines */
+ gst_element_set_state (source->udpsrc[i], GST_STATE_PLAYING);
+ gst_element_set_locked_state (source->udpsrc[i], TRUE);
+ }
/* add udpsrc */
gst_bin_add (bin, source->udpsrc[i]);
diff --git a/gst/rtsp-server/rtsp-stream.h b/gst/rtsp-server/rtsp-stream.h
index 4fb41959d4..b71c776fdf 100644
--- a/gst/rtsp-server/rtsp-stream.h
+++ b/gst/rtsp-server/rtsp-stream.h
@@ -67,10 +67,11 @@ struct _GstRTSPStreamClass {
GType gst_rtsp_stream_get_type (void);
GstRTSPStream * gst_rtsp_stream_new (guint idx, GstElement *payloader,
- GstPad *srcpad);
+ GstPad *pad);
guint gst_rtsp_stream_get_index (GstRTSPStream *stream);
guint gst_rtsp_stream_get_pt (GstRTSPStream *stream);
GstPad * gst_rtsp_stream_get_srcpad (GstRTSPStream *stream);
+GstPad * gst_rtsp_stream_get_sinkpad (GstRTSPStream *stream);
void gst_rtsp_stream_set_control (GstRTSPStream *stream, const gchar *control);
gchar * gst_rtsp_stream_get_control (GstRTSPStream *stream);
@@ -160,6 +161,8 @@ guint gst_rtsp_stream_get_retransmission_pt (GstRTSPStream * s
void gst_rtsp_stream_set_retransmission_pt (GstRTSPStream * stream,
guint rtx_pt);
+void gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps);
+
/**
* GstRTSPStreamTransportFilterFunc:
* @stream: a #GstRTSPStream object