Add initial support for RECORD
authorSebastian Dröge <sebastian@centricular.com>
Fri, 9 Jan 2015 11:40:47 +0000 (12:40 +0100)
committerSebastian Dröge <sebastian@centricular.com>
Fri, 6 Feb 2015 08:42:42 +0000 (09:42 +0100)
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
examples/test-record.c [new file with mode: 0644]
gst/rtsp-server/rtsp-client.c
gst/rtsp-server/rtsp-client.h
gst/rtsp-server/rtsp-media-factory.c
gst/rtsp-server/rtsp-media-factory.h
gst/rtsp-server/rtsp-media.c
gst/rtsp-server/rtsp-media.h
gst/rtsp-server/rtsp-session-media.c
gst/rtsp-server/rtsp-stream.c
gst/rtsp-server/rtsp-stream.h

index 6d13ed6..ef33adf 100644 (file)
@@ -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 (file)
index 0000000..676ae98
--- /dev/null
@@ -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;
+}
index c6b582c..d69ef0a 100644 (file)
@@ -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);
index 4e9519a..5137cc6 100644 (file)
@@ -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);
index 6e0da84..34a0ac8 100644 (file)
@@ -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;
+}
index ce069d7..288d111 100644 (file)
@@ -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);
index eddbf41..9b64569 100644 (file)
@@ -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
  * 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;
+}
index 7c7515b..6b4922b 100644 (file)
@@ -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,
index e319241..0fffbca 100644 (file)
@@ -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);
index d2e0432..6c7cacf 100644 (file)
@@ -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]);
 
index 4fb4195..b71c776 100644 (file)
@@ -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