#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,
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;
/**
* GstRTSPClient::pre-options-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-describe-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-setup-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-play-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-pause-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-teardown-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-set-parameter-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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,
/**
* GstRTSPClient::pre-get-parameter-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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,
/**
* GstRTSPClient::pre-announce-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::pre-record-request:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
*
* Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
* otherwise an appropriate return code
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),
/**
* GstRTSPClient::check-requirements:
* @client: a #GstRTSPClient
- * @ctx: a #GstRTSPContext
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
* @arr: a NULL-terminated array of strings
*
* Returns: a newly allocated string with comma-separated list of
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
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);
}
}
+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;
}
/**
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 */
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 */
/* 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;
}
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;
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;
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 (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);
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);
/* 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;
}
}
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)
+static gchar *
+stream_make_keymgmt (GstRTSPClient * client, const gchar * location,
+ GstRTSPStream * stream)
{
- GstMIKEYMessage *msg;
- guint i, n_cs;
- GstCaps *caps = NULL;
- GstMIKEYPayloadKEMAC *kemac;
- const GstMIKEYPayloadKeyData *pkd;
+ 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
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 (!(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;
/* 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 =
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 */
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 */
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);
guint8 *data;
guint size;
GstRTSPStatusCode sig_result;
+ guint i, n_streams;
klass = GST_RTSP_CLIENT_GET_CLASS (client);
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);
- gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
- gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
-
send_message (client, ctx, ctx->response, FALSE);
g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST],
gchar *path;
gint matched;
GstRTSPStatusCode sig_result;
+ GPtrArray *transports;
if (!(session = ctx->session))
goto no_session;
if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
goto invalid_state;
- /* in play we first unsuspend, media could be suspended from SDP or PAUSED */
+ /* 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 record we first unsuspend, media could be suspended from SDP or PAUSED */
if (!gst_rtsp_media_unsuspend (media))
goto unsuspend_failed;
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);
}
static gboolean
-handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx)
+handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPVersion version)
{
GstRTSPMethod options;
gchar *str;
GST_RTSP_OPTIONS |
GST_RTSP_PAUSE |
GST_RTSP_PLAY |
- GST_RTSP_RECORD | GST_RTSP_ANNOUNCE |
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_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);
}
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;
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);
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);
/**
* 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;
}
GST_INFO ("client %p: watch destroyed", client);
priv->watch = 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);
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;
}