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
This commit is contained in:
Sebastian Dröge 2015-01-09 12:40:47 +01:00
parent 18668bf495
commit ccf6c6eb53
11 changed files with 1468 additions and 86 deletions

View 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)

68
examples/test-record.c Normal file
View file

@ -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;
}

View 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;
/* prepare the media */
if (!(gst_rtsp_media_prepare (media, thread)))
goto no_prepare;
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;
}
/* 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);
@ -2171,6 +2228,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)
{
@ -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);

View 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);

View 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;
}

View 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);

View 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
@ -61,12 +63,22 @@
* Last reviewed on 2013-07-11 (1.0.0)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>
#include <gst/sdp/gstmikey.h>
#include <gst/rtp/gstrtppayloads.h>
#define AES_128_KEY_LEN 16
#define AES_256_KEY_LEN 32
#define HMAC_32_KEY_LEN 4
#define HMAC_80_KEY_LEN 10
#include "rtsp-media.h"
#define GST_RTSP_MEDIA_GET_PRIVATE(obj) \
@ -89,6 +101,7 @@ struct _GstRTSPMediaPrivate
guint buffer_size;
GstRTSPAddressPool *pool;
gboolean blocked;
gboolean record;
GstElement *element;
GRecMutex state_lock; /* locking order: state lock, lock */
@ -135,6 +148,7 @@ struct _GstRTSPMediaPrivate
#define DEFAULT_EOS_SHUTDOWN FALSE
#define DEFAULT_BUFFER_SIZE 0x80000
#define DEFAULT_TIME_PROVIDER FALSE
#define DEFAULT_RECORD FALSE
/* define to dump received RTCP packets */
#undef DUMP_STATS
@ -151,6 +165,7 @@ enum
PROP_BUFFER_SIZE,
PROP_ELEMENT,
PROP_TIME_PROVIDER,
PROP_RECORD,
PROP_LAST
};
@ -189,6 +204,7 @@ static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop);
static GstElement *default_create_rtpbin (GstRTSPMedia * media);
static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
GstSDPInfo * info);
static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
static gboolean wait_preroll (GstRTSPMedia * media);
@ -277,6 +293,11 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
"Use a NetTimeProvider for clients",
DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_RECORD,
g_param_spec_boolean ("record", "Record",
"If this media pipeline can be used for PLAY or RECORD",
DEFAULT_RECORD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL,
@ -320,6 +341,7 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
klass->query_stop = default_query_stop;
klass->create_rtpbin = default_create_rtpbin;
klass->setup_sdp = default_setup_sdp;
klass->handle_sdp = default_handle_sdp;
}
static void
@ -342,6 +364,7 @@ gst_rtsp_media_init (GstRTSPMedia * media)
priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
priv->buffer_size = DEFAULT_BUFFER_SIZE;
priv->time_provider = DEFAULT_TIME_PROVIDER;
priv->record = DEFAULT_RECORD;
}
static void
@ -412,6 +435,9 @@ gst_rtsp_media_get_property (GObject * object, guint propid,
case PROP_TIME_PROVIDER:
g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media));
break;
case PROP_RECORD:
g_value_set_boolean (value, gst_rtsp_media_is_record (media));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@ -452,6 +478,9 @@ gst_rtsp_media_set_property (GObject * object, guint propid,
case PROP_TIME_PROVIDER:
gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value));
break;
case PROP_RECORD:
gst_rtsp_media_set_record (media, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
@ -1300,6 +1329,9 @@ _next_available_pt (GList * payloads)
*
* Collect all dynamic elements, named dynpay\%d, and add them to
* the list of dynamic elements.
*
* Find all depayloader elements, they should be named depay\%d in the
* element of @media, and create #GstRTSPStreams for them.
*/
void
gst_rtsp_media_collect_streams (GstRTSPMedia * media)
@ -1348,6 +1380,21 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
have_elem = TRUE;
}
g_free (name);
name = g_strdup_printf ("depay%d", i);
if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
GST_INFO ("found stream %d with depayloader %p", i, elem);
/* take the pad of the payloader */
pad = gst_element_get_static_pad (elem, "sink");
/* create the stream */
gst_rtsp_media_create_stream (media, elem, pad);
gst_object_unref (pad);
gst_object_unref (elem);
have_elem = TRUE;
}
g_free (name);
}
}
@ -1355,10 +1402,10 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
* gst_rtsp_media_create_stream:
* @media: a #GstRTSPMedia
* @payloader: a #GstElement
* @srcpad: a source #GstPad
* @pad: a #GstPad
*
* Create a new stream in @media that provides RTP data on @srcpad.
* @srcpad should be a pad of an element inside @media->element.
* Create a new stream in @media that provides RTP data on @pad.
* @pad should be a pad of an element inside @media->element.
*
* Returns: (transfer none): a new #GstRTSPStream that remains valid for as long
* as @media exists.
@ -1369,15 +1416,13 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
{
GstRTSPMediaPrivate *priv;
GstRTSPStream *stream;
GstPad *srcpad;
GstPad *ghostpad;
gchar *name;
gint idx;
gint i, n;
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
g_return_val_if_fail (GST_IS_PAD (pad), NULL);
g_return_val_if_fail (GST_PAD_IS_SRC (pad), NULL);
priv = media->priv;
@ -1386,13 +1431,17 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
GST_DEBUG ("media %p: creating stream with index %d", media, idx);
name = g_strdup_printf ("src_%u", idx);
srcpad = gst_ghost_pad_new (name, pad);
gst_pad_set_active (srcpad, TRUE);
gst_element_add_pad (priv->element, srcpad);
if (GST_PAD_IS_SRC (pad))
name = g_strdup_printf ("src_%u", idx);
else
name = g_strdup_printf ("sink_%u", idx);
ghostpad = gst_ghost_pad_new (name, pad);
gst_pad_set_active (ghostpad, TRUE);
gst_element_add_pad (priv->element, ghostpad);
g_free (name);
stream = gst_rtsp_stream_new (idx, payloader, srcpad);
stream = gst_rtsp_stream_new (idx, payloader, ghostpad);
if (priv->pool)
gst_rtsp_stream_set_address_pool (stream, priv->pool);
gst_rtsp_stream_set_profiles (stream, priv->profiles);
@ -1401,23 +1450,28 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
g_ptr_array_add (priv->streams, stream);
if (priv->payloads)
g_list_free (priv->payloads);
priv->payloads = _find_payload_types (media);
if (GST_PAD_IS_SRC (pad)) {
gint i, n;
n = priv->streams->len;
for (i = 0; i < n; i++) {
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
guint rtx_pt = _next_available_pt (priv->payloads);
if (priv->payloads)
g_list_free (priv->payloads);
priv->payloads = _find_payload_types (media);
if (rtx_pt == 0) {
GST_WARNING ("Ran out of space of dynamic payload types");
break;
n = priv->streams->len;
for (i = 0; i < n; i++) {
GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
guint rtx_pt = _next_available_pt (priv->payloads);
if (rtx_pt == 0) {
GST_WARNING ("Ran out of space of dynamic payload types");
break;
}
gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);
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;
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_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;
}

View 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,

View 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);

View 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,9 +432,31 @@ 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;
@ -1808,6 +1854,101 @@ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPStream * stream)
return bin;
}
/**
* 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
@ -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]);

View 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