204ccf78f7c82e014e0f0792e4c152138848f493
[platform/upstream/gst-rtsp-server.git] / gst / rtsp-server / rtsp-session.c
1 /* GStreamer
2  * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /**
20  * SECTION:rtsp-session
21  * @short_description: An object to manage media
22  * @see_also: #GstRTSPSessionPool, #GstRTSPSessionMedia, #GstRTSPMedia
23  *
24  * The #GstRTSPSession is identified by an id, unique in the
25  * #GstRTSPSessionPool that created the session and manages media and its
26  * configuration.
27  *
28  * A #GstRTSPSession has a timeout that can be retrieved with
29  * gst_rtsp_session_get_timeout(). You can check if the sessions is expired with
30  * gst_rtsp_session_is_expired(). gst_rtsp_session_touch() will reset the
31  * expiration counter of the session.
32  *
33  * When a client configures a media with SETUP, a session will be created to
34  * keep track of the configuration of that media. With
35  * gst_rtsp_session_manage_media(), the media is added to the managed media
36  * in the session. With gst_rtsp_session_release_media() the media can be
37  * released again from the session. Managed media is identified in the sessions
38  * with a url. Use gst_rtsp_session_get_media() to get the media that matches
39  * (part of) the given url.
40  *
41  * The media in a session can be iterated with gst_rtsp_session_filter().
42  *
43  * Last reviewed on 2013-07-11 (1.0.0)
44  */
45
46 #include <string.h>
47
48 #include "rtsp-session.h"
49
50 #define GST_RTSP_SESSION_GET_PRIVATE(obj)  \
51        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_SESSION, GstRTSPSessionPrivate))
52
53 struct _GstRTSPSessionPrivate
54 {
55   GMutex lock;                  /* protects everything but sessionid and create_time */
56   gchar *sessionid;
57
58   guint timeout;
59   GTimeVal create_time;         /* immutable */
60   GTimeVal last_access;
61   gint expire_count;
62
63   GList *medias;
64 };
65
66 #undef DEBUG
67
68 #define DEFAULT_TIMEOUT 60
69
70 enum
71 {
72   PROP_0,
73   PROP_SESSIONID,
74   PROP_TIMEOUT,
75   PROP_LAST
76 };
77
78 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
79 #define GST_CAT_DEFAULT rtsp_session_debug
80
81 static void gst_rtsp_session_get_property (GObject * object, guint propid,
82     GValue * value, GParamSpec * pspec);
83 static void gst_rtsp_session_set_property (GObject * object, guint propid,
84     const GValue * value, GParamSpec * pspec);
85 static void gst_rtsp_session_finalize (GObject * obj);
86
87 G_DEFINE_TYPE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT);
88
89 static void
90 gst_rtsp_session_class_init (GstRTSPSessionClass * klass)
91 {
92   GObjectClass *gobject_class;
93
94   g_type_class_add_private (klass, sizeof (GstRTSPSessionPrivate));
95
96   gobject_class = G_OBJECT_CLASS (klass);
97
98   gobject_class->get_property = gst_rtsp_session_get_property;
99   gobject_class->set_property = gst_rtsp_session_set_property;
100   gobject_class->finalize = gst_rtsp_session_finalize;
101
102   g_object_class_install_property (gobject_class, PROP_SESSIONID,
103       g_param_spec_string ("sessionid", "Sessionid", "the session id",
104           NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
105           G_PARAM_STATIC_STRINGS));
106
107   g_object_class_install_property (gobject_class, PROP_TIMEOUT,
108       g_param_spec_uint ("timeout", "timeout",
109           "the timeout of the session (0 = never)", 0, G_MAXUINT,
110           DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
111
112   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsession", 0,
113       "GstRTSPSession");
114 }
115
116 static void
117 gst_rtsp_session_init (GstRTSPSession * session)
118 {
119   GstRTSPSessionPrivate *priv = GST_RTSP_SESSION_GET_PRIVATE (session);
120
121   session->priv = priv;
122
123   g_mutex_init (&priv->lock);
124   priv->timeout = DEFAULT_TIMEOUT;
125   g_get_current_time (&priv->create_time);
126   gst_rtsp_session_touch (session);
127 }
128
129 static void
130 gst_rtsp_session_finalize (GObject * obj)
131 {
132   GstRTSPSession *session;
133   GstRTSPSessionPrivate *priv;
134
135   session = GST_RTSP_SESSION (obj);
136   priv = session->priv;
137
138   GST_INFO ("finalize session %p", session);
139
140   /* free all media */
141   g_list_free_full (priv->medias, g_object_unref);
142
143   /* free session id */
144   g_free (priv->sessionid);
145   g_mutex_clear (&priv->lock);
146
147   G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj);
148 }
149
150 static void
151 gst_rtsp_session_get_property (GObject * object, guint propid,
152     GValue * value, GParamSpec * pspec)
153 {
154   GstRTSPSession *session = GST_RTSP_SESSION (object);
155   GstRTSPSessionPrivate *priv = session->priv;
156
157   switch (propid) {
158     case PROP_SESSIONID:
159       g_value_set_string (value, priv->sessionid);
160       break;
161     case PROP_TIMEOUT:
162       g_value_set_uint (value, gst_rtsp_session_get_timeout (session));
163       break;
164     default:
165       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
166   }
167 }
168
169 static void
170 gst_rtsp_session_set_property (GObject * object, guint propid,
171     const GValue * value, GParamSpec * pspec)
172 {
173   GstRTSPSession *session = GST_RTSP_SESSION (object);
174   GstRTSPSessionPrivate *priv = session->priv;
175
176   switch (propid) {
177     case PROP_SESSIONID:
178       g_free (priv->sessionid);
179       priv->sessionid = g_value_dup_string (value);
180       break;
181     case PROP_TIMEOUT:
182       gst_rtsp_session_set_timeout (session, g_value_get_uint (value));
183       break;
184     default:
185       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
186   }
187 }
188
189 /**
190  * gst_rtsp_session_manage_media:
191  * @sess: a #GstRTSPSession
192  * @path: the path for the media
193  * @media: (transfer full): a #GstRTSPMedia
194  *
195  * Manage the media object @obj in @sess. @path will be used to retrieve this
196  * media from the session with gst_rtsp_session_get_media().
197  *
198  * Ownership is taken from @media.
199  *
200  * Returns: (transfer none): a new @GstRTSPSessionMedia object.
201  */
202 GstRTSPSessionMedia *
203 gst_rtsp_session_manage_media (GstRTSPSession * sess, const gchar * path,
204     GstRTSPMedia * media)
205 {
206   GstRTSPSessionPrivate *priv;
207   GstRTSPSessionMedia *result;
208
209   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
210   g_return_val_if_fail (path != NULL, NULL);
211   g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
212   g_return_val_if_fail (gst_rtsp_media_get_status (media) ==
213       GST_RTSP_MEDIA_STATUS_PREPARED, NULL);
214
215   priv = sess->priv;
216
217   result = gst_rtsp_session_media_new (path, media);
218
219   g_mutex_lock (&priv->lock);
220   priv->medias = g_list_prepend (priv->medias, result);
221   g_mutex_unlock (&priv->lock);
222
223   GST_INFO ("manage new media %p in session %p", media, result);
224
225   return result;
226 }
227
228 /**
229  * gst_rtsp_session_release_media:
230  * @sess: a #GstRTSPSession
231  * @media: a #GstRTSPMedia
232  *
233  * Release the managed @media in @sess, freeing the memory allocated by it.
234  *
235  * Returns: %TRUE if there are more media session left in @sess.
236  */
237 gboolean
238 gst_rtsp_session_release_media (GstRTSPSession * sess,
239     GstRTSPSessionMedia * media)
240 {
241   GstRTSPSessionPrivate *priv;
242   GList *find;
243   gboolean more;
244
245   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
246   g_return_val_if_fail (media != NULL, FALSE);
247
248   priv = sess->priv;
249
250   g_mutex_lock (&priv->lock);
251   find = g_list_find (priv->medias, media);
252   if (find)
253     priv->medias = g_list_delete_link (priv->medias, find);
254   more = (priv->medias != NULL);
255   g_mutex_unlock (&priv->lock);
256
257   if (find)
258     g_object_unref (media);
259
260   return more;
261 }
262
263 /**
264  * gst_rtsp_session_get_media:
265  * @sess: a #GstRTSPSession
266  * @path: the path for the media
267  * @matched: (out): the amount of matched characters
268  *
269  * Get the session media for @path. @matched will contain the number of matched
270  * characters of @path.
271  *
272  * Returns: (transfer none): the configuration for @path in @sess.
273  */
274 GstRTSPSessionMedia *
275 gst_rtsp_session_get_media (GstRTSPSession * sess, const gchar * path,
276     gint * matched)
277 {
278   GstRTSPSessionPrivate *priv;
279   GstRTSPSessionMedia *result;
280   GList *walk;
281   gint best;
282
283   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
284   g_return_val_if_fail (path != NULL, NULL);
285
286   priv = sess->priv;
287   result = NULL;
288   best = 0;
289
290   g_mutex_lock (&priv->lock);
291   for (walk = priv->medias; walk; walk = g_list_next (walk)) {
292     GstRTSPSessionMedia *test;
293
294     test = (GstRTSPSessionMedia *) walk->data;
295
296     /* find largest match */
297     if (gst_rtsp_session_media_matches (test, path, matched)) {
298       if (best < *matched) {
299         result = test;
300         best = *matched;
301       }
302     }
303   }
304   g_mutex_unlock (&priv->lock);
305
306   *matched = best;
307
308   return result;
309 }
310
311 /**
312  * gst_rtsp_session_filter:
313  * @sess: a #GstRTSPSession
314  * @func: (scope call) (allow-none): a callback
315  * @user_data: user data passed to @func
316  *
317  * Call @func for each media in @sess. The result value of @func determines
318  * what happens to the media. @func will be called with @sess
319  * locked so no further actions on @sess can be performed from @func.
320  *
321  * If @func returns #GST_RTSP_FILTER_REMOVE, the media will be removed from
322  * @sess.
323  *
324  * If @func returns #GST_RTSP_FILTER_KEEP, the media will remain in @sess.
325  *
326  * If @func returns #GST_RTSP_FILTER_REF, the media will remain in @sess but
327  * will also be added with an additional ref to the result #GList of this
328  * function..
329  *
330  * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all media.
331  *
332  * Returns: (element-type GstRTSPSessionMedia) (transfer full): a GList with all
333  * media for which @func returned #GST_RTSP_FILTER_REF. After usage, each
334  * element in the #GList should be unreffed before the list is freed.
335  */
336 GList *
337 gst_rtsp_session_filter (GstRTSPSession * sess,
338     GstRTSPSessionFilterFunc func, gpointer user_data)
339 {
340   GstRTSPSessionPrivate *priv;
341   GList *result, *walk, *next;
342
343   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
344
345   priv = sess->priv;
346
347   result = NULL;
348
349   g_mutex_lock (&priv->lock);
350   for (walk = priv->medias; walk; walk = next) {
351     GstRTSPSessionMedia *media = walk->data;
352     GstRTSPFilterResult res;
353
354     next = g_list_next (walk);
355
356     if (func)
357       res = func (sess, media, user_data);
358     else
359       res = GST_RTSP_FILTER_REF;
360
361     switch (res) {
362       case GST_RTSP_FILTER_REMOVE:
363         g_object_unref (media);
364         priv->medias = g_list_delete_link (priv->medias, walk);
365         break;
366       case GST_RTSP_FILTER_REF:
367         result = g_list_prepend (result, g_object_ref (media));
368         break;
369       case GST_RTSP_FILTER_KEEP:
370       default:
371         break;
372     }
373   }
374   g_mutex_unlock (&priv->lock);
375
376   return result;
377 }
378
379 /**
380  * gst_rtsp_session_new:
381  * @sessionid: a session id
382  *
383  * Create a new #GstRTSPSession instance with @sessionid.
384  */
385 GstRTSPSession *
386 gst_rtsp_session_new (const gchar * sessionid)
387 {
388   GstRTSPSession *result;
389
390   g_return_val_if_fail (sessionid != NULL, NULL);
391
392   result = g_object_new (GST_TYPE_RTSP_SESSION, "sessionid", sessionid, NULL);
393
394   return result;
395 }
396
397 /**
398  * gst_rtsp_session_get_sessionid:
399  * @session: a #GstRTSPSession
400  *
401  * Get the sessionid of @session.
402  *
403  * Returns: the sessionid of @session. The value remains valid as long as
404  * @session is alive.
405  */
406 const gchar *
407 gst_rtsp_session_get_sessionid (GstRTSPSession * session)
408 {
409   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
410
411   return session->priv->sessionid;
412 }
413
414 /**
415  * gst_rtsp_session_get_header:
416  * @session: a #GstRTSPSession
417  *
418  * Get the string that can be placed in the Session header field.
419  *
420  * Returns: (transfer full): the Session header of @session. g_free() after usage.
421  */
422 gchar *
423 gst_rtsp_session_get_header (GstRTSPSession * session)
424 {
425   GstRTSPSessionPrivate *priv;
426   gchar *result;
427
428   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
429
430   priv = session->priv;
431
432   g_mutex_lock (&priv->lock);
433   if (priv->timeout != 60)
434     result = g_strdup_printf ("%s; timeout=%d", priv->sessionid, priv->timeout);
435   else
436     result = g_strdup (priv->sessionid);
437   g_mutex_unlock (&priv->lock);
438
439   return result;
440 }
441
442 /**
443  * gst_rtsp_session_set_timeout:
444  * @session: a #GstRTSPSession
445  * @timeout: the new timeout
446  *
447  * Configure @session for a timeout of @timeout seconds. The session will be
448  * cleaned up when there is no activity for @timeout seconds.
449  */
450 void
451 gst_rtsp_session_set_timeout (GstRTSPSession * session, guint timeout)
452 {
453   GstRTSPSessionPrivate *priv;
454
455   g_return_if_fail (GST_IS_RTSP_SESSION (session));
456
457   priv = session->priv;
458
459   g_mutex_lock (&priv->lock);
460   priv->timeout = timeout;
461   g_mutex_unlock (&priv->lock);
462 }
463
464 /**
465  * gst_rtsp_session_get_timeout:
466  * @session: a #GstRTSPSession
467  *
468  * Get the timeout value of @session.
469  *
470  * Returns: the timeout of @session in seconds.
471  */
472 guint
473 gst_rtsp_session_get_timeout (GstRTSPSession * session)
474 {
475   GstRTSPSessionPrivate *priv;
476   guint res;
477
478   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), 0);
479
480   priv = session->priv;
481
482   g_mutex_lock (&priv->lock);
483   res = priv->timeout;
484   g_mutex_unlock (&priv->lock);
485
486   return res;
487 }
488
489 /**
490  * gst_rtsp_session_touch:
491  * @session: a #GstRTSPSession
492  *
493  * Update the last_access time of the session to the current time.
494  */
495 void
496 gst_rtsp_session_touch (GstRTSPSession * session)
497 {
498   GstRTSPSessionPrivate *priv;
499
500   g_return_if_fail (GST_IS_RTSP_SESSION (session));
501
502   priv = session->priv;
503
504   g_mutex_lock (&priv->lock);
505   g_get_current_time (&priv->last_access);
506   g_mutex_unlock (&priv->lock);
507 }
508
509 /**
510  * gst_rtsp_session_prevent_expire:
511  * @session: a #GstRTSPSession
512  *
513  * Prevent @session from expiring.
514  */
515 void
516 gst_rtsp_session_prevent_expire (GstRTSPSession * session)
517 {
518   g_return_if_fail (GST_IS_RTSP_SESSION (session));
519
520   g_atomic_int_add (&session->priv->expire_count, 1);
521 }
522
523 /**
524  * gst_rtsp_session_allow_expire:
525  * @session: a #GstRTSPSession
526  *
527  * Allow @session to expire. This method must be called an equal
528  * amount of time as gst_rtsp_session_prevent_expire().
529  */
530 void
531 gst_rtsp_session_allow_expire (GstRTSPSession * session)
532 {
533   g_atomic_int_add (&session->priv->expire_count, -1);
534 }
535
536 /**
537  * gst_rtsp_session_next_timeout:
538  * @session: a #GstRTSPSession
539  * @now: the current system time
540  *
541  * Get the amount of milliseconds till the session will expire.
542  *
543  * Returns: the amount of milliseconds since the session will time out.
544  */
545 gint
546 gst_rtsp_session_next_timeout (GstRTSPSession * session, GTimeVal * now)
547 {
548   GstRTSPSessionPrivate *priv;
549   gint res;
550   GstClockTime last_access, now_ns;
551
552   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), -1);
553   g_return_val_if_fail (now != NULL, -1);
554
555   priv = session->priv;
556
557   g_mutex_lock (&priv->lock);
558   if (g_atomic_int_get (&priv->expire_count) != 0) {
559     /* touch session when the expire count is not 0 */
560     g_get_current_time (&priv->last_access);
561   }
562
563   last_access = GST_TIMEVAL_TO_TIME (priv->last_access);
564   /* add timeout allow for 5 seconds of extra time */
565   last_access += priv->timeout * GST_SECOND + (5 * GST_SECOND);
566   g_mutex_unlock (&priv->lock);
567
568   now_ns = GST_TIMEVAL_TO_TIME (*now);
569
570   if (last_access > now_ns)
571     res = GST_TIME_AS_MSECONDS (last_access - now_ns);
572   else
573     res = 0;
574
575   return res;
576 }
577
578 /**
579  * gst_rtsp_session_is_expired:
580  * @session: a #GstRTSPSession
581  * @now: the current system time
582  *
583  * Check if @session timeout out.
584  *
585  * Returns: %TRUE if @session timed out
586  */
587 gboolean
588 gst_rtsp_session_is_expired (GstRTSPSession * session, GTimeVal * now)
589 {
590   gboolean res;
591
592   res = (gst_rtsp_session_next_timeout (session, now) == 0);
593
594   return res;
595 }