Initial release including wifi display based on gst-rtsp-server-1.4.1
[platform/upstream/gstreamer.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   gboolean timeout_always_visible;
60   GTimeVal create_time;         /* immutable */
61   GTimeVal last_access;
62   gint expire_count;
63
64   GList *medias;
65   guint medias_cookie;
66 };
67
68 #undef DEBUG
69
70 #define DEFAULT_TIMEOUT        60
71 #define DEFAULT_ALWAYS_VISIBLE  FALSE
72
73 enum
74 {
75   PROP_0,
76   PROP_SESSIONID,
77   PROP_TIMEOUT,
78   PROP_TIMEOUT_ALWAYS_VISIBLE,
79   PROP_LAST
80 };
81
82 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
83 #define GST_CAT_DEFAULT rtsp_session_debug
84
85 static void gst_rtsp_session_get_property (GObject * object, guint propid,
86     GValue * value, GParamSpec * pspec);
87 static void gst_rtsp_session_set_property (GObject * object, guint propid,
88     const GValue * value, GParamSpec * pspec);
89 static void gst_rtsp_session_finalize (GObject * obj);
90
91 G_DEFINE_TYPE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT);
92
93 static void
94 gst_rtsp_session_class_init (GstRTSPSessionClass * klass)
95 {
96   GObjectClass *gobject_class;
97
98   g_type_class_add_private (klass, sizeof (GstRTSPSessionPrivate));
99
100   gobject_class = G_OBJECT_CLASS (klass);
101
102   gobject_class->get_property = gst_rtsp_session_get_property;
103   gobject_class->set_property = gst_rtsp_session_set_property;
104   gobject_class->finalize = gst_rtsp_session_finalize;
105
106   g_object_class_install_property (gobject_class, PROP_SESSIONID,
107       g_param_spec_string ("sessionid", "Sessionid", "the session id",
108           NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
109           G_PARAM_STATIC_STRINGS));
110
111   g_object_class_install_property (gobject_class, PROP_TIMEOUT,
112       g_param_spec_uint ("timeout", "timeout",
113           "the timeout of the session (0 = never)", 0, G_MAXUINT,
114           DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
115
116   g_object_class_install_property (gobject_class, PROP_TIMEOUT_ALWAYS_VISIBLE,
117       g_param_spec_boolean ("timeout-always-visible", "Timeout Always Visible ",
118           "timeout always visible in header",
119           DEFAULT_ALWAYS_VISIBLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
120
121   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsession", 0,
122       "GstRTSPSession");
123 }
124
125 static void
126 gst_rtsp_session_init (GstRTSPSession * session)
127 {
128   GstRTSPSessionPrivate *priv = GST_RTSP_SESSION_GET_PRIVATE (session);
129
130   session->priv = priv;
131
132   GST_INFO ("init session %p", session);
133
134   g_mutex_init (&priv->lock);
135   priv->timeout = DEFAULT_TIMEOUT;
136   g_get_current_time (&priv->create_time);
137   gst_rtsp_session_touch (session);
138 }
139
140 static void
141 gst_rtsp_session_finalize (GObject * obj)
142 {
143   GstRTSPSession *session;
144   GstRTSPSessionPrivate *priv;
145
146   session = GST_RTSP_SESSION (obj);
147   priv = session->priv;
148
149   GST_INFO ("finalize session %p", session);
150
151   /* free all media */
152   g_list_free_full (priv->medias, g_object_unref);
153
154   /* free session id */
155   g_free (priv->sessionid);
156   g_mutex_clear (&priv->lock);
157
158   G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj);
159 }
160
161 static void
162 gst_rtsp_session_get_property (GObject * object, guint propid,
163     GValue * value, GParamSpec * pspec)
164 {
165   GstRTSPSession *session = GST_RTSP_SESSION (object);
166   GstRTSPSessionPrivate *priv = session->priv;
167
168   switch (propid) {
169     case PROP_SESSIONID:
170       g_value_set_string (value, priv->sessionid);
171       break;
172     case PROP_TIMEOUT:
173       g_value_set_uint (value, gst_rtsp_session_get_timeout (session));
174       break;
175     case PROP_TIMEOUT_ALWAYS_VISIBLE:
176       g_value_set_boolean (value, priv->timeout_always_visible);
177       break;
178     default:
179       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
180   }
181 }
182
183 static void
184 gst_rtsp_session_set_property (GObject * object, guint propid,
185     const GValue * value, GParamSpec * pspec)
186 {
187   GstRTSPSession *session = GST_RTSP_SESSION (object);
188   GstRTSPSessionPrivate *priv = session->priv;
189
190   switch (propid) {
191     case PROP_SESSIONID:
192       g_free (priv->sessionid);
193       priv->sessionid = g_value_dup_string (value);
194       break;
195     case PROP_TIMEOUT:
196       gst_rtsp_session_set_timeout (session, g_value_get_uint (value));
197       break;
198     case PROP_TIMEOUT_ALWAYS_VISIBLE:
199       g_mutex_lock (&priv->lock);
200       priv->timeout_always_visible = g_value_get_boolean (value);
201       g_mutex_unlock (&priv->lock);
202       break;
203     default:
204       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
205   }
206 }
207
208 /**
209  * gst_rtsp_session_manage_media:
210  * @sess: a #GstRTSPSession
211  * @path: the path for the media
212  * @media: (transfer full): a #GstRTSPMedia
213  *
214  * Manage the media object @obj in @sess. @path will be used to retrieve this
215  * media from the session with gst_rtsp_session_get_media().
216  *
217  * Ownership is taken from @media.
218  *
219  * Returns: (transfer none): a new @GstRTSPSessionMedia object.
220  */
221 GstRTSPSessionMedia *
222 gst_rtsp_session_manage_media (GstRTSPSession * sess, const gchar * path,
223     GstRTSPMedia * media)
224 {
225   GstRTSPSessionPrivate *priv;
226   GstRTSPSessionMedia *result;
227   GstRTSPMediaStatus status;
228
229   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
230   g_return_val_if_fail (path != NULL, NULL);
231   g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
232   status = gst_rtsp_media_get_status (media);
233   g_return_val_if_fail (status == GST_RTSP_MEDIA_STATUS_PREPARED || status ==
234       GST_RTSP_MEDIA_STATUS_SUSPENDED, NULL);
235
236   priv = sess->priv;
237
238   result = gst_rtsp_session_media_new (path, media);
239
240   g_mutex_lock (&priv->lock);
241   priv->medias = g_list_prepend (priv->medias, result);
242   priv->medias_cookie++;
243   g_mutex_unlock (&priv->lock);
244
245   GST_INFO ("manage new media %p in session %p", media, result);
246
247   return result;
248 }
249
250 /**
251  * gst_rtsp_session_release_media:
252  * @sess: a #GstRTSPSession
253  * @media: (transfer none): a #GstRTSPMedia
254  *
255  * Release the managed @media in @sess, freeing the memory allocated by it.
256  *
257  * Returns: %TRUE if there are more media session left in @sess.
258  */
259 gboolean
260 gst_rtsp_session_release_media (GstRTSPSession * sess,
261     GstRTSPSessionMedia * media)
262 {
263   GstRTSPSessionPrivate *priv;
264   GList *find;
265   gboolean more;
266
267   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
268   g_return_val_if_fail (media != NULL, FALSE);
269
270   priv = sess->priv;
271
272   g_mutex_lock (&priv->lock);
273   find = g_list_find (priv->medias, media);
274   if (find) {
275     priv->medias = g_list_delete_link (priv->medias, find);
276     priv->medias_cookie++;
277   }
278   more = (priv->medias != NULL);
279   g_mutex_unlock (&priv->lock);
280
281   if (find)
282     g_object_unref (media);
283
284   return more;
285 }
286
287 /**
288  * gst_rtsp_session_get_media:
289  * @sess: a #GstRTSPSession
290  * @path: the path for the media
291  * @matched: (out): the amount of matched characters
292  *
293  * Get the session media for @path. @matched will contain the number of matched
294  * characters of @path.
295  *
296  * Returns: (transfer none): the configuration for @path in @sess.
297  */
298 GstRTSPSessionMedia *
299 gst_rtsp_session_get_media (GstRTSPSession * sess, const gchar * path,
300     gint * matched)
301 {
302   GstRTSPSessionPrivate *priv;
303   GstRTSPSessionMedia *result;
304   GList *walk;
305   gint best;
306
307   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
308   g_return_val_if_fail (path != NULL, NULL);
309
310   priv = sess->priv;
311   result = NULL;
312   best = 0;
313
314   g_mutex_lock (&priv->lock);
315   for (walk = priv->medias; walk; walk = g_list_next (walk)) {
316     GstRTSPSessionMedia *test;
317
318     test = (GstRTSPSessionMedia *) walk->data;
319
320     /* find largest match */
321     if (gst_rtsp_session_media_matches (test, path, matched)) {
322       if (best < *matched) {
323         result = test;
324         best = *matched;
325       }
326     }
327   }
328   g_mutex_unlock (&priv->lock);
329
330   *matched = best;
331
332   return result;
333 }
334
335 /**
336  * gst_rtsp_session_filter:
337  * @sess: a #GstRTSPSession
338  * @func: (scope call) (allow-none): a callback
339  * @user_data: (closure): user data passed to @func
340  *
341  * Call @func for each media in @sess. The result value of @func determines
342  * what happens to the media. @func will be called with @sess
343  * locked so no further actions on @sess can be performed from @func.
344  *
345  * If @func returns #GST_RTSP_FILTER_REMOVE, the media will be removed from
346  * @sess.
347  *
348  * If @func returns #GST_RTSP_FILTER_KEEP, the media will remain in @sess.
349  *
350  * If @func returns #GST_RTSP_FILTER_REF, the media will remain in @sess but
351  * will also be added with an additional ref to the result #GList of this
352  * function..
353  *
354  * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all media.
355  *
356  * Returns: (element-type GstRTSPSessionMedia) (transfer full): a GList with all
357  * media for which @func returned #GST_RTSP_FILTER_REF. After usage, each
358  * element in the #GList should be unreffed before the list is freed.
359  */
360 GList *
361 gst_rtsp_session_filter (GstRTSPSession * sess,
362     GstRTSPSessionFilterFunc func, gpointer user_data)
363 {
364   GstRTSPSessionPrivate *priv;
365   GList *result, *walk, *next;
366   GHashTable *visited;
367   guint cookie;
368
369   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), NULL);
370
371   priv = sess->priv;
372
373   result = NULL;
374   if (func)
375     visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
376
377   g_mutex_lock (&priv->lock);
378 restart:
379   cookie = priv->medias_cookie;
380   for (walk = priv->medias; walk; walk = next) {
381     GstRTSPSessionMedia *media = walk->data;
382     GstRTSPFilterResult res;
383     gboolean changed;
384
385     next = g_list_next (walk);
386
387     if (func) {
388       /* only visit each media once */
389       if (g_hash_table_contains (visited, media))
390         continue;
391
392       g_hash_table_add (visited, g_object_ref (media));
393       g_mutex_unlock (&priv->lock);
394
395       res = func (sess, media, user_data);
396
397       g_mutex_lock (&priv->lock);
398     } else
399       res = GST_RTSP_FILTER_REF;
400
401     changed = (cookie != priv->medias_cookie);
402
403     switch (res) {
404       case GST_RTSP_FILTER_REMOVE:
405         if (changed)
406           priv->medias = g_list_remove (priv->medias, media);
407         else
408           priv->medias = g_list_delete_link (priv->medias, walk);
409         cookie = ++priv->medias_cookie;
410         g_object_unref (media);
411         break;
412       case GST_RTSP_FILTER_REF:
413         result = g_list_prepend (result, g_object_ref (media));
414         break;
415       case GST_RTSP_FILTER_KEEP:
416       default:
417         break;
418     }
419     if (changed)
420       goto restart;
421   }
422   g_mutex_unlock (&priv->lock);
423
424   if (func)
425     g_hash_table_unref (visited);
426
427   return result;
428 }
429
430 /**
431  * gst_rtsp_session_new:
432  * @sessionid: a session id
433  *
434  * Create a new #GstRTSPSession instance with @sessionid.
435  *
436  * Returns: (transfer full): a new #GstRTSPSession
437  */
438 GstRTSPSession *
439 gst_rtsp_session_new (const gchar * sessionid)
440 {
441   GstRTSPSession *result;
442
443   g_return_val_if_fail (sessionid != NULL, NULL);
444
445   result = g_object_new (GST_TYPE_RTSP_SESSION, "sessionid", sessionid, NULL);
446
447   return result;
448 }
449
450 /**
451  * gst_rtsp_session_get_sessionid:
452  * @session: a #GstRTSPSession
453  *
454  * Get the sessionid of @session.
455  *
456  * Returns: (transfer none): the sessionid of @session. The value remains valid
457  * as long as @session is alive.
458  */
459 const gchar *
460 gst_rtsp_session_get_sessionid (GstRTSPSession * session)
461 {
462   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
463
464   return session->priv->sessionid;
465 }
466
467 /**
468  * gst_rtsp_session_get_header:
469  * @session: a #GstRTSPSession
470  *
471  * Get the string that can be placed in the Session header field.
472  *
473  * Returns: (transfer full): the Session header of @session. g_free() after usage.
474  */
475 gchar *
476 gst_rtsp_session_get_header (GstRTSPSession * session)
477 {
478   GstRTSPSessionPrivate *priv;
479   gchar *result;
480
481   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
482
483   priv = session->priv;
484
485
486   g_mutex_lock (&priv->lock);
487   if (priv->timeout_always_visible || priv->timeout != 60)
488     result = g_strdup_printf ("%s; timeout=%d", priv->sessionid, priv->timeout);
489   else
490     result = g_strdup (priv->sessionid);
491   g_mutex_unlock (&priv->lock);
492
493   return result;
494 }
495
496 /**
497  * gst_rtsp_session_set_timeout:
498  * @session: a #GstRTSPSession
499  * @timeout: the new timeout
500  *
501  * Configure @session for a timeout of @timeout seconds. The session will be
502  * cleaned up when there is no activity for @timeout seconds.
503  */
504 void
505 gst_rtsp_session_set_timeout (GstRTSPSession * session, guint timeout)
506 {
507   GstRTSPSessionPrivate *priv;
508
509   g_return_if_fail (GST_IS_RTSP_SESSION (session));
510
511   priv = session->priv;
512
513   g_mutex_lock (&priv->lock);
514   priv->timeout = timeout;
515   g_mutex_unlock (&priv->lock);
516 }
517
518 /**
519  * gst_rtsp_session_get_timeout:
520  * @session: a #GstRTSPSession
521  *
522  * Get the timeout value of @session.
523  *
524  * Returns: the timeout of @session in seconds.
525  */
526 guint
527 gst_rtsp_session_get_timeout (GstRTSPSession * session)
528 {
529   GstRTSPSessionPrivate *priv;
530   guint res;
531
532   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), 0);
533
534   priv = session->priv;
535
536   g_mutex_lock (&priv->lock);
537   res = priv->timeout;
538   g_mutex_unlock (&priv->lock);
539
540   return res;
541 }
542
543 /**
544  * gst_rtsp_session_touch:
545  * @session: a #GstRTSPSession
546  *
547  * Update the last_access time of the session to the current time.
548  */
549 void
550 gst_rtsp_session_touch (GstRTSPSession * session)
551 {
552   GstRTSPSessionPrivate *priv;
553
554   g_return_if_fail (GST_IS_RTSP_SESSION (session));
555
556   priv = session->priv;
557
558   g_mutex_lock (&priv->lock);
559   g_get_current_time (&priv->last_access);
560   g_mutex_unlock (&priv->lock);
561 }
562
563 /**
564  * gst_rtsp_session_prevent_expire:
565  * @session: a #GstRTSPSession
566  *
567  * Prevent @session from expiring.
568  */
569 void
570 gst_rtsp_session_prevent_expire (GstRTSPSession * session)
571 {
572   g_return_if_fail (GST_IS_RTSP_SESSION (session));
573
574   g_atomic_int_add (&session->priv->expire_count, 1);
575 }
576
577 /**
578  * gst_rtsp_session_allow_expire:
579  * @session: a #GstRTSPSession
580  *
581  * Allow @session to expire. This method must be called an equal
582  * amount of time as gst_rtsp_session_prevent_expire().
583  */
584 void
585 gst_rtsp_session_allow_expire (GstRTSPSession * session)
586 {
587   g_atomic_int_add (&session->priv->expire_count, -1);
588 }
589
590 /**
591  * gst_rtsp_session_next_timeout:
592  * @session: a #GstRTSPSession
593  * @now: (transfer none): the current system time
594  *
595  * Get the amount of milliseconds till the session will expire.
596  *
597  * Returns: the amount of milliseconds since the session will time out.
598  */
599 gint
600 gst_rtsp_session_next_timeout (GstRTSPSession * session, GTimeVal * now)
601 {
602   GstRTSPSessionPrivate *priv;
603   gint res;
604   GstClockTime last_access, now_ns;
605
606   g_return_val_if_fail (GST_IS_RTSP_SESSION (session), -1);
607   g_return_val_if_fail (now != NULL, -1);
608
609   priv = session->priv;
610
611   g_mutex_lock (&priv->lock);
612   if (g_atomic_int_get (&priv->expire_count) != 0) {
613     /* touch session when the expire count is not 0 */
614     g_get_current_time (&priv->last_access);
615   }
616
617   last_access = GST_TIMEVAL_TO_TIME (priv->last_access);
618   /* add timeout allow for 5 seconds of extra time */
619   last_access += priv->timeout * GST_SECOND + (5 * GST_SECOND);
620   g_mutex_unlock (&priv->lock);
621
622   now_ns = GST_TIMEVAL_TO_TIME (*now);
623
624   if (last_access > now_ns)
625     res = GST_TIME_AS_MSECONDS (last_access - now_ns);
626   else
627     res = 0;
628
629   return res;
630 }
631
632 /**
633  * gst_rtsp_session_is_expired:
634  * @session: a #GstRTSPSession
635  * @now: (transfer none): the current system time
636  *
637  * Check if @session timeout out.
638  *
639  * Returns: %TRUE if @session timed out
640  */
641 gboolean
642 gst_rtsp_session_is_expired (GstRTSPSession * session, GTimeVal * now)
643 {
644   gboolean res;
645
646   res = (gst_rtsp_session_next_timeout (session, now) == 0);
647
648   return res;
649 }