From ccf6c6eb53cc35050a9d9b736a6714268446c550 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 9 Jan 2015 12:40:47 +0100 Subject: [PATCH] Add initial support for RECORD We currently only support media that is RECORD or PLAY only, not both at once. https://bugzilla.gnome.org/show_bug.cgi?id=743175 --- examples/Makefile.am | 2 +- examples/test-record.c | 68 +++ gst/rtsp-server/rtsp-client.c | 372 ++++++++++++++++- gst/rtsp-server/rtsp-client.h | 8 +- gst/rtsp-server/rtsp-media-factory.c | 70 ++++ gst/rtsp-server/rtsp-media-factory.h | 4 + gst/rtsp-server/rtsp-media.c | 776 +++++++++++++++++++++++++++++++++-- gst/rtsp-server/rtsp-media.h | 10 +- gst/rtsp-server/rtsp-session-media.c | 3 + gst/rtsp-server/rtsp-stream.c | 236 +++++++++-- gst/rtsp-server/rtsp-stream.h | 5 +- 11 files changed, 1468 insertions(+), 86 deletions(-) create mode 100644 examples/test-record.c diff --git a/examples/Makefile.am b/examples/Makefile.am index 6d13ed6..ef33adf 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 0000000..676ae98 --- /dev/null +++ b/examples/test-record.c @@ -0,0 +1,68 @@ +/* GStreamer + * Copyright (C) 2008 Wim Taymans + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge + * + * 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 + +#include + +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 c6b582c..d69ef0a 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 + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge * * 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 4e9519a..5137cc6 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 6e0da84..34a0ac8 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 + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge * * 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 ce069d7..288d111 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 eddbf41..9b64569 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 + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge * * 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 #include #include #include #include +#include +#include + +#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: + * + * /[/] + */ +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: /[/] + * a=fmtp: [=];... + */ +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, ¶ms); + 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 [=];... */ + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gchar **pairs; + gint i; + + /* [=] 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 ;.. is translated into =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 7c7515b..6b4922b 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 e319241..0fffbca 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 + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge * * 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 d2e0432..6c7cacf 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 + * Copyright (C) 2015 Centricular Ltd + * Author: Sebastian Dröge * * 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 4fb4195..b71c776 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 -- 2.7.4