/* 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
#include <string.h>
#include <gst/sdp/gstmikey.h>
+#include <gst/rtsp/gstrtsp-enumtypes.h>
#include "rtsp-client.h"
#include "rtsp-sdp.h"
#include "rtsp-params.h"
-#define GST_RTSP_CLIENT_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClientPrivate))
+typedef enum
+{
+ TUNNEL_STATE_UNKNOWN,
+ TUNNEL_STATE_GET,
+ TUNNEL_STATE_POST
+} GstRTSPTunnelState;
/* locking order:
* send_lock, lock, tunnels_lock
GstRTSPConnection *connection;
GstRTSPWatch *watch;
GMainContext *watch_context;
- guint close_seq;
gchar *server_ip;
gboolean is_ipv6;
- GstRTSPClientSendFunc send_func; /* protected by send_lock */
- gpointer send_data; /* protected by send_lock */
- GDestroyNotify send_notify; /* protected by send_lock */
+ /* protected by send_lock */
+ GstRTSPClientSendFunc send_func;
+ gpointer send_data;
+ GDestroyNotify send_notify;
+ guint close_seq;
+ GArray *data_seqs;
GstRTSPSessionPool *session_pool;
gulong session_removed_id;
guint sessions_cookie;
gboolean drop_backlog;
+
+ guint rtsp_ctrl_timeout_id;
+ guint rtsp_ctrl_timeout_cnt;
+
+ /* The version currently being used */
+ GstRTSPVersion version;
+
+ GHashTable *pipelined_requests; /* pipelined_request_id -> session_id */
+ GstRTSPTunnelState tstate;
};
+typedef struct
+{
+ guint8 channel;
+ guint seq;
+} DataSeq;
+
static GMutex tunnels_lock;
static GHashTable *tunnels; /* protected by tunnels_lock */
-/* FIXME make this configurable. We don't want to do this yet because it will
- * be superceeded by a cache object later */
#define WATCH_BACKLOG_SIZE 100
#define DEFAULT_SESSION_POOL NULL
#define DEFAULT_MOUNT_POINTS NULL
#define DEFAULT_DROP_BACKLOG TRUE
+#define RTSP_CTRL_CB_INTERVAL 1
+#define RTSP_CTRL_TIMEOUT_VALUE 60
+
enum
{
PROP_0,
{
SIGNAL_CLOSED,
SIGNAL_NEW_SESSION,
+ SIGNAL_PRE_OPTIONS_REQUEST,
SIGNAL_OPTIONS_REQUEST,
+ SIGNAL_PRE_DESCRIBE_REQUEST,
SIGNAL_DESCRIBE_REQUEST,
+ SIGNAL_PRE_SETUP_REQUEST,
SIGNAL_SETUP_REQUEST,
+ SIGNAL_PRE_PLAY_REQUEST,
SIGNAL_PLAY_REQUEST,
+ SIGNAL_PRE_PAUSE_REQUEST,
SIGNAL_PAUSE_REQUEST,
+ SIGNAL_PRE_TEARDOWN_REQUEST,
SIGNAL_TEARDOWN_REQUEST,
+ SIGNAL_PRE_SET_PARAMETER_REQUEST,
SIGNAL_SET_PARAMETER_REQUEST,
+ SIGNAL_PRE_GET_PARAMETER_REQUEST,
SIGNAL_GET_PARAMETER_REQUEST,
SIGNAL_HANDLE_RESPONSE,
SIGNAL_SEND_MESSAGE,
+ SIGNAL_PRE_ANNOUNCE_REQUEST,
+ SIGNAL_ANNOUNCE_REQUEST,
+ SIGNAL_PRE_RECORD_REQUEST,
+ SIGNAL_RECORD_REQUEST,
+ SIGNAL_CHECK_REQUIREMENTS,
SIGNAL_LAST
};
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,
const GstRTSPUrl * uri);
static void client_session_removed (GstRTSPSessionPool * pool,
GstRTSPSession * session, GstRTSPClient * client);
+static GstRTSPStatusCode default_pre_signal_handler (GstRTSPClient * client,
+ GstRTSPContext * ctx);
+static gboolean pre_signal_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer data);
-G_DEFINE_TYPE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT);
+G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT);
static void
gst_rtsp_client_class_init (GstRTSPClientClass * klass)
{
GObjectClass *gobject_class;
- g_type_class_add_private (klass, sizeof (GstRTSPClientPrivate));
-
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gst_rtsp_client_get_property;
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;
klass->params_get = default_params_get;
klass->make_path_from_uri = default_make_path_from_uri;
+ klass->pre_options_request = default_pre_signal_handler;
+ klass->pre_describe_request = default_pre_signal_handler;
+ klass->pre_setup_request = default_pre_signal_handler;
+ klass->pre_play_request = default_pre_signal_handler;
+ klass->pre_pause_request = default_pre_signal_handler;
+ klass->pre_teardown_request = default_pre_signal_handler;
+ klass->pre_set_parameter_request = default_pre_signal_handler;
+ klass->pre_get_parameter_request = default_pre_signal_handler;
+ klass->pre_announce_request = default_pre_signal_handler;
+ klass->pre_record_request = default_pre_signal_handler;
+
g_object_class_install_property (gobject_class, PROP_SESSION_POOL,
g_param_spec_object ("session-pool", "Session Pool",
"The session pool to use for client session",
G_STRUCT_OFFSET (GstRTSPClientClass, new_session), NULL, NULL,
g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_RTSP_SESSION);
+ /**
+ * GstRTSPClient::pre-options-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST] =
+ g_signal_new ("pre-options-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_options_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::options-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST] =
g_signal_new ("options-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, options_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-describe-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST] =
+ g_signal_new ("pre-describe-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_describe_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::describe-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST] =
g_signal_new ("describe-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, describe_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-setup-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST] =
+ g_signal_new ("pre-setup-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_setup_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::setup-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST] =
g_signal_new ("setup-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, setup_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-play-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST] =
+ g_signal_new ("pre-play-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_play_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::play-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST] =
g_signal_new ("play-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, play_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-pause-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST] =
+ g_signal_new ("pre-pause-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_pause_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pause-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST] =
g_signal_new ("pause-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, pause_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-teardown-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST] =
+ g_signal_new ("pre-teardown-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_teardown_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::teardown-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST] =
g_signal_new ("teardown-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, teardown_request),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-set-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST] =
+ g_signal_new ("pre-set-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_set_parameter_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::set-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST] =
g_signal_new ("set-parameter-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
set_parameter_request), NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::pre-get-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST] =
+ g_signal_new ("pre-get-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_get_parameter_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::get-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST] =
g_signal_new ("get-parameter-request", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
get_parameter_request), NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+ /**
+ * GstRTSPClient::handle-response:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE] =
g_signal_new ("handle-response", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
send_message), NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_POINTER);
+ /**
+ * GstRTSPClient::pre-announce-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST] =
+ g_signal_new ("pre-announce-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_announce_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::announce-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ 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);
+
+ /**
+ * GstRTSPClient::pre-record-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST] =
+ g_signal_new ("pre-record-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_record_request), pre_signal_accumulator, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_RTSP_STATUS_CODE, 1,
+ GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::record-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ 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);
+
+ /**
+ * GstRTSPClient::check-requirements:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ * @arr: a NULL-terminated array of strings
+ *
+ * Returns: a newly allocated string with comma-separated list of
+ * unsupported options. An empty string must be returned if
+ * all options are supported.
+ *
+ * Since: 1.6
+ */
+ gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS] =
+ g_signal_new ("check-requirements", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ check_requirements), NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_STRING, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_STRV);
+
tunnels =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
g_mutex_init (&tunnels_lock);
static void
gst_rtsp_client_init (GstRTSPClient * client)
{
- GstRTSPClientPrivate *priv = GST_RTSP_CLIENT_GET_PRIVATE (client);
+ GstRTSPClientPrivate *priv = gst_rtsp_client_get_instance_private (client);
client->priv = priv;
g_mutex_init (&priv->send_lock);
g_mutex_init (&priv->watch_lock);
priv->close_seq = 0;
+ priv->data_seqs = g_array_new (FALSE, FALSE, sizeof (DataSeq));
priv->drop_backlog = DEFAULT_DROP_BACKLOG;
priv->transports =
g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
g_object_unref);
+ priv->pipelined_requests = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, g_free);
+ priv->tstate = TUNNEL_STATE_UNKNOWN;
}
static GstRTSPFilterResult
filter_session_media (GstRTSPSession * sess, GstRTSPSessionMedia * sessmedia,
gpointer user_data)
{
- gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
+ gboolean *closed = user_data;
+ GstRTSPMedia *media;
+ guint i, n_streams;
+ gboolean is_all_udp = TRUE;
+
+ media = gst_rtsp_session_media_get_media (sessmedia);
+ n_streams = gst_rtsp_media_n_streams (media);
- return GST_RTSP_FILTER_REMOVE;
+ for (i = 0; i < n_streams; i++) {
+ GstRTSPStreamTransport *transport =
+ gst_rtsp_session_media_get_transport (sessmedia, i);
+ const GstRTSPTransport *rtsp_transport;
+
+ if (!transport)
+ continue;
+
+ rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
+ if (rtsp_transport
+ && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP
+ && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ is_all_udp = FALSE;
+ break;
+ }
+ }
+
+ if (!is_all_udp || gst_rtsp_media_is_stop_on_disconnect (media)) {
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
+ return GST_RTSP_FILTER_REMOVE;
+ } else {
+ *closed = FALSE;
+ return GST_RTSP_FILTER_KEEP;
+ }
}
static void
priv->session_removed_id = 0;
}
+ if (!priv->drop_backlog) {
+ /* unlink all media managed in this session */
+ gst_rtsp_session_filter (session, filter_session_media, client);
+ }
+
/* remove the session */
g_object_unref (session);
}
cleanup_session (GstRTSPClient * client, GstRTSPSession * sess,
gpointer user_data)
{
- /* unlink all media managed in this session. This needs to happen
- * without the client lock, so we really want to do it here. */
- gst_rtsp_session_filter (sess, filter_session_media, client);
+ gboolean *closed = user_data;
+ GstRTSPClientPrivate *priv = client->priv;
+
+ if (priv->drop_backlog) {
+ /* unlink all media managed in this session. This needs to happen
+ * without the client lock, so we really want to do it here. */
+ gst_rtsp_session_filter (sess, filter_session_media, user_data);
+ }
- return GST_RTSP_FILTER_REMOVE;
+ if (*closed)
+ return GST_RTSP_FILTER_REMOVE;
+ else
+ return GST_RTSP_FILTER_KEEP;
}
static void
g_assert (priv->sessions == NULL);
g_assert (priv->session_removed_id == 0);
+ g_array_unref (priv->data_seqs);
g_hash_table_unref (priv->transports);
+ g_hash_table_unref (priv->pipelined_requests);
if (priv->connection)
gst_rtsp_connection_free (priv->connection);
if (close)
gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close");
+ if (ctx->request)
+ message->type_data.response.version =
+ ctx->request->type_data.request.version;
+
g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE],
0, ctx, message);
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);
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_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD)) {
+ 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);
}
no_factory_access:
{
+ g_object_unref (factory);
+ ctx->factory = NULL;
GST_ERROR ("client %p: not authorized to see factory path %s", client,
path);
/* error reply is already sent */
}
not_authorized:
{
+ g_object_unref (factory);
+ ctx->factory = NULL;
GST_ERROR ("client %p: not authorized for factory path %s", client, path);
/* error reply is already sent */
return NULL;
}
}
+static inline DataSeq *
+get_data_seq_element (GstRTSPClient * client, guint8 channel)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ GArray *data_seqs = priv->data_seqs;
+ gint i = 0;
+
+ while (i < data_seqs->len) {
+ DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
+ if (data_seq->channel == channel)
+ return data_seq;
+ i++;
+ }
+
+ return NULL;
+}
+
+static void
+add_data_seq (GstRTSPClient * client, guint8 channel)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ DataSeq data_seq = {.channel = channel,.seq = 0 };
+
+ if (get_data_seq_element (client, channel) == NULL)
+ g_array_append_val (priv->data_seqs, data_seq);
+}
+
+static void
+set_data_seq (GstRTSPClient * client, guint8 channel, guint seq)
+{
+ DataSeq *data_seq;
+
+ data_seq = get_data_seq_element (client, channel);
+ g_assert_nonnull (data_seq);
+ data_seq->seq = seq;
+}
+
+static guint
+get_data_seq (GstRTSPClient * client, guint8 channel)
+{
+ DataSeq *data_seq;
+
+ data_seq = get_data_seq_element (client, channel);
+ g_assert_nonnull (data_seq);
+ return data_seq->seq;
+}
+
+static gboolean
+get_data_channel (GstRTSPClient * client, guint seq, guint8 * channel)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ GArray *data_seqs = priv->data_seqs;
+ gint i = 0;
+
+ while (i < data_seqs->len) {
+ DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
+ if (data_seq->seq == seq) {
+ *channel = data_seq->channel;
+ return TRUE;
+ }
+ i++;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+do_close (gpointer user_data)
+{
+ GstRTSPClient *client = user_data;
+
+ gst_rtsp_client_close (client);
+
+ return G_SOURCE_REMOVE;
+}
+
static gboolean
do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client)
{
GstRTSPClientPrivate *priv = client->priv;
GstRTSPMessage message = { 0 };
- GstRTSPResult res = GST_RTSP_OK;
+ gboolean ret = TRUE;
GstMapInfo map_info;
guint8 *data;
guint usize;
gst_rtsp_message_take_body (&message, map_info.data, map_info.size);
g_mutex_lock (&priv->send_lock);
+ if (get_data_seq (client, channel) != 0) {
+ GST_WARNING ("already a queued data message for channel %d", channel);
+ g_mutex_unlock (&priv->send_lock);
+ return FALSE;
+ }
if (priv->send_func)
- res = priv->send_func (client, &message, FALSE, priv->send_data);
+ ret = priv->send_func (client, &message, FALSE, priv->send_data);
g_mutex_unlock (&priv->send_lock);
gst_rtsp_message_steal_body (&message, &data, &usize);
gst_rtsp_message_unset (&message);
- return res == GST_RTSP_OK;
+ if (!ret) {
+ GSource *idle_src;
+
+ /* close in watch context */
+ idle_src = g_idle_source_new ();
+ g_source_set_callback (idle_src, do_close, client, NULL);
+ g_source_attach (idle_src, priv->watch_context);
+ g_source_unref (idle_src);
+ }
+
+ return ret;
}
/**
return path;
}
+/* Default signal handler function for all "pre-command" signals, like
+ * pre-options-request. It just returns the RTSP return code 200.
+ * Subclasses can override this to get another default behaviour.
+ */
+static GstRTSPStatusCode
+default_pre_signal_handler (GstRTSPClient * client, GstRTSPContext * ctx)
+{
+ GST_LOG_OBJECT (client, "returning GST_RTSP_STS_OK");
+ return GST_RTSP_STS_OK;
+}
+
+/* The pre-signal accumulator function checks the return value of the signal
+ * handlers. If any of them returns an RTSP status code that does not start
+ * with 2 it will return FALSE, no more signal handlers will be called, and
+ * this last RTSP status code will be the result of the signal emission.
+ */
+static gboolean
+pre_signal_accumulator (GSignalInvocationHint * ihint, GValue * return_accu,
+ const GValue * handler_return, gpointer data)
+{
+ GstRTSPStatusCode handler_value = g_value_get_enum (handler_return);
+ GstRTSPStatusCode accumulated_value = g_value_get_enum (return_accu);
+
+ if (handler_value < 200 || handler_value > 299) {
+ GST_DEBUG ("handler_value : %d, returning FALSE", handler_value);
+ g_value_set_enum (return_accu, handler_value);
+ return FALSE;
+ }
+
+ /* the accumulated value is initiated to 0 by GLib. if current handler value is
+ * bigger then use that instead
+ *
+ * FIXME: Should we prioritize the 2xx codes in a smarter way?
+ * Like, "201 Created" > "250 Low On Storage Space" > "200 OK"?
+ */
+ if (handler_value > accumulated_value)
+ g_value_set_enum (return_accu, handler_value);
+
+ return TRUE;
+}
+
static gboolean
handle_teardown_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
gchar *path;
gint matched;
gboolean keep_session;
+ GstRTSPStatusCode sig_result;
if (!ctx->session)
goto no_session;
ctx->sessmedia = sessmedia;
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
/* we emit the signal before closing the connection */
g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST],
0, ctx);
- /* make sure we unblock the backlog and don't accept new messages
- * on the watch */
- if (priv->watch != NULL)
- gst_rtsp_watch_set_flushing (priv->watch, TRUE);
-
gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
- /* allow messages again so that we can send the reply */
- if (priv->watch != NULL)
- gst_rtsp_watch_set_flushing (priv->watch, FALSE);
-
/* unmanage the media in the session, returns false if all media session
* are torn down. */
keep_session = gst_rtsp_session_release_media (session, sessmedia);
g_free (path);
return FALSE;
}
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
}
static GstRTSPResult
GstRTSPResult res;
guint8 *data;
guint size;
+ GstRTSPStatusCode sig_result;
+
+ g_signal_emit (client,
+ gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST], 0, ctx,
+ &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
res = gst_rtsp_message_get_body (ctx->request, &data, &size);
if (res != GST_RTSP_OK)
goto bad_request;
- if (size == 0) {
- /* no body, keep-alive request */
+ if (size == 0 || !data || strlen ((char *) data) == 0) {
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+ GST_ERROR_OBJECT (client, "Using PLAY request for keep-alive is forbidden"
+ " in RTSP 2.0");
+ goto bad_request;
+ }
+
+ /* no body (or only '\0'), keep-alive request */
send_generic_response (client, GST_RTSP_STS_OK, ctx);
} else {
/* there is a body, handle the params */
return TRUE;
/* ERRORS */
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
bad_request:
{
GST_ERROR ("client %p: bad request", client);
GstRTSPResult res;
guint8 *data;
guint size;
+ GstRTSPStatusCode sig_result;
+
+ g_signal_emit (client,
+ gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST], 0, ctx,
+ &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
res = gst_rtsp_message_get_body (ctx->request, &data, &size);
if (res != GST_RTSP_OK)
goto bad_request;
- if (size == 0) {
- /* no body, keep-alive request */
+ if (size == 0 || !data || strlen ((char *) data) == 0) {
+ /* no body (or only '\0'), keep-alive request */
send_generic_response (client, GST_RTSP_STS_OK, ctx);
} else {
/* there is a body, handle the params */
return TRUE;
/* ERRORS */
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
bad_request:
{
GST_ERROR ("client %p: bad request", client);
GstRTSPSession *session;
GstRTSPClientClass *klass;
GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
GstRTSPStatusCode code;
GstRTSPState rtspstate;
gchar *path;
gint matched;
+ GstRTSPStatusCode sig_result;
+ guint i, n;
if (!(session = ctx->session))
goto no_session;
g_free (path);
+ media = gst_rtsp_session_media_get_media (sessmedia);
+ n = gst_rtsp_media_n_streams (media);
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
+
+ if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET)
+ goto not_supported;
+ }
+
ctx->sessmedia = sessmedia;
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
/* the session state must be playing or recording */
if (rtspstate != GST_RTSP_STATE_PLAYING &&
/* ERRORS */
no_session:
{
- GST_ERROR ("client %p: no seesion", client);
+ GST_ERROR ("client %p: no session", client);
send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
return FALSE;
}
g_free (path);
return FALSE;
}
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
invalid_state:
{
GST_ERROR ("client %p: not PLAYING or RECORDING", client);
ctx);
return FALSE;
}
+not_supported:
+ {
+ GST_ERROR ("client %p: pausing not supported", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
}
/* convert @url and @path to a URL used as a content base for the factory
GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT;
gchar *path, *rtpinfo;
gint matched;
+ gchar *seek_style = NULL;
+ GstRTSPStatusCode sig_result;
+ GPtrArray *transports;
if (!(session = ctx->session))
goto no_session;
ctx->sessmedia = sessmedia;
ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ goto unsupported_mode;
+
/* 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;
+ /* update the pipeline */
+ transports = gst_rtsp_session_media_get_transports (sessmedia);
+ if (!gst_rtsp_media_complete_pipeline (media, transports)) {
+ g_ptr_array_unref (transports);
+ goto pipeline_error;
+ }
+ g_ptr_array_unref (transports);
+
/* in play we first unsuspend, media could be suspended from SDP or PAUSED */
if (!gst_rtsp_media_unsuspend (media))
goto unsuspend_failed;
res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0);
if (res == GST_RTSP_OK) {
if (gst_rtsp_range_parse (str, &range) == GST_RTSP_OK) {
+ GstRTSPMediaStatus media_status;
+ GstSeekFlags flags = 0;
+
+ if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_SEEK_STYLE,
+ &seek_style, 0)) {
+ if (g_strcmp0 (seek_style, "RAP") == 0)
+ flags = GST_SEEK_FLAG_ACCURATE;
+ else if (g_strcmp0 (seek_style, "CoRAP") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT;
+ else if (g_strcmp0 (seek_style, "First-Prior") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_BEFORE;
+ else if (g_strcmp0 (seek_style, "Next") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_AFTER;
+ else
+ GST_FIXME_OBJECT (client, "Add support for seek style %s",
+ seek_style);
+ }
+
/* we have a range, seek to the position */
unit = range->unit;
- gst_rtsp_media_seek (media, range);
+ gst_rtsp_media_seek_full (media, range, flags);
gst_rtsp_range_free (range);
+
+ media_status = gst_rtsp_media_get_status (media);
+ if (media_status == GST_RTSP_MEDIA_STATUS_ERROR)
+ goto seek_failed;
}
}
if (rtpinfo)
gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO,
rtpinfo);
+ if (seek_style)
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_SEEK_STYLE,
+ seek_style);
/* add the range */
str = gst_rtsp_media_get_range_string (media, TRUE, unit);
g_free (path);
return FALSE;
}
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
invalid_state:
{
GST_ERROR ("client %p: not PLAYING or READY", client);
ctx);
return FALSE;
}
+pipeline_error:
+ {
+ GST_ERROR ("client %p: failed to configure the pipeline", 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;
}
-}
+seek_failed:
+ {
+ GST_ERROR ("client %p: seek failed", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ return FALSE;
+ }
+unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support PLAY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ return FALSE;
+ }
+}
static void
do_keepalive (GstRTSPSession * session)
/* loop through the transports, try to parse */
for (i = 0; transports[i]; i++) {
+ g_strstrip (transports[i]);
res = gst_rtsp_transport_parse (transports[i], tr);
if (res != GST_RTSP_OK) {
/* no valid transport, search some more */
GstRTSPMessage *request = ctx->request;
gchar *blocksize_str;
+ if (!gst_rtsp_stream_is_sender (stream))
+ return TRUE;
+
if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_BLOCKSIZE,
&blocksize_str, 0) == GST_RTSP_OK) {
guint64 blocksize;
GstRTSPClientPrivate *priv = client->priv;
/* we have a valid transport now, set the destination of the client. */
- if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
- gboolean use_client_settings;
+ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST ||
+ ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP) {
+ /* allocate UDP ports */
+ GSocketFamily family;
+ gboolean use_client_settings = FALSE;
- use_client_settings =
- gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS);
+ family = priv->is_ipv6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4;
- if (ct->destination && use_client_settings) {
- GstRTSPAddress *addr;
+ if ((ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) &&
+ gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS) &&
+ (ct->destination != NULL)) {
- addr = gst_rtsp_stream_reserve_address (ctx->stream, ct->destination,
- ct->port.min, ct->port.max - ct->port.min + 1, ct->ttl);
+ if (!gst_rtsp_stream_verify_mcast_ttl (ctx->stream, ct->ttl))
+ goto error_ttl;
- if (addr == NULL)
- goto no_address;
+ use_client_settings = TRUE;
+ }
- gst_rtsp_address_free (addr);
- } else {
- GstRTSPAddress *addr;
- GSocketFamily family;
+ /* We need to allocate the sockets for both families before starting
+ * multiudpsink, otherwise multiudpsink won't accept new clients with
+ * a different family.
+ */
+ /* FIXME: could be more adequately solved by making it possible
+ * to set a socket on multiudpsink after it has already been started */
+ if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
+ G_SOCKET_FAMILY_IPV4, ct, use_client_settings)
+ && family == G_SOCKET_FAMILY_IPV4)
+ goto error_allocating_ports;
+
+ if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
+ G_SOCKET_FAMILY_IPV6, ct, use_client_settings)
+ && family == G_SOCKET_FAMILY_IPV6)
+ goto error_allocating_ports;
+
+ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ if (use_client_settings) {
+ /* FIXME: the address has been successfully allocated, however, in
+ * the use_client_settings case we need to verify that the allocated
+ * address is the one requested by the client and if this address is
+ * an allowed destination. Verifying this via the address pool in not
+ * the proper way as the address pool should only be used for choosing
+ * the server-selected address/port pairs. */
+ GSocket *rtp_socket;
+ guint ttl;
+
+ rtp_socket =
+ gst_rtsp_stream_get_rtp_multicast_socket (ctx->stream, family);
+ if (rtp_socket == NULL)
+ goto no_socket;
+ ttl = g_socket_get_multicast_ttl (rtp_socket);
+ g_object_unref (rtp_socket);
+ if (ct->ttl < ttl) {
+ /* use the maximum ttl that is requested by multicast clients */
+ GST_DEBUG ("requested ttl %u, but keeping ttl %u", ct->ttl, ttl);
+ ct->ttl = ttl;
+ }
+
+ } else {
+ GstRTSPAddress *addr = NULL;
+
+ g_free (ct->destination);
+ addr = gst_rtsp_stream_get_multicast_address (ctx->stream, family);
+ if (addr == NULL)
+ goto no_address;
+ ct->destination = g_strdup (addr->address);
+ ct->port.min = addr->port;
+ ct->port.max = addr->port + addr->n_ports - 1;
+ ct->ttl = addr->ttl;
+ gst_rtsp_address_free (addr);
+ }
- family = priv->is_ipv6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4;
+ if (!gst_rtsp_stream_add_multicast_client_address (ctx->stream,
+ ct->destination, ct->port.min, ct->port.max, family))
+ goto error_mcast_transport;
- addr = gst_rtsp_stream_get_multicast_address (ctx->stream, family);
- if (addr == NULL)
- goto no_address;
+ } else {
+ GstRTSPUrl *url;
+ url = gst_rtsp_connection_get_url (priv->connection);
g_free (ct->destination);
- ct->destination = g_strdup (addr->address);
- ct->port.min = addr->port;
- ct->port.max = addr->port + addr->n_ports - 1;
- ct->ttl = addr->ttl;
-
- gst_rtsp_address_free (addr);
+ ct->destination = g_strdup (url->host);
}
} else {
GstRTSPUrl *url;
gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
&ct->interleaved);
}
+ /* alloc new channels if they are already taken */
+ while (g_hash_table_contains (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.min))
+ || g_hash_table_contains (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.max))) {
+ gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
+ &ct->interleaved);
+ if (ct->interleaved.max > 255)
+ goto error_allocating_channels;
+ }
}
}
return TRUE;
/* ERRORS */
+error_ttl:
+ {
+ GST_ERROR_OBJECT (client,
+ "Failed to allocate UDP ports: invalid ttl value");
+ return FALSE;
+ }
+error_allocating_ports:
+ {
+ GST_ERROR_OBJECT (client, "Failed to allocate UDP ports");
+ return FALSE;
+ }
no_address:
{
- GST_ERROR_OBJECT (client, "failed to acquire address for stream");
+ GST_ERROR_OBJECT (client, "Failed to acquire address for stream");
+ return FALSE;
+ }
+no_socket:
+ {
+ GST_ERROR_OBJECT (client, "Failed to get UDP socket");
+ return FALSE;
+ }
+error_mcast_transport:
+ {
+ GST_ERROR_OBJECT (client, "Failed to add multicast client transport");
+ return FALSE;
+ }
+error_allocating_channels:
+ {
+ GST_ERROR_OBJECT (client, "Failed to allocate interleaved channels");
return FALSE;
}
}
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;
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);
break;
}
- gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);
+ if ((gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);
return st;
}
-#define AES_128_KEY_LEN 16
-#define AES_256_KEY_LEN 32
-
-#define HMAC_32_KEY_LEN 4
-#define HMAC_80_KEY_LEN 10
-
static gboolean
-mikey_apply_policy (GstCaps * caps, GstMIKEYMessage * msg, guint8 policy)
+rtsp_ctrl_timeout_cb (gpointer user_data)
{
- const gchar *srtp_cipher;
- const gchar *srtp_auth;
- const GstMIKEYPayload *sp;
- guint i;
+ gboolean res = G_SOURCE_CONTINUE;
+ GstRTSPClient *client = (GstRTSPClient *) user_data;
+ GstRTSPClientPrivate *priv = client->priv;
- /* loop over Security policy until we find one containing policy */
- for (i = 0;; i++) {
- if ((sp = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, i)) == NULL)
- break;
+ priv->rtsp_ctrl_timeout_cnt += RTSP_CTRL_CB_INTERVAL;
- if (((GstMIKEYPayloadSP *) sp)->policy == policy)
- break;
+ if (priv->rtsp_ctrl_timeout_cnt > RTSP_CTRL_TIMEOUT_VALUE) {
+ GST_DEBUG ("rtsp control session timeout id=%u expired, closing client.",
+ priv->rtsp_ctrl_timeout_id);
+ g_mutex_lock (&priv->lock);
+ priv->rtsp_ctrl_timeout_id = 0;
+ priv->rtsp_ctrl_timeout_cnt = 0;
+ g_mutex_unlock (&priv->lock);
+ gst_rtsp_client_close (client);
+
+ res = G_SOURCE_REMOVE;
}
- /* the default ciphers */
- srtp_cipher = "aes-128-icm";
- srtp_auth = "hmac-sha1-80";
-
- /* now override the defaults with what is in the Security Policy */
- if (sp != NULL) {
- guint len;
-
- /* collect all the params and go over them */
- len = gst_mikey_payload_sp_get_n_params (sp);
- for (i = 0; i < len; i++) {
- const GstMIKEYPayloadSPParam *param =
- gst_mikey_payload_sp_get_param (sp, 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;
- }
- }
+ return res;
+}
+
+static void
+rtsp_ctrl_timeout_remove (GstRTSPClientPrivate * priv)
+{
+ g_mutex_lock (&priv->lock);
+
+ if (priv->rtsp_ctrl_timeout_id != 0) {
+ g_source_destroy (g_main_context_find_source_by_id (priv->watch_context,
+ priv->rtsp_ctrl_timeout_id));
+ GST_DEBUG ("rtsp control session removed timeout id=%u.",
+ priv->rtsp_ctrl_timeout_id);
+ priv->rtsp_ctrl_timeout_id = 0;
+ priv->rtsp_ctrl_timeout_cnt = 0;
}
- /* now configure the SRTP parameters */
- 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);
- return TRUE;
+ g_mutex_unlock (&priv->lock);
}
-static gboolean
-handle_mikey_data (GstRTSPClient * client, GstRTSPContext * ctx,
- guint8 * data, gsize size)
-{
- GstMIKEYMessage *msg;
- guint i, n_cs;
- GstCaps *caps = NULL;
- GstMIKEYPayloadKEMAC *kemac;
- const GstMIKEYPayloadKeyData *pkd;
+static gchar *
+stream_make_keymgmt (GstRTSPClient * client, const gchar * location,
+ GstRTSPStream * stream)
+{
+ gchar *base64, *result = NULL;
+ GstMIKEYMessage *mikey_msg;
+ GstCaps *srtcpparams;
+ GstElement *rtcp_encoder;
+ gint srtcp_cipher, srtp_cipher;
+ gint srtcp_auth, srtp_auth;
GstBuffer *key;
+ GType ciphertype, authtype;
+ GEnumClass *cipher_enum, *auth_enum;
+ GEnumValue *srtcp_cipher_value, *srtp_cipher_value, *srtcp_auth_value,
+ *srtp_auth_value;
- /* the MIKEY message contains a CSB or crypto session bundle. It is a
- * set of Crypto Sessions protected with the same master key.
- * In the context of SRTP, an RTP and its RTCP stream is part of a
- * crypto session */
- if ((msg = gst_mikey_message_new_from_data (data, size, NULL, NULL)) == NULL)
- goto parse_failed;
-
- /* we can only handle SRTP crypto sessions for now */
- if (msg->map_type != GST_MIKEY_MAP_TYPE_SRTP)
- goto invalid_map_type;
-
- /* get the number of crypto sessions. This maps SSRC to its
- * security parameters */
- n_cs = gst_mikey_message_get_n_cs (msg);
- if (n_cs == 0)
- goto no_crypto_sessions;
-
- /* we also need keys */
- if (!(kemac = (GstMIKEYPayloadKEMAC *) gst_mikey_message_find_payload
- (msg, GST_MIKEY_PT_KEMAC, 0)))
- goto no_keys;
-
- /* we don't support encrypted keys */
- if (kemac->enc_alg != GST_MIKEY_ENC_NULL
- || kemac->mac_alg != GST_MIKEY_MAC_NULL)
- goto unsupported_encryption;
-
- /* get Key data sub-payload */
- pkd = (const GstMIKEYPayloadKeyData *)
- gst_mikey_payload_kemac_get_sub (&kemac->pt, 0);
-
- key =
- gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len),
- pkd->key_len);
-
- /* go over all crypto sessions and create the security policy for each
- * SSRC */
- for (i = 0; i < n_cs; i++) {
- const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i);
-
- caps = gst_caps_new_simple ("application/x-srtp",
- "ssrc", G_TYPE_UINT, map->ssrc,
- "roc", G_TYPE_UINT, map->roc, "srtp-key", GST_TYPE_BUFFER, key, NULL);
- mikey_apply_policy (caps, msg, map->policy);
-
- gst_rtsp_stream_update_crypto (ctx->stream, map->ssrc, caps);
- gst_caps_unref (caps);
- }
- gst_mikey_message_unref (msg);
- gst_buffer_unref (key);
+ rtcp_encoder = gst_rtsp_stream_get_srtp_encoder (stream);
- return TRUE;
+ if (!rtcp_encoder)
+ goto done;
- /* ERRORS */
-parse_failed:
- {
- GST_DEBUG_OBJECT (client, "failed to parse MIKEY message");
- return FALSE;
- }
-invalid_map_type:
- {
- GST_DEBUG_OBJECT (client, "invalid map type %d", msg->map_type);
- goto cleanup_message;
- }
-no_crypto_sessions:
- {
- GST_DEBUG_OBJECT (client, "no crypto sessions");
- goto cleanup_message;
- }
-no_keys:
- {
- GST_DEBUG_OBJECT (client, "no keys found");
- goto cleanup_message;
- }
-unsupported_encryption:
- {
- GST_DEBUG_OBJECT (client, "unsupported key encryption");
- goto cleanup_message;
- }
-cleanup_message:
- {
- gst_mikey_message_unref (msg);
- return FALSE;
- }
-}
+ ciphertype = g_type_from_name ("GstSrtpCipherType");
+ authtype = g_type_from_name ("GstSrtpAuthType");
-#define IS_STRIP_CHAR(c) (g_ascii_isspace ((guchar)(c)) || ((c) == '\"'))
+ cipher_enum = g_type_class_ref (ciphertype);
+ auth_enum = g_type_class_ref (authtype);
-static void
-strip_chars (gchar * str)
-{
- gchar *s;
- gsize len;
+ /* We need to bring the encoder to READY so that it generates its key */
+ gst_element_set_state (rtcp_encoder, GST_STATE_READY);
- len = strlen (str);
- while (len--) {
- if (!IS_STRIP_CHAR (str[len]))
- break;
- str[len] = '\0';
- }
- for (s = str; *s && IS_STRIP_CHAR (*s); s++);
- memmove (str, s, len + 1);
-}
+ g_object_get (rtcp_encoder, "rtcp-cipher", &srtcp_cipher, "rtcp-auth",
+ &srtcp_auth, "rtp-cipher", &srtp_cipher, "rtp-auth", &srtp_auth, "key",
+ &key, NULL);
+ g_object_unref (rtcp_encoder);
-/* KeyMgmt = "KeyMgmt" ":" key-mgmt-spec 0*("," key-mgmt-spec)
- * key-mgmt-spec = "prot" "=" KMPID ";" ["uri" "=" %x22 URI %x22 ";"]
- */
-static gboolean
-handle_keymgmt (GstRTSPClient * client, GstRTSPContext * ctx, gchar * keymgmt)
-{
- gchar **specs;
- gint i, j;
-
- specs = g_strsplit (keymgmt, ",", 0);
- for (i = 0; specs[i]; i++) {
- gchar **split;
-
- split = g_strsplit (specs[i], ";", 0);
- for (j = 0; split[j]; j++) {
- g_strstrip (split[j]);
- if (g_str_has_prefix (split[j], "prot=")) {
- g_strstrip (split[j] + 5);
- if (!g_str_equal (split[j] + 5, "mikey"))
- break;
- GST_DEBUG ("found mikey");
- } else if (g_str_has_prefix (split[j], "uri=")) {
- strip_chars (split[j] + 4);
- GST_DEBUG ("found uri '%s'", split[j] + 4);
- } else if (g_str_has_prefix (split[j], "data=")) {
- guchar *data;
- gsize size;
- strip_chars (split[j] + 5);
- GST_DEBUG ("found data '%s'", split[j] + 5);
- data = g_base64_decode_inplace (split[j] + 5, &size);
- handle_mikey_data (client, ctx, data, size);
- }
+ srtcp_cipher_value = g_enum_get_value (cipher_enum, srtcp_cipher);
+ srtp_cipher_value = g_enum_get_value (cipher_enum, srtp_cipher);
+ srtcp_auth_value = g_enum_get_value (auth_enum, srtcp_auth);
+ srtp_auth_value = g_enum_get_value (auth_enum, srtp_auth);
+
+ g_type_class_unref (cipher_enum);
+ g_type_class_unref (auth_enum);
+
+ srtcpparams = gst_caps_new_simple ("application/x-srtcp",
+ "srtcp-cipher", G_TYPE_STRING, srtcp_cipher_value->value_nick,
+ "srtcp-auth", G_TYPE_STRING, srtcp_auth_value->value_nick,
+ "srtp-cipher", G_TYPE_STRING, srtp_cipher_value->value_nick,
+ "srtp-auth", G_TYPE_STRING, srtp_auth_value->value_nick,
+ "srtp-key", GST_TYPE_BUFFER, key, NULL);
+
+ mikey_msg = gst_mikey_message_new_from_caps (srtcpparams);
+ if (mikey_msg) {
+ guint send_ssrc;
+
+ gst_rtsp_stream_get_ssrc (stream, &send_ssrc);
+ gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_ssrc, 0);
+
+ base64 = gst_mikey_message_base64_encode (mikey_msg);
+ gst_mikey_message_unref (mikey_msg);
+
+ if (base64) {
+ result = gst_sdp_make_keymgmt (location, base64);
+ g_free (base64);
}
- g_strfreev (split);
}
- g_strfreev (specs);
- return TRUE;
+
+done:
+ return result;
}
static gboolean
gchar *path, *control = NULL;
gint matched;
gboolean new_session = FALSE;
+ GstRTSPStatusCode sig_result;
+ gchar *pipelined_request_id = NULL, *accept_range = NULL;
if (!ctx->uri)
goto no_uri;
if (res != GST_RTSP_OK)
goto no_transport;
+ /* Handle Pipelined-requests if using >= 2.0 */
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0)
+ gst_rtsp_message_get_header (ctx->request,
+ GST_RTSP_HDR_PIPELINED_REQUESTS, &pipelined_request_id, 0);
+
/* we create the session after parsing stuff so that we don't make
* a session for malformed requests */
if (priv->session_pool == NULL)
if (sessmedia == NULL) {
/* get a handle to the configuration of the media in the session */
media = find_media (client, ctx, path, &matched);
+ /* need to suspend the media, if the protocol has changed */
+ if (media != NULL)
+ gst_rtsp_media_suspend (media);
} else {
if ((media = gst_rtsp_session_media_get_media (sessmedia)))
g_object_ref (media);
ctx->stream = stream;
ctx->media = media;
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
if (session == NULL) {
/* create a session if this fails we probably reached our session limit or
* something. */
if (!(session = gst_rtsp_session_pool_create (priv->session_pool)))
goto service_unavailable;
+ /* Pipelined requests should be cleared between sessions */
+ g_hash_table_remove_all (priv->pipelined_requests);
+
/* make sure this client is closed when the session is closed */
client_watch_session (client, session);
ctx->session = session;
}
+ if (pipelined_request_id) {
+ g_hash_table_insert (client->priv->pipelined_requests,
+ g_strdup (pipelined_request_id),
+ g_strdup (gst_rtsp_session_get_sessionid (session)));
+ }
+ rtsp_ctrl_timeout_remove (priv);
+
if (!klass->configure_client_media (client, media, stream, ctx))
goto configure_media_failed_no_reply;
if (!parse_transport (transport, stream, ct))
goto unsupported_transports;
+ if ((ct->mode_play
+ && !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY)) || (ct->mode_record
+ && !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD)))
+ goto unsupported_mode;
+
/* parse the keymgmt */
if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_KEYMGMT,
&keymgmt, 0) == GST_RTSP_OK) {
- if (!handle_keymgmt (client, ctx, keymgmt))
+ if (!gst_rtsp_stream_handle_keymgmt (ctx->stream, keymgmt))
goto keymgmt_error;
}
+ if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+ &accept_range, 0) == GST_RTSP_OK) {
+ GEnumValue *runit = NULL;
+ gint i;
+ gchar **valid_ranges;
+ GEnumClass *runit_class = g_type_class_ref (GST_TYPE_RTSP_RANGE_UNIT);
+
+ gst_rtsp_message_dump (ctx->request);
+ valid_ranges = g_strsplit (accept_range, ",", -1);
+
+ for (i = 0; valid_ranges[i]; i++) {
+ gchar *range = valid_ranges[i];
+
+ while (*range == ' ')
+ range++;
+
+ runit = g_enum_get_value_by_nick (runit_class, range);
+ if (runit)
+ break;
+ }
+ g_strfreev (valid_ranges);
+ g_type_class_unref (runit_class);
+
+ if (!runit)
+ goto unsupported_range_unit;
+ }
+
if (sessmedia == NULL) {
/* manage the media in our session now, if not done already */
- sessmedia = gst_rtsp_session_manage_media (session, path, media);
+ sessmedia =
+ gst_rtsp_session_manage_media (session, path, g_object_ref (media));
/* if we stil have no media, error */
if (sessmedia == NULL)
goto sessmedia_unavailable;
/* don't cache media anymore */
clean_cached_media (client, FALSE);
- } else {
- g_object_unref (media);
}
ctx->sessmedia = sessmedia;
g_hash_table_insert (priv->transports,
GINT_TO_POINTER (ct->interleaved.max), trans);
g_object_ref (trans);
+ add_data_seq (client, ct->interleaved.min);
+ add_data_seq (client, ct->interleaved.max);
}
/* 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);
trans_str);
g_free (trans_str);
+ if (pipelined_request_id)
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PIPELINED_REQUESTS,
+ pipelined_request_id);
+
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+ GstClockTimeDiff seekable = gst_rtsp_media_seekable (media);
+ GString *media_properties = g_string_new (NULL);
+
+ if (seekable == -1)
+ g_string_append (media_properties,
+ "No-Seeking,Time-Progressing,Time-Duration=0.0");
+ else if (seekable == 0)
+ g_string_append (media_properties, "Beginning-Only");
+ else if (seekable == G_MAXINT64)
+ g_string_append (media_properties, "Random-Access");
+ else
+ g_string_append_printf (media_properties,
+ "Random-Access=%f, Unlimited, Immutable",
+ (gdouble) seekable / GST_SECOND);
+
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_MEDIA_PROPERTIES,
+ g_string_free (media_properties, FALSE));
+ /* TODO Check how Accept-Ranges should be filled */
+ gst_rtsp_message_add_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+ "npt, clock, smpte, clock");
+ }
+
send_message (client, ctx, ctx->response, FALSE);
/* update the state */
gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY);
break;
}
+ g_object_unref (media);
g_object_unref (session);
g_free (path);
{
GST_ERROR ("client %p: media '%s' not found", client, path);
/* error reply is already sent */
- goto cleanup_path;
+ goto cleanup_session;
}
media_not_found:
{
GST_ERROR ("client %p: media '%s' not found", client, path);
send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
- goto cleanup_path;
+ goto cleanup_session;
}
control_not_found:
{
GST_ERROR ("client %p: no control in path '%s'", client, path);
send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
g_object_unref (media);
- goto cleanup_path;
+ goto cleanup_session;
}
stream_not_found:
{
GST_STR_NULL (control));
send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
g_object_unref (media);
+ goto cleanup_session;
+ }
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ g_object_unref (media);
goto cleanup_path;
}
service_unavailable:
GST_ERROR ("client %p: can't create session", client);
send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
g_object_unref (media);
- goto cleanup_path;
+ goto cleanup_session;
}
sessmedia_unavailable:
{
GST_ERROR ("client %p: can't create session media", client);
send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
- g_object_unref (media);
- goto cleanup_session;
+ goto cleanup_transport;
}
configure_media_failed_no_reply:
{
GST_ERROR ("client %p: configure_media failed", client);
+ g_object_unref (media);
/* error reply is already sent */
goto cleanup_session;
}
send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
goto cleanup_transport;
}
+unsupported_mode:
+ {
+ GST_ERROR ("client %p: unsupported mode (media play: %d, media record: %d, "
+ "mode play: %d, mode record: %d)", client,
+ ! !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY),
+ ! !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD), ct->mode_play, ct->mode_record);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_transport;
+ }
+unsupported_range_unit:
+ {
+ GST_ERROR ("Client %p: does not support any range format we support",
+ client);
+ send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
+ goto cleanup_transport;
+ }
keymgmt_error:
{
GST_ERROR ("client %p: keymgmt error", client);
{
cleanup_transport:
gst_rtsp_transport_free (ct);
+ if (media)
+ g_object_unref (media);
cleanup_session:
if (new_session)
gst_rtsp_session_pool_remove (priv->session_pool, session);
- g_object_unref (session);
+ if (session)
+ g_object_unref (session);
cleanup_path:
g_free (path);
return FALSE;
/* ERRORS */
no_sdp:
{
- GST_ERROR ("client %p: could not create SDP", client);
+ GST_ERROR ("client %p: could not create SDP", client);
+ gst_sdp_message_free (sdp);
+ return NULL;
+ }
+}
+
+/* for the describe we must generate an SDP */
+static gboolean
+handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx)
+{
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPResult res;
+ GstSDPMessage *sdp;
+ guint i;
+ gchar *path, *str;
+ GstRTSPMedia *media;
+ GstRTSPClientClass *klass;
+ GstRTSPStatusCode sig_result;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ /* check what kind of format is accepted, we don't really do anything with it
+ * and always return SDP for now. */
+ for (i = 0;; i++) {
+ gchar *accept;
+
+ res =
+ gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT,
+ &accept, i);
+ if (res == GST_RTSP_ENOTIMPL)
+ break;
+
+ if (g_ascii_strcasecmp (accept, "application/sdp") == 0)
+ break;
+ }
+
+ if (!priv->mount_points)
+ goto no_mount_points;
+
+ 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_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ goto unsupported_mode;
+
+ /* create an SDP for the media object on this client */
+ if (!(sdp = klass->create_sdp (client, media)))
+ goto no_sdp;
+
+ /* we suspend after the describe */
+ 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);
+
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_CONTENT_TYPE,
+ "application/sdp");
+
+ /* content base for some clients that might screw up creating the setup uri */
+ str = make_base_url (client, ctx->uri, path);
+ g_free (path);
+
+ GST_INFO ("adding content-base: %s", str);
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_CONTENT_BASE, str);
+
+ /* add SDP to the response body */
+ str = gst_sdp_message_as_text (sdp);
+ gst_rtsp_message_take_body (ctx->response, (guint8 *) str, strlen (str));
+ gst_sdp_message_free (sdp);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST],
+ 0, ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
+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;
+ }
+no_media:
+ {
+ GST_ERROR ("client %p: no media", client);
+ g_free (path);
+ /* error reply is already sent */
+ return FALSE;
+ }
+unsupported_mode:
+ {
+ GST_ERROR ("client %p: 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);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ g_free (path);
+ g_object_unref (media);
+ return FALSE;
+ }
+}
+
+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;
+ GstRTSPStatusCode sig_result;
+ guint i, n_streams;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ if (!priv->mount_points)
+ goto no_mount_points;
+
+ /* 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;
+
+ ctx->media = media;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD))
+ goto unsupported_mode;
+
+ /* Tell client subclass about the media */
+ if (!klass->handle_sdp (client, ctx, media, sdp))
+ goto unhandled_sdp;
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ n_streams = gst_rtsp_media_n_streams (media);
+ for (i = 0; i < n_streams; i++) {
+ GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
+ gchar *location =
+ g_strdup_printf ("rtsp://%s%s:8554/stream=%d", priv->server_ip, path,
+ i);
+ gchar *keymgmt = stream_make_keymgmt (client, location, stream);
+
+ if (keymgmt)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_KEYMGMT,
+ keymgmt);
+
+ g_free (location);
+ }
+
+ /* we suspend after the announce */
+ gst_rtsp_media_suspend (media);
+ g_object_unref (media);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST],
+ 0, ctx);
+
+ gst_sdp_message_free (sdp);
+ g_free (path);
+ 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);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+wrong_content_type:
+ {
+ GST_ERROR ("client %p: unknown content type", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+no_message:
+ {
+ GST_ERROR ("client %p: can't find SDP message", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ 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);
gst_sdp_message_free (sdp);
- return NULL;
+ return FALSE;
+ }
+no_media:
+ {
+ GST_ERROR ("client %p: no media", client);
+ g_free (path);
+ /* error reply is already sent */
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support ANNOUNCE", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ g_free (path);
+ g_object_unref (media);
+ gst_sdp_message_free (sdp);
+ 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);
+ gst_sdp_message_free (sdp);
+ return FALSE;
}
}
-/* for the describe we must generate an SDP */
static gboolean
-handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx)
+handle_record_request (GstRTSPClient * client, GstRTSPContext * ctx)
{
- GstRTSPClientPrivate *priv = client->priv;
- GstRTSPResult res;
- GstSDPMessage *sdp;
- guint i;
- gchar *path, *str;
- GstRTSPMedia *media;
+ GstRTSPSession *session;
GstRTSPClientClass *klass;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPUrl *uri;
+ GstRTSPState rtspstate;
+ gchar *path;
+ gint matched;
+ GstRTSPStatusCode sig_result;
+ GPtrArray *transports;
- klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ if (!(session = ctx->session))
+ goto no_session;
- if (!ctx->uri)
+ if (!(uri = ctx->uri))
goto no_uri;
- /* check what kind of format is accepted, we don't really do anything with it
- * and always return SDP for now. */
- for (i = 0;; i++) {
- gchar *accept;
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, uri);
- res =
- gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT,
- &accept, i);
- if (res == GST_RTSP_ENOTIMPL)
- break;
+ /* 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 (g_ascii_strcasecmp (accept, "application/sdp") == 0)
- break;
- }
+ if (path[matched] != '\0')
+ goto no_aggregate;
- if (!priv->mount_points)
- goto no_mount_points;
+ g_free (path);
- if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
- goto no_path;
+ ctx->sessmedia = sessmedia;
+ ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
- /* find the media object for the uri */
- if (!(media = find_media (client, ctx, path, NULL)))
- goto no_media;
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
- /* create an SDP for the media object on this client */
- if (!(sdp = klass->create_sdp (client, media)))
- goto no_sdp;
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD))
+ goto unsupported_mode;
- /* we suspend after the describe */
- gst_rtsp_media_suspend (media);
- g_object_unref (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;
- gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
- gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+ /* update the pipeline */
+ transports = gst_rtsp_session_media_get_transports (sessmedia);
+ if (!gst_rtsp_media_complete_pipeline (media, transports)) {
+ g_ptr_array_unref (transports);
+ goto pipeline_error;
+ }
+ g_ptr_array_unref (transports);
- gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_CONTENT_TYPE,
- "application/sdp");
+ /* in record we first unsuspend, media could be suspended from SDP or PAUSED */
+ if (!gst_rtsp_media_unsuspend (media))
+ goto unsuspend_failed;
- /* content base for some clients that might screw up creating the setup uri */
- str = make_base_url (client, ctx->uri, path);
- g_free (path);
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
- GST_INFO ("adding content-base: %s", str);
- gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_CONTENT_BASE, str);
+ send_message (client, ctx, ctx->response, FALSE);
- /* add SDP to the response body */
- str = gst_sdp_message_as_text (sdp);
- gst_rtsp_message_take_body (ctx->response, (guint8 *) str, strlen (str));
- gst_sdp_message_free (sdp);
+ /* start playing after sending the response */
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);
- send_message (client, ctx, ctx->response, FALSE);
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);
- g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST],
- 0, ctx);
+ 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", client);
+ GST_ERROR ("client %p: no uri supplied", client);
send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
return FALSE;
}
-no_mount_points:
+not_found:
{
- GST_ERROR ("client %p: no mount points configured", client);
+ GST_ERROR ("client %p: media not found", client);
send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
return FALSE;
}
-no_path:
+no_aggregate:
{
- GST_ERROR ("client %p: can't find path for url", client);
- send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ 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;
}
-no_media:
+sig_failed:
{
- GST_ERROR ("client %p: no media", client);
- g_free (path);
- /* error reply is already sent */
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
return FALSE;
}
-no_sdp:
+unsupported_mode:
{
- GST_ERROR ("client %p: can't create SDP", client);
+ GST_ERROR ("client %p: 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;
+ }
+pipeline_error:
+ {
+ GST_ERROR ("client %p: failed to configure the pipeline", 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);
- g_free (path);
- g_object_unref (media);
return FALSE;
}
}
static gboolean
-handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
+handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPVersion version)
{
GstRTSPMethod options;
gchar *str;
+ GstRTSPStatusCode sig_result;
options = GST_RTSP_DESCRIBE |
GST_RTSP_OPTIONS |
GST_RTSP_SETUP |
GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN;
+ if (version < GST_RTSP_VERSION_2_0) {
+ options |= GST_RTSP_RECORD;
+ options |= GST_RTSP_ANNOUNCE;
+ }
+
str = gst_rtsp_options_as_text (options);
gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PUBLIC, str);
g_free (str);
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
send_message (client, ctx, ctx->response, FALSE);
g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST],
0, ctx);
return TRUE;
+
+/* ERRORS */
+sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_message_free (ctx->response);
+ return FALSE;
+ }
}
/* remove duplicate and trailing '/' */
GST_INFO ("client %p: session %p removed", client, session);
g_mutex_lock (&priv->lock);
- if (priv->watch != NULL)
- gst_rtsp_watch_set_send_backlog (priv->watch, 0, 0);
client_unwatch_session (client, session, NULL);
- if (priv->watch != NULL)
- gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
g_mutex_unlock (&priv->lock);
}
-/* Returns TRUE if there are no Require headers, otherwise returns FALSE
- * and also returns a newly-allocated string of (comma-separated) unsupported
- * options in the unsupported_reqs variable .
+/* Check for Require headers. Returns TRUE if there are no Require headers,
+ * otherwise lets the application decide which headers are supported.
+ * By default all headers are unsupported.
+ * If there are unsupported options, FALSE will be returned together with
+ * a newly-allocated string of (comma-separated) unsupported options in
+ * the unsupported_reqs variable.
*
* There may be multiple Require headers, but we must send one single
* Unsupported header with all the unsupported options as response. If
* an incoming Require header contained a comma-separated list of options
* GstRtspConnection will already have split that list up into multiple
* headers.
- *
- * TODO: allow the application to decide what features are supported
*/
static gboolean
-check_request_requirements (GstRTSPMessage * msg, gchar ** unsupported_reqs)
+check_request_requirements (GstRTSPContext * ctx, gchar ** unsupported_reqs)
{
GstRTSPResult res;
GPtrArray *arr = NULL;
+ GstRTSPMessage *msg = ctx->request;
gchar *reqs = NULL;
gint i;
+ gchar *sig_result = NULL;
+ gboolean result = TRUE;
i = 0;
do {
/* otherwise we've now processed at all the Require headers */
g_ptr_array_add (arr, NULL);
- /* for now we don't commit to supporting anything, so will just report
- * all of the required options as unsupported */
- *unsupported_reqs = g_strjoinv (", ", (gchar **) arr->pdata);
+ g_signal_emit (ctx->client,
+ gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS], 0, ctx,
+ (gchar **) arr->pdata, &sig_result);
+
+ if (sig_result == NULL) {
+ /* no supported options, just report all of the required ones as
+ * unsupported */
+ *unsupported_reqs = g_strjoinv (", ", (gchar **) arr->pdata);
+ result = FALSE;
+ goto done;
+ }
+
+ if (strlen (sig_result) == 0)
+ g_free (sig_result);
+ else {
+ *unsupported_reqs = sig_result;
+ result = FALSE;
+ }
+done:
g_ptr_array_unref (arr);
- return FALSE;
+ return result;
}
static void
GstRTSPContext sctx = { NULL }, *ctx;
GstRTSPMessage response = { 0 };
gchar *unsupported_reqs = NULL;
- gchar *sessid;
+ gchar *sessid = NULL, *pipelined_request_id = NULL;
if (!(ctx = gst_rtsp_context_get_current ())) {
ctx = &sctx;
gst_rtsp_version_as_text (version));
/* we can only handle 1.0 requests */
- if (version != GST_RTSP_VERSION_1_0)
+ if (version != GST_RTSP_VERSION_1_0 && version != GST_RTSP_VERSION_2_0)
goto not_supported;
ctx->method = method;
}
/* get the session if there is any */
- res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);
+ res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_PIPELINED_REQUESTS,
+ &pipelined_request_id, 0);
+ if (res == GST_RTSP_OK) {
+ sessid = g_hash_table_lookup (client->priv->pipelined_requests,
+ pipelined_request_id);
+
+ if (!sessid)
+ res = GST_RTSP_ERROR;
+ }
+
+ if (res != GST_RTSP_OK)
+ res =
+ gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);
+
if (res == GST_RTSP_OK) {
if (priv->session_pool == NULL)
goto no_pool;
goto not_authorized;
/* handle any 'Require' headers */
- if (!check_request_requirements (ctx->request, &unsupported_reqs))
+ if (!check_request_requirements (ctx, &unsupported_reqs))
goto unsupported_requirement;
- /* the backlog must be unlimited while processing requests.
- * the causes of this are two cases of deadlocks while streaming over TCP:
- *
- * 1. consider the scenario where the media pipeline's streaming thread
- * is blocking in the appsink (taking the appsink's preroll lock) because
- * the backlog is full. when a PAUSE request is received by the RTSP
- * client thread then the the state of the session media ought to change
- * to PAUSED. while most elements in the pipeline can change state this
- * can never happen for the appsink since its preroll lock is taken by
- * another thread.
- *
- * 2. consider the scenario where the media pipeline's streaming thread
- * is blocking in the appsink new_sample callback (taking the send lock
- * in RTSP client) because the backlog is full. when e.g. a GET request
- * is received by the RTSP client thread then a response ought to be sent
- * but this can never happen since it requires taking the send lock
- * already taken by another thread.
- *
- * the reason that the backlog is never emptied is that the source used
- * for dequeing messages from the backlog is never dispatched because it
- * is attached to the same mainloop as the source receving RTSP requests and
- * therefore run by the RTSP client thread which is alreayd blocking.
- *
- * without significant changes the easiest way to cope with this is to
- * not block indefinitely when the backlog is full, but rather let the
- * backlog grow in size. this in effect means that there can not be any
- * upper boundary on its size.
- */
- if (priv->watch != NULL)
- gst_rtsp_watch_set_send_backlog (priv->watch, 0, 0);
-
/* now see what is asked and dispatch to a dedicated handler */
switch (method) {
case GST_RTSP_OPTIONS:
- handle_options_request (client, ctx);
+ priv->version = version;
+ handle_options_request (client, ctx, version);
break;
case GST_RTSP_DESCRIBE:
handle_describe_request (client, ctx);
handle_get_param_request (client, ctx);
break;
case GST_RTSP_ANNOUNCE:
+ if (version >= GST_RTSP_VERSION_2_0)
+ goto invalid_command_for_version;
+ handle_announce_request (client, ctx);
+ break;
case GST_RTSP_RECORD:
+ if (version >= GST_RTSP_VERSION_2_0)
+ goto invalid_command_for_version;
+ 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);
goto not_implemented;
case GST_RTSP_INVALID:
default:
- if (priv->watch != NULL)
- gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
goto bad_request;
}
- if (priv->watch != NULL)
- gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
-
done:
if (ctx == &sctx)
gst_rtsp_context_pop_current (ctx);
ctx);
goto done;
}
+invalid_command_for_version:
+ {
+ GST_ERROR ("client %p: invalid command for version", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ goto done;
+ }
bad_request:
{
GST_ERROR ("client %p: bad request", client);
gst_rtsp_message_steal_body (message, &data, &size);
- /* Strip trailing \0 */
- buffer = gst_buffer_new_wrapped (data, size - 1);
+ /* Strip trailing \0 (which GstRTSPConnection adds) */
+ --size;
+
+ buffer = gst_buffer_new_wrapped (data, size);
trans =
g_hash_table_lookup (priv->transports, GINT_TO_POINTER ((gint) channel));
if (trans) {
+ GSocketAddress *addr;
+
+ /* Only create the socket address once for the transport, we don't really
+ * want to do that for every single packet.
+ *
+ * The netaddress meta is later used by the RTP stack to know where
+ * packets came from and allows us to match it again to a stream transport
+ *
+ * In theory we could use the remote socket address of the RTSP connection
+ * here, but this would fail with a custom configure_client_transport()
+ * implementation.
+ */
+ if (!(addr =
+ g_object_get_data (G_OBJECT (trans), "rtsp-client.remote-addr"))) {
+ const GstRTSPTransport *tr;
+ GInetAddress *iaddr;
+
+ tr = gst_rtsp_stream_transport_get_transport (trans);
+ iaddr = g_inet_address_new_from_string (tr->destination);
+ if (iaddr) {
+ addr = g_inet_socket_address_new (iaddr, tr->client_port.min);
+ g_object_unref (iaddr);
+ g_object_set_data_full (G_OBJECT (trans), "rtsp-client.remote-addr",
+ addr, (GDestroyNotify) g_object_unref);
+ }
+ }
+
+ if (addr) {
+ gst_buffer_add_net_address_meta (buffer, addr);
+ }
+
/* dispatch to the stream based on the channel number */
+ GST_LOG_OBJECT (client, "%u bytes of data on channel %u", size, channel);
gst_rtsp_stream_transport_recv_data (trans, channel, buffer);
} else {
+ GST_DEBUG_OBJECT (client, "received %u bytes of data for "
+ "unknown channel %u", size, channel);
gst_buffer_unref (buffer);
}
/**
* gst_rtsp_client_set_session_pool:
* @client: a #GstRTSPClient
- * @pool: (transfer none): a #GstRTSPSessionPool
+ * @pool: (transfer none) (nullable): a #GstRTSPSessionPool
*
* Set @pool as the sessionpool for @client which it will use to find
* or allocate sessions. the sessionpool is usually inherited from the server
*
* Get the #GstRTSPSessionPool object that @client uses to manage its sessions.
*
- * Returns: (transfer full): a #GstRTSPSessionPool, unref after usage.
+ * Returns: (transfer full) (nullable): a #GstRTSPSessionPool, unref after usage.
*/
GstRTSPSessionPool *
gst_rtsp_client_get_session_pool (GstRTSPClient * client)
/**
* gst_rtsp_client_set_mount_points:
* @client: a #GstRTSPClient
- * @mounts: (transfer none): a #GstRTSPMountPoints
+ * @mounts: (transfer none) (nullable): a #GstRTSPMountPoints
*
* Set @mounts as the mount points for @client which it will use to map urls
* to media streams. These mount points are usually inherited from the server that
*
* Get the #GstRTSPMountPoints object that @client uses to manage its sessions.
*
- * Returns: (transfer full): a #GstRTSPMountPoints, unref after usage.
+ * Returns: (transfer full) (nullable): a #GstRTSPMountPoints, unref after usage.
*/
GstRTSPMountPoints *
gst_rtsp_client_get_mount_points (GstRTSPClient * client)
/**
* gst_rtsp_client_set_auth:
* @client: a #GstRTSPClient
- * @auth: (transfer none): a #GstRTSPAuth
+ * @auth: (transfer none) (nullable): a #GstRTSPAuth
*
* configure @auth to be used as the authentication manager of @client.
*/
*
* Get the #GstRTSPAuth used as the authentication manager of @client.
*
- * Returns: (transfer full): the #GstRTSPAuth of @client. g_object_unref() after
- * usage.
+ * Returns: (transfer full) (nullable): the #GstRTSPAuth of @client.
+ * g_object_unref() after usage.
*/
GstRTSPAuth *
gst_rtsp_client_get_auth (GstRTSPClient * client)
/**
* gst_rtsp_client_set_thread_pool:
* @client: a #GstRTSPClient
- * @pool: (transfer none): a #GstRTSPThreadPool
+ * @pool: (transfer none) (nullable): a #GstRTSPThreadPool
*
* configure @pool to be used as the thread pool of @client.
*/
*
* Get the #GstRTSPThreadPool used as the thread pool of @client.
*
- * Returns: (transfer full): the #GstRTSPThreadPool of @client. g_object_unref() after
+ * Returns: (transfer full) (nullable): the #GstRTSPThreadPool of @client. g_object_unref() after
* usage.
*/
GstRTSPThreadPool *
*
* Get the #GstRTSPConnection of @client.
*
- * Returns: (transfer none): the #GstRTSPConnection of @client.
+ * Returns: (transfer none) (nullable): the #GstRTSPConnection of @client.
* The connection object returned remains valid until the client is freed.
*/
GstRTSPConnection *
return GST_RTSP_OK;
}
-static GstRTSPResult
+static gboolean
do_send_message (GstRTSPClient * client, GstRTSPMessage * message,
gboolean close, gpointer user_data)
{
GstRTSPClientPrivate *priv = client->priv;
+ guint id = 0;
GstRTSPResult ret;
- GTimeVal time;
- time.tv_sec = 1;
- time.tv_usec = 0;
+ /* send the message */
+ ret = gst_rtsp_watch_send_message (priv->watch, message, &id);
+ if (ret != GST_RTSP_OK)
+ goto error;
- do {
- /* send the response and store the seq number so we can wait until it's
- * written to the client to close the connection */
- ret =
- gst_rtsp_watch_send_message (priv->watch, message,
- close ? &priv->close_seq : NULL);
- if (ret == GST_RTSP_OK)
- break;
+ /* if close flag is set, store the seq number so we can wait until it's
+ * written to the client to close the connection */
+ if (close)
+ priv->close_seq = id;
- if (ret != GST_RTSP_ENOMEM)
- goto error;
+ if (gst_rtsp_message_get_type (message) == GST_RTSP_MESSAGE_DATA) {
+ guint8 channel = 0;
+ GstRTSPResult r;
- /* drop backlog */
- if (priv->drop_backlog)
- break;
+ r = gst_rtsp_message_parse_data (message, &channel);
+ if (r != GST_RTSP_OK) {
+ ret = r;
+ goto error;
+ }
- /* queue was full, wait for more space */
- GST_DEBUG_OBJECT (client, "waiting for backlog");
- ret = gst_rtsp_watch_wait_backlog (priv->watch, &time);
- GST_DEBUG_OBJECT (client, "Resend due to backlog full");
- } while (ret != GST_RTSP_EINTR);
+ /* check if the message has been queued for transmission in watch */
+ if (id) {
+ /* store the seq number so we can wait until it has been sent */
+ GST_DEBUG_OBJECT (client, "wait for message %d, channel %d", id, channel);
+ set_data_seq (client, channel, id);
+ } else {
+ GstRTSPStreamTransport *trans;
+
+ trans =
+ g_hash_table_lookup (priv->transports,
+ GINT_TO_POINTER ((gint) channel));
+ if (trans) {
+ GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
+ g_mutex_unlock (&priv->send_lock);
+ gst_rtsp_stream_transport_message_sent (trans);
+ g_mutex_lock (&priv->send_lock);
+ }
+ }
+ }
- return ret;
+ return ret == GST_RTSP_OK;
/* ERRORS */
error:
{
GST_DEBUG_OBJECT (client, "got error %d", ret);
- return ret;
+ return FALSE;
}
}
{
GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPStreamTransport *trans = NULL;
+ guint8 channel = 0;
+ gboolean close = FALSE;
+
+ g_mutex_lock (&priv->send_lock);
+
+ if (get_data_channel (client, cseq, &channel)) {
+ trans = g_hash_table_lookup (priv->transports, GINT_TO_POINTER (channel));
+ set_data_seq (client, channel, 0);
+ }
if (priv->close_seq && priv->close_seq == cseq) {
GST_INFO ("client %p: send close message", client);
+ close = TRUE;
priv->close_seq = 0;
- gst_rtsp_client_close (client);
}
+ g_mutex_unlock (&priv->send_lock);
+
+ if (trans) {
+ GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
+ gst_rtsp_stream_transport_message_sent (trans);
+ }
+
+ if (close)
+ gst_rtsp_client_close (client);
+
return GST_RTSP_OK;
}
return GST_RTSP_OK;
}
-static gboolean
+static GstRTSPStatusCode
handle_tunnel (GstRTSPClient * client)
{
GstRTSPClientPrivate *priv = client->priv;
g_mutex_lock (&opriv->watch_lock);
if (opriv->watch == NULL)
goto tunnel_closed;
+ if (opriv->tstate == priv->tstate)
+ goto tunnel_duplicate_id;
GST_INFO ("client %p: found previous tunnel %p (old %p, new %p)", client,
oclient, opriv->connection, priv->connection);
gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
}
- return TRUE;
+ return GST_RTSP_STS_OK;
/* ERRORS */
no_tunnelid:
{
GST_ERROR ("client %p: no tunnelid provided", client);
- return FALSE;
+ return GST_RTSP_STS_SERVICE_UNAVAILABLE;
}
tunnel_closed:
{
GST_ERROR ("client %p: tunnel session %s was closed", client, tunnelid);
g_mutex_unlock (&opriv->watch_lock);
g_object_unref (oclient);
- return FALSE;
+ return GST_RTSP_STS_SERVICE_UNAVAILABLE;
+ }
+tunnel_duplicate_id:
+ {
+ GST_ERROR ("client %p: tunnel session %s was duplicate", client, tunnelid);
+ g_mutex_unlock (&opriv->watch_lock);
+ g_object_unref (oclient);
+ return GST_RTSP_STS_BAD_REQUEST;
}
}
GST_INFO ("client %p: tunnel get (connection %p)", client,
client->priv->connection);
- if (!handle_tunnel (client)) {
- return GST_RTSP_STS_SERVICE_UNAVAILABLE;
- }
+ g_mutex_lock (&client->priv->lock);
+ client->priv->tstate = TUNNEL_STATE_GET;
+ g_mutex_unlock (&client->priv->lock);
- return GST_RTSP_STS_OK;
+ return handle_tunnel (client);
}
static GstRTSPResult
GST_INFO ("client %p: tunnel post (connection %p)", client,
client->priv->connection);
- if (!handle_tunnel (client)) {
+ g_mutex_lock (&client->priv->lock);
+ client->priv->tstate = TUNNEL_STATE_POST;
+ g_mutex_unlock (&client->priv->lock);
+
+ if (handle_tunnel (client) != GST_RTSP_STS_OK)
return GST_RTSP_ERROR;
- }
return GST_RTSP_OK;
}
client_watch_notify (GstRTSPClient * client)
{
GstRTSPClientPrivate *priv = client->priv;
+ gboolean closed = TRUE;
GST_INFO ("client %p: watch destroyed", client);
priv->watch = NULL;
- /* remove all sessions and so drop the extra client ref */
- gst_rtsp_client_session_filter (client, cleanup_session, NULL);
- g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_CLOSED], 0, NULL);
+ /* remove all sessions if the media says so and so drop the extra client ref */
+ rtsp_ctrl_timeout_remove (priv);
+ gst_rtsp_client_session_filter (client, cleanup_session, &closed);
+ if (closed)
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_CLOSED], 0, NULL);
g_object_unref (client);
}
gst_rtsp_client_attach (GstRTSPClient * client, GMainContext * context)
{
GstRTSPClientPrivate *priv;
+ GSource *timer_src;
guint res;
g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), 0);
GST_INFO ("client %p: attaching to context %p", client, context);
res = gst_rtsp_watch_attach (priv->watch, context);
+ /* Setting up a timeout for the RTSP control channel until a session
+ * is up where it is handling timeouts. */
+ rtsp_ctrl_timeout_remove (priv); /* removing old if any */
+ g_mutex_lock (&priv->lock);
+
+ timer_src = g_timeout_source_new_seconds (RTSP_CTRL_CB_INTERVAL);
+ g_source_set_callback (timer_src, rtsp_ctrl_timeout_cb, client, NULL);
+ priv->rtsp_ctrl_timeout_id = g_source_attach (timer_src, priv->watch_context);
+ g_source_unref (timer_src);
+ GST_DEBUG ("rtsp control setting up session timeout id=%u.",
+ priv->rtsp_ctrl_timeout_id);
+
+ g_mutex_unlock (&priv->lock);
+
return res;
}