mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-01 06:01:04 +00:00
620 lines
16 KiB
C
620 lines
16 KiB
C
/* GStreamer
|
|
* Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
#include <string.h>
|
|
|
|
#include "rtsp-session.h"
|
|
|
|
#undef DEBUG
|
|
|
|
#define DEFAULT_TIMEOUT 60
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_SESSIONID,
|
|
PROP_TIMEOUT,
|
|
PROP_LAST
|
|
};
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
|
|
#define GST_CAT_DEFAULT rtsp_session_debug
|
|
|
|
static void gst_rtsp_session_get_property (GObject * object, guint propid,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_session_set_property (GObject * object, guint propid,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_rtsp_session_finalize (GObject * obj);
|
|
|
|
G_DEFINE_TYPE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT);
|
|
|
|
static void
|
|
gst_rtsp_session_class_init (GstRTSPSessionClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->get_property = gst_rtsp_session_get_property;
|
|
gobject_class->set_property = gst_rtsp_session_set_property;
|
|
gobject_class->finalize = gst_rtsp_session_finalize;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SESSIONID,
|
|
g_param_spec_string ("sessionid", "Sessionid", "the session id",
|
|
NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TIMEOUT,
|
|
g_param_spec_uint ("timeout", "timeout",
|
|
"the timeout of the session (0 = never)", 0, G_MAXUINT,
|
|
DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsession", 0,
|
|
"GstRTSPSession");
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_init (GstRTSPSession * session)
|
|
{
|
|
session->timeout = DEFAULT_TIMEOUT;
|
|
g_get_current_time (&session->create_time);
|
|
gst_rtsp_session_touch (session);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_free_stream (GstRTSPSessionStream * stream)
|
|
{
|
|
GST_INFO ("free session stream %p", stream);
|
|
|
|
/* remove callbacks now */
|
|
gst_rtsp_session_stream_set_callbacks (stream, NULL, NULL, NULL, NULL, NULL,
|
|
NULL);
|
|
gst_rtsp_session_stream_set_keepalive (stream, NULL, NULL, NULL);
|
|
|
|
gst_rtsp_media_trans_cleanup (&stream->trans);
|
|
|
|
g_free (stream);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_free_media (GstRTSPSessionMedia * media,
|
|
GstRTSPSession * session)
|
|
{
|
|
guint size, i;
|
|
|
|
size = media->streams->len;
|
|
|
|
GST_INFO ("free session media %p", media);
|
|
|
|
gst_rtsp_session_media_set_state (media, GST_STATE_NULL);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
GstRTSPSessionStream *stream;
|
|
|
|
stream = g_array_index (media->streams, GstRTSPSessionStream *, i);
|
|
|
|
if (stream)
|
|
gst_rtsp_session_free_stream (stream);
|
|
}
|
|
g_array_free (media->streams, TRUE);
|
|
|
|
if (media->url)
|
|
gst_rtsp_url_free (media->url);
|
|
|
|
if (media->media)
|
|
g_object_unref (media->media);
|
|
|
|
g_free (media);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_finalize (GObject * obj)
|
|
{
|
|
GstRTSPSession *session;
|
|
|
|
session = GST_RTSP_SESSION (obj);
|
|
|
|
GST_INFO ("finalize session %p", session);
|
|
|
|
/* free all media */
|
|
g_list_foreach (session->medias, (GFunc) gst_rtsp_session_free_media,
|
|
session);
|
|
g_list_free (session->medias);
|
|
|
|
/* free session id */
|
|
g_free (session->sessionid);
|
|
|
|
G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_get_property (GObject * object, guint propid,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPSession *session = GST_RTSP_SESSION (object);
|
|
|
|
switch (propid) {
|
|
case PROP_SESSIONID:
|
|
g_value_set_string (value, session->sessionid);
|
|
break;
|
|
case PROP_TIMEOUT:
|
|
g_value_set_uint (value, gst_rtsp_session_get_timeout (session));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtsp_session_set_property (GObject * object, guint propid,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTSPSession *session = GST_RTSP_SESSION (object);
|
|
|
|
switch (propid) {
|
|
case PROP_SESSIONID:
|
|
g_free (session->sessionid);
|
|
session->sessionid = g_value_dup_string (value);
|
|
break;
|
|
case PROP_TIMEOUT:
|
|
gst_rtsp_session_set_timeout (session, g_value_get_uint (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_manage_media:
|
|
* @sess: a #GstRTSPSession
|
|
* @uri: the uri for the media
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Manage the media object @obj in @sess. @uri will be used to retrieve this
|
|
* media from the session with gst_rtsp_session_get_media().
|
|
*
|
|
* Ownership is taken from @media.
|
|
*
|
|
* Returns: a new @GstRTSPSessionMedia object.
|
|
*/
|
|
GstRTSPSessionMedia *
|
|
gst_rtsp_session_manage_media (GstRTSPSession * sess, const GstRTSPUrl * uri,
|
|
GstRTSPMedia * media)
|
|
{
|
|
GstRTSPSessionMedia *result;
|
|
guint n_streams;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
|
|
g_return_val_if_fail (media->status == GST_RTSP_MEDIA_STATUS_PREPARED, NULL);
|
|
|
|
result = g_new0 (GstRTSPSessionMedia, 1);
|
|
result->media = media;
|
|
result->url = gst_rtsp_url_copy ((GstRTSPUrl *) uri);
|
|
result->state = GST_RTSP_STATE_INIT;
|
|
|
|
/* prealloc the streams now, filled with NULL */
|
|
n_streams = gst_rtsp_media_n_streams (media);
|
|
result->streams =
|
|
g_array_sized_new (FALSE, TRUE, sizeof (GstRTSPSessionStream *),
|
|
n_streams);
|
|
g_array_set_size (result->streams, n_streams);
|
|
|
|
sess->medias = g_list_prepend (sess->medias, result);
|
|
|
|
GST_INFO ("manage new media %p in session %p", media, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_release_media:
|
|
* @sess: a #GstRTSPSession
|
|
* @media: a #GstRTSPMedia
|
|
*
|
|
* Release the managed @media in @sess, freeing the memory allocated by it.
|
|
*
|
|
* Returns: %TRUE if there are more media session left in @sess.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_session_release_media (GstRTSPSession * sess,
|
|
GstRTSPSessionMedia * media)
|
|
{
|
|
GList *walk, *next;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
|
|
g_return_val_if_fail (media != NULL, FALSE);
|
|
|
|
for (walk = sess->medias; walk;) {
|
|
GstRTSPSessionMedia *find;
|
|
|
|
find = (GstRTSPSessionMedia *) walk->data;
|
|
next = g_list_next (walk);
|
|
|
|
if (find == media) {
|
|
sess->medias = g_list_delete_link (sess->medias, walk);
|
|
|
|
gst_rtsp_session_free_media (find, sess);
|
|
break;
|
|
}
|
|
walk = next;
|
|
}
|
|
return (sess->medias != NULL);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_get_media:
|
|
* @sess: a #GstRTSPSession
|
|
* @url: the url for the media
|
|
*
|
|
* Get the session media of the @url.
|
|
*
|
|
* Returns: the configuration for @url in @sess.
|
|
*/
|
|
GstRTSPSessionMedia *
|
|
gst_rtsp_session_get_media (GstRTSPSession * sess, const GstRTSPUrl * url)
|
|
{
|
|
GstRTSPSessionMedia *result;
|
|
GList *walk;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
|
|
g_return_val_if_fail (url != NULL, NULL);
|
|
|
|
result = NULL;
|
|
|
|
for (walk = sess->medias; walk; walk = g_list_next (walk)) {
|
|
result = (GstRTSPSessionMedia *) walk->data;
|
|
|
|
if (strcmp (result->url->abspath, url->abspath) == 0)
|
|
break;
|
|
|
|
result = NULL;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_media_get_stream:
|
|
* @media: a #GstRTSPSessionMedia
|
|
* @idx: the stream index
|
|
*
|
|
* Get a previously created or create a new #GstRTSPSessionStream at @idx.
|
|
*
|
|
* Returns: a #GstRTSPSessionStream that is valid until the session of @media
|
|
* is unreffed.
|
|
*/
|
|
GstRTSPSessionStream *
|
|
gst_rtsp_session_media_get_stream (GstRTSPSessionMedia * media, guint idx)
|
|
{
|
|
GstRTSPSessionStream *result;
|
|
|
|
g_return_val_if_fail (media != NULL, NULL);
|
|
g_return_val_if_fail (media->media != NULL, NULL);
|
|
|
|
if (idx >= media->streams->len)
|
|
return NULL;
|
|
|
|
result = g_array_index (media->streams, GstRTSPSessionStream *, idx);
|
|
if (result == NULL) {
|
|
GstRTSPMediaStream *media_stream;
|
|
|
|
media_stream = gst_rtsp_media_get_stream (media->media, idx);
|
|
if (media_stream == NULL)
|
|
goto no_media;
|
|
|
|
result = g_new0 (GstRTSPSessionStream, 1);
|
|
result->trans.idx = idx;
|
|
result->trans.transport = NULL;
|
|
result->media_stream = media_stream;
|
|
|
|
g_array_index (media->streams, GstRTSPSessionStream *, idx) = result;
|
|
}
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
no_media:
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_rtsp_session_media_alloc_channels (GstRTSPSessionMedia * media,
|
|
GstRTSPRange * range)
|
|
{
|
|
range->min = media->counter++;
|
|
range->max = media->counter++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_new:
|
|
*
|
|
* Create a new #GstRTSPSession instance.
|
|
*/
|
|
GstRTSPSession *
|
|
gst_rtsp_session_new (const gchar * sessionid)
|
|
{
|
|
GstRTSPSession *result;
|
|
|
|
g_return_val_if_fail (sessionid != NULL, NULL);
|
|
|
|
result = g_object_new (GST_TYPE_RTSP_SESSION, "sessionid", sessionid, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_get_sessionid:
|
|
* @session: a #GstRTSPSession
|
|
*
|
|
* Get the sessionid of @session.
|
|
*
|
|
* Returns: the sessionid of @session. The value remains valid as long as
|
|
* @session is alive.
|
|
*/
|
|
const gchar *
|
|
gst_rtsp_session_get_sessionid (GstRTSPSession * session)
|
|
{
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
|
|
|
|
return session->sessionid;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_set_timeout:
|
|
* @session: a #GstRTSPSession
|
|
* @timeout: the new timeout
|
|
*
|
|
* Configure @session for a timeout of @timeout seconds. The session will be
|
|
* cleaned up when there is no activity for @timeout seconds.
|
|
*/
|
|
void
|
|
gst_rtsp_session_set_timeout (GstRTSPSession * session, guint timeout)
|
|
{
|
|
g_return_if_fail (GST_IS_RTSP_SESSION (session));
|
|
|
|
session->timeout = timeout;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_get_timeout:
|
|
* @session: a #GstRTSPSession
|
|
*
|
|
* Get the timeout value of @session.
|
|
*
|
|
* Returns: the timeout of @session in seconds.
|
|
*/
|
|
guint
|
|
gst_rtsp_session_get_timeout (GstRTSPSession * session)
|
|
{
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (session), 0);
|
|
|
|
return session->timeout;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_touch:
|
|
* @session: a #GstRTSPSession
|
|
*
|
|
* Update the last_access time of the session to the current time.
|
|
*/
|
|
void
|
|
gst_rtsp_session_touch (GstRTSPSession * session)
|
|
{
|
|
g_return_if_fail (GST_IS_RTSP_SESSION (session));
|
|
|
|
g_get_current_time (&session->last_access);
|
|
}
|
|
|
|
void
|
|
gst_rtsp_session_prevent_expire (GstRTSPSession * session)
|
|
{
|
|
g_return_if_fail (GST_IS_RTSP_SESSION (session));
|
|
|
|
g_atomic_int_add (&session->expire_count, 1);
|
|
}
|
|
|
|
void
|
|
gst_rtsp_session_allow_expire (GstRTSPSession * session)
|
|
{
|
|
g_atomic_int_add (&session->expire_count, -1);
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_next_timeout:
|
|
* @session: a #GstRTSPSession
|
|
* @now: the current system time
|
|
*
|
|
* Get the amount of milliseconds till the session will expire.
|
|
*
|
|
* Returns: the amount of milliseconds since the session will time out.
|
|
*/
|
|
gint
|
|
gst_rtsp_session_next_timeout (GstRTSPSession * session, GTimeVal * now)
|
|
{
|
|
gint res;
|
|
GstClockTime last_access, now_ns;
|
|
|
|
g_return_val_if_fail (GST_IS_RTSP_SESSION (session), -1);
|
|
g_return_val_if_fail (now != NULL, -1);
|
|
|
|
if (g_atomic_int_get (&session->expire_count) != 0) {
|
|
/* touch session when the expire count is not 0 */
|
|
g_get_current_time (&session->last_access);
|
|
}
|
|
|
|
last_access = GST_TIMEVAL_TO_TIME (session->last_access);
|
|
/* add timeout allow for 5 seconds of extra time */
|
|
last_access += session->timeout * GST_SECOND + (5 * GST_SECOND);
|
|
|
|
now_ns = GST_TIMEVAL_TO_TIME (*now);
|
|
|
|
if (last_access > now_ns)
|
|
res = GST_TIME_AS_MSECONDS (last_access - now_ns);
|
|
else
|
|
res = 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_is_expired:
|
|
* @session: a #GstRTSPSession
|
|
* @now: the current system time
|
|
*
|
|
* Check if @session timeout out.
|
|
*
|
|
* Returns: %TRUE if @session timed out
|
|
*/
|
|
gboolean
|
|
gst_rtsp_session_is_expired (GstRTSPSession * session, GTimeVal * now)
|
|
{
|
|
gboolean res;
|
|
|
|
res = (gst_rtsp_session_next_timeout (session, now) == 0);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_stream_init_udp:
|
|
* @stream: a #GstRTSPSessionStream
|
|
* @ct: a client #GstRTSPTransport
|
|
*
|
|
* Set @ct as the client transport and create and return a matching server
|
|
* transport. This function takes ownership of the passed @ct.
|
|
*
|
|
* Returns: a server transport or NULL if something went wrong. Use
|
|
* gst_rtsp_transport_free () after usage.
|
|
*/
|
|
GstRTSPTransport *
|
|
gst_rtsp_session_stream_set_transport (GstRTSPSessionStream * stream,
|
|
GstRTSPTransport * ct)
|
|
{
|
|
GstRTSPTransport *st;
|
|
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
g_return_val_if_fail (ct != NULL, NULL);
|
|
|
|
/* prepare the server transport */
|
|
gst_rtsp_transport_new (&st);
|
|
|
|
st->trans = ct->trans;
|
|
st->profile = ct->profile;
|
|
st->lower_transport = ct->lower_transport;
|
|
|
|
switch (st->lower_transport) {
|
|
case GST_RTSP_LOWER_TRANS_UDP:
|
|
st->client_port = ct->client_port;
|
|
st->server_port = stream->media_stream->server_port;
|
|
break;
|
|
case GST_RTSP_LOWER_TRANS_UDP_MCAST:
|
|
ct->port = st->port = stream->media_stream->server_port;
|
|
st->destination = g_strdup (ct->destination);
|
|
break;
|
|
case GST_RTSP_LOWER_TRANS_TCP:
|
|
st->interleaved = ct->interleaved;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (stream->media_stream->session)
|
|
g_object_get (stream->media_stream->session, "internal-ssrc", &st->ssrc,
|
|
NULL);
|
|
|
|
/* keep track of the transports in the stream. */
|
|
if (stream->trans.transport)
|
|
gst_rtsp_transport_free (stream->trans.transport);
|
|
stream->trans.transport = ct;
|
|
|
|
return st;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_stream_set_callbacks:
|
|
* @stream: a #GstRTSPSessionStream
|
|
* @send_rtp: a callback called when RTP should be sent
|
|
* @send_rtcp: a callback called when RTCP should be sent
|
|
* @send_rtp_list: a callback called when RTP should be sent
|
|
* @send_rtcp_list: a callback called when RTCP should be sent
|
|
* @user_data: user data passed to callbacks
|
|
* @notify: called with the user_data when no longer needed.
|
|
*
|
|
* Install callbacks that will be called when data for a stream should be sent
|
|
* to a client. This is usually used when sending RTP/RTCP over TCP.
|
|
*/
|
|
void
|
|
gst_rtsp_session_stream_set_callbacks (GstRTSPSessionStream * stream,
|
|
GstRTSPSendFunc send_rtp, GstRTSPSendFunc send_rtcp,
|
|
GstRTSPSendListFunc send_rtp_list, GstRTSPSendListFunc send_rtcp_list,
|
|
gpointer user_data, GDestroyNotify notify)
|
|
{
|
|
stream->trans.send_rtp = send_rtp;
|
|
stream->trans.send_rtcp = send_rtcp;
|
|
stream->trans.send_rtp_list = send_rtp_list;
|
|
stream->trans.send_rtcp_list = send_rtcp_list;
|
|
if (stream->trans.notify)
|
|
stream->trans.notify (stream->trans.user_data);
|
|
stream->trans.user_data = user_data;
|
|
stream->trans.notify = notify;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_stream_set_keepalive:
|
|
* @stream: a #GstRTSPSessionStream
|
|
* @keep_alive: a callback called when the receiver is active
|
|
* @user_data: user data passed to callback
|
|
* @notify: called with the user_data when no longer needed.
|
|
*
|
|
* Install callbacks that will be called when RTCP packets are received from the
|
|
* receiver of @stream.
|
|
*/
|
|
void
|
|
gst_rtsp_session_stream_set_keepalive (GstRTSPSessionStream * stream,
|
|
GstRTSPKeepAliveFunc keep_alive, gpointer user_data, GDestroyNotify notify)
|
|
{
|
|
stream->trans.keep_alive = keep_alive;
|
|
if (stream->trans.ka_notify)
|
|
stream->trans.ka_notify (stream->trans.ka_user_data);
|
|
stream->trans.ka_user_data = user_data;
|
|
stream->trans.ka_notify = notify;
|
|
}
|
|
|
|
/**
|
|
* gst_rtsp_session_media_set_state:
|
|
* @media: a #GstRTSPSessionMedia
|
|
* @state: the new state
|
|
*
|
|
* Tell the media object @media to change to @state.
|
|
*
|
|
* Returns: %TRUE on success.
|
|
*/
|
|
gboolean
|
|
gst_rtsp_session_media_set_state (GstRTSPSessionMedia * media, GstState state)
|
|
{
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (media != NULL, FALSE);
|
|
|
|
ret = gst_rtsp_media_set_state (media->media, state, media->streams);
|
|
|
|
return ret;
|
|
}
|