X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp-server%2Frtsp-media.c;h=6b81e254e5dd1bdcb465c02b636869dde684d2cb;hb=376488d8c7d0a92c56065070f9003e699533c3e5;hp=10610ffd039f77e76bbbab6228cc9404853b7b15;hpb=0844e8afbc2f9d4892463bc0b0327d12af4b0878;p=platform%2Fupstream%2Fgstreamer.git diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 10610ff..6b81e25 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -16,6 +16,50 @@ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ +/** + * SECTION:rtsp-media + * @short_description: The media pipeline + * @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession, + * #GstRTSPSessionMedia + * + * a #GstRTSPMedia contains the complete GStreamer pipeline to manage the + * streaming to the clients. The actual data transfer is done by the + * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia. + * + * The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the + * client does a DESCRIBE or SETUP of a resource. + * + * A media is created with gst_rtsp_media_new() that takes the element that will + * provide the streaming elements. For each of the streams, a new #GstRTSPStream + * object needs to be made with the gst_rtsp_media_create_stream() which takes + * the payloader element and the source pad that produces the RTP stream. + * + * The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The + * prepare method will add rtpbin and sinks and sources to send and receive RTP + * and RTCP packets from the clients. Each stream srcpad is connected to an + * input into the internal rtpbin. + * + * It is also possible to dynamically create #GstRTSPStream objects during the + * prepare phase. With gst_rtsp_media_get_status() you can check the status of + * the prepare phase. + * + * After the media is prepared, it is ready for streaming. It will usually be + * managed in a session with gst_rtsp_session_manage_media(). See + * #GstRTSPSession and #GstRTSPSessionMedia. + * + * The state of the media can be controlled with gst_rtsp_media_set_state (). + * Seeking can be done with gst_rtsp_media_seek(). + * + * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When + * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to + * cleanly shut down. + * + * With gst_rtsp_media_set_shared(), the media can be shared between multiple + * clients. With gst_rtsp_media_set_reusable() you can control if the pipeline + * can be prepared again after an unprepare. + * + * Last reviewed on 2013-07-11 (1.0.0) + */ #include #include @@ -33,29 +77,37 @@ struct _GstRTSPMediaPrivate GMutex lock; GCond cond; + /* protected by lock */ + GstRTSPPermissions *permissions; gboolean shared; + gboolean suspend_mode; gboolean reusable; + GstRTSPProfile profiles; GstRTSPLowerTrans protocols; gboolean reused; gboolean eos_shutdown; guint buffer_size; - GstRTSPAuth *auth; GstRTSPAddressPool *pool; + gboolean blocked; GstElement *element; - GRecMutex state_lock; - GPtrArray *streams; - GList *dynamic; - GstRTSPMediaStatus status; + GRecMutex state_lock; /* locking order: state lock, lock */ + GPtrArray *streams; /* protected by lock */ + GList *dynamic; /* protected by lock */ + GstRTSPMediaStatus status; /* protected by lock */ gint prepare_count; gint n_active; gboolean adding; /* the pipeline for the media */ GstElement *pipeline; - GstElement *fakesink; + GstElement *fakesink; /* protected by lock */ GSource *source; guint id; + GstRTSPThread *thread; + + gboolean time_provider; + GstNetTimeProvider *nettime; gboolean is_live; gboolean seekable; @@ -66,17 +118,20 @@ struct _GstRTSPMediaPrivate GstElement *rtpbin; /* the range of media */ - GstRTSPTimeRange range; + GstRTSPTimeRange range; /* protected by lock */ GstClockTime range_start; GstClockTime range_stop; }; #define DEFAULT_SHARED FALSE +#define DEFAULT_SUSPEND_MODE GST_RTSP_SUSPEND_MODE_NONE #define DEFAULT_REUSABLE FALSE -#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_TCP -//#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP_MCAST +#define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP +#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \ + GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_EOS_SHUTDOWN FALSE #define DEFAULT_BUFFER_SIZE 0x80000 +#define DEFAULT_TIME_PROVIDER FALSE /* define to dump received RTCP packets */ #undef DUMP_STATS @@ -85,19 +140,24 @@ enum { PROP_0, PROP_SHARED, + PROP_SUSPEND_MODE, PROP_REUSABLE, + PROP_PROFILES, PROP_PROTOCOLS, PROP_EOS_SHUTDOWN, PROP_BUFFER_SIZE, PROP_ELEMENT, + PROP_TIME_PROVIDER, PROP_LAST }; enum { SIGNAL_NEW_STREAM, + SIGNAL_REMOVED_STREAM, SIGNAL_PREPARED, SIGNAL_UNPREPARED, + SIGNAL_TARGET_STATE, SIGNAL_NEW_STATE, SIGNAL_LAST }; @@ -111,14 +171,49 @@ static void gst_rtsp_media_set_property (GObject * object, guint propid, const GValue * value, GParamSpec * pspec); static void gst_rtsp_media_finalize (GObject * obj); -static gpointer do_loop (GstRTSPMediaClass * klass); static gboolean default_handle_message (GstRTSPMedia * media, GstMessage * message); static void finish_unprepare (GstRTSPMedia * media); +static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread); static gboolean default_unprepare (GstRTSPMedia * media); +static gboolean default_suspend (GstRTSPMedia * media); +static gboolean default_unsuspend (GstRTSPMedia * media); +static gboolean default_convert_range (GstRTSPMedia * media, + GstRTSPTimeRange * range, GstRTSPRangeUnit unit); +static gboolean default_query_position (GstRTSPMedia * media, + gint64 * position); +static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop); +static GstElement *default_create_rtpbin (GstRTSPMedia * media); +static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, + GstSDPInfo * info); + +static gboolean wait_preroll (GstRTSPMedia * media); static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 }; +#define C_ENUM(v) ((gint) v) + +#define GST_TYPE_RTSP_SUSPEND_MODE (gst_rtsp_suspend_mode_get_type()) +GType +gst_rtsp_suspend_mode_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_RTSP_SUSPEND_MODE_NONE), "GST_RTSP_SUSPEND_MODE_NONE", "none"}, + {C_ENUM (GST_RTSP_SUSPEND_MODE_PAUSE), "GST_RTSP_SUSPEND_MODE_PAUSE", + "pause"}, + {C_ENUM (GST_RTSP_SUSPEND_MODE_RESET), "GST_RTSP_SUSPEND_MODE_RESET", + "reset"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstRTSPSuspendMode", values); + g_once_init_leave (&id, tmp); + } + return (GType) id; +} + G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT); static void @@ -139,11 +234,21 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) "If this media pipeline can be shared", DEFAULT_SHARED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SUSPEND_MODE, + g_param_spec_enum ("suspend-mode", "Suspend Mode", + "How to suspend the media in PAUSED", GST_TYPE_RTSP_SUSPEND_MODE, + DEFAULT_SUSPEND_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_REUSABLE, g_param_spec_boolean ("reusable", "Reusable", "If this media pipeline can be reused after an unprepare", DEFAULT_REUSABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PROFILES, + g_param_spec_flags ("profiles", "Profiles", + "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE, + DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PROTOCOLS, g_param_spec_flags ("protocols", "Protocols", "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS, @@ -164,35 +269,54 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) "The GstBin to use for streaming the media", GST_TYPE_ELEMENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_TIME_PROVIDER, + g_param_spec_boolean ("time-provider", "Time Provider", + "Use a NetTimeProvider for clients", + DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gst_rtsp_media_signals[SIGNAL_NEW_STREAM] = g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM); + gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM] = + g_signal_new ("removed-stream", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, removed_stream), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, + GST_TYPE_RTSP_STREAM); + gst_rtsp_media_signals[SIGNAL_PREPARED] = g_signal_new ("prepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, prepared), NULL, NULL, - g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); gst_rtsp_media_signals[SIGNAL_UNPREPARED] = g_signal_new ("unprepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, unprepared), NULL, NULL, - g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + + gst_rtsp_media_signals[SIGNAL_TARGET_STATE] = + g_signal_new ("target-state", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, target_state), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT); gst_rtsp_media_signals[SIGNAL_NEW_STATE] = g_signal_new ("new-state", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, new_state), NULL, NULL, - g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 0, G_TYPE_INT); - - klass->context = g_main_context_new (); - klass->loop = g_main_loop_new (klass->context, TRUE); + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT); GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmedia", 0, "GstRTSPMedia"); - klass->thread = g_thread_new ("Bus Thread", (GThreadFunc) do_loop, klass); - klass->handle_message = default_handle_message; + klass->prepare = default_prepare; klass->unprepare = default_unprepare; + klass->suspend = default_suspend; + klass->unsuspend = default_unsuspend; + klass->convert_range = default_convert_range; + klass->query_position = default_query_position; + klass->query_stop = default_query_stop; + klass->create_rtpbin = default_create_rtpbin; + klass->setup_sdp = default_setup_sdp; } static void @@ -208,10 +332,13 @@ gst_rtsp_media_init (GstRTSPMedia * media) g_rec_mutex_init (&priv->state_lock); priv->shared = DEFAULT_SHARED; + priv->suspend_mode = DEFAULT_SUSPEND_MODE; priv->reusable = DEFAULT_REUSABLE; + priv->profiles = DEFAULT_PROFILES; priv->protocols = DEFAULT_PROTOCOLS; priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN; priv->buffer_size = DEFAULT_BUFFER_SIZE; + priv->time_provider = DEFAULT_TIME_PROVIDER; } static void @@ -225,15 +352,18 @@ gst_rtsp_media_finalize (GObject * obj) GST_INFO ("finalize media %p", media); + if (priv->permissions) + gst_rtsp_permissions_unref (priv->permissions); + g_ptr_array_unref (priv->streams); g_list_free_full (priv->dynamic, gst_object_unref); if (priv->pipeline) gst_object_unref (priv->pipeline); + if (priv->nettime) + gst_object_unref (priv->nettime); gst_object_unref (priv->element); - if (priv->auth) - g_object_unref (priv->auth); if (priv->pool) g_object_unref (priv->pool); g_mutex_clear (&priv->lock); @@ -256,9 +386,15 @@ gst_rtsp_media_get_property (GObject * object, guint propid, case PROP_SHARED: g_value_set_boolean (value, gst_rtsp_media_is_shared (media)); break; + case PROP_SUSPEND_MODE: + g_value_set_enum (value, gst_rtsp_media_get_suspend_mode (media)); + break; case PROP_REUSABLE: g_value_set_boolean (value, gst_rtsp_media_is_reusable (media)); break; + case PROP_PROFILES: + g_value_set_flags (value, gst_rtsp_media_get_profiles (media)); + break; case PROP_PROTOCOLS: g_value_set_flags (value, gst_rtsp_media_get_protocols (media)); break; @@ -268,6 +404,9 @@ gst_rtsp_media_get_property (GObject * object, guint propid, case PROP_BUFFER_SIZE: g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media)); break; + case PROP_TIME_PROVIDER: + g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } @@ -282,13 +421,20 @@ gst_rtsp_media_set_property (GObject * object, guint propid, switch (propid) { case PROP_ELEMENT: media->priv->element = g_value_get_object (value); + gst_object_ref_sink (media->priv->element); break; case PROP_SHARED: gst_rtsp_media_set_shared (media, g_value_get_boolean (value)); break; + case PROP_SUSPEND_MODE: + gst_rtsp_media_set_suspend_mode (media, g_value_get_enum (value)); + break; case PROP_REUSABLE: gst_rtsp_media_set_reusable (media, g_value_get_boolean (value)); break; + case PROP_PROFILES: + gst_rtsp_media_set_profiles (media, g_value_get_flags (value)); + break; case PROP_PROTOCOLS: gst_rtsp_media_set_protocols (media, g_value_get_flags (value)); break; @@ -298,19 +444,92 @@ gst_rtsp_media_set_property (GObject * object, guint propid, case PROP_BUFFER_SIZE: gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value)); break; + case PROP_TIME_PROVIDER: + gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } } -static gpointer -do_loop (GstRTSPMediaClass * klass) +typedef struct +{ + gint64 position; + gboolean ret; +} DoQueryPositionData; + +static void +do_query_position (GstRTSPStream * stream, DoQueryPositionData * data) +{ + gint64 tmp; + + if (gst_rtsp_stream_query_position (stream, &tmp)) { + data->position = MAX (data->position, tmp); + data->ret = TRUE; + } +} + +static gboolean +default_query_position (GstRTSPMedia * media, gint64 * position) +{ + GstRTSPMediaPrivate *priv; + DoQueryPositionData data; + + priv = media->priv; + + data.position = -1; + data.ret = FALSE; + + g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data); + + *position = data.position; + + return data.ret; +} + +typedef struct +{ + gint64 stop; + gboolean ret; +} DoQueryStopData; + +static void +do_query_stop (GstRTSPStream * stream, DoQueryStopData * data) +{ + gint64 tmp; + + if (gst_rtsp_stream_query_stop (stream, &tmp)) { + data->stop = MAX (data->stop, tmp); + data->ret = TRUE; + } +} + +static gboolean +default_query_stop (GstRTSPMedia * media, gint64 * stop) +{ + GstRTSPMediaPrivate *priv; + DoQueryStopData data; + + priv = media->priv; + + data.stop = -1; + data.ret = FALSE; + + g_ptr_array_foreach (priv->streams, (GFunc) do_query_stop, &data); + + *stop = data.stop; + + return data.ret; +} + +static GstElement * +default_create_rtpbin (GstRTSPMedia * media) { - GST_INFO ("enter mainloop"); - g_main_loop_run (klass->loop); - GST_INFO ("exit mainloop"); + GstElement *rtpbin; - return NULL; + rtpbin = gst_element_factory_make ("rtpbin", NULL); + + return rtpbin; } /* must be called with state lock */ @@ -318,7 +537,7 @@ static void collect_media_stats (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; - gint64 position, duration; + gint64 position = 0, stop = -1; if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) @@ -336,22 +555,33 @@ collect_media_stats (GstRTSPMedia * media) priv->range.max.seconds = -1; priv->range_stop = -1; } else { + GstRTSPMediaClass *klass; + gboolean ret; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + /* get the position */ - if (!gst_element_query_position (priv->pipeline, GST_FORMAT_TIME, - &position)) { + ret = FALSE; + if (klass->query_position) + ret = klass->query_position (media, &position); + + if (!ret) { GST_INFO ("position query failed"); position = 0; } - /* get the duration */ - if (!gst_element_query_duration (priv->pipeline, GST_FORMAT_TIME, - &duration)) { - GST_INFO ("duration query failed"); - duration = -1; + /* get the current segment stop */ + ret = FALSE; + if (klass->query_stop) + ret = klass->query_stop (media, &stop); + + if (!ret) { + GST_INFO ("stop query failed"); + stop = -1; } - GST_INFO ("stats: position %" GST_TIME_FORMAT ", duration %" - GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (duration)); + GST_INFO ("stats: position %" GST_TIME_FORMAT ", stop %" + GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (stop)); if (position == -1) { priv->range.min.type = GST_RTSP_TIME_NOW; @@ -362,14 +592,14 @@ collect_media_stats (GstRTSPMedia * media) priv->range.min.seconds = ((gdouble) position) / GST_SECOND; priv->range_start = position; } - if (duration == -1) { + if (stop == -1) { priv->range.max.type = GST_RTSP_TIME_END; priv->range.max.seconds = -1; priv->range_stop = -1; } else { priv->range.max.type = GST_RTSP_TIME_SECONDS; - priv->range.max.seconds = ((gdouble) duration) / GST_SECOND; - priv->range_stop = duration; + priv->range.max.seconds = ((gdouble) stop) / GST_SECOND; + priv->range_stop = stop; } } } @@ -385,7 +615,7 @@ collect_media_stats (GstRTSPMedia * media) * * Ownership is taken of @element. * - * Returns: a new #GstRTSPMedia object. + * Returns: (transfer full): a new #GstRTSPMedia object. */ GstRTSPMedia * gst_rtsp_media_new (GstElement * element) @@ -400,7 +630,23 @@ gst_rtsp_media_new (GstElement * element) } /** - * gst_rtsp_media_take_element: + * gst_rtsp_media_get_element: + * @media: a #GstRTSPMedia + * + * Get the element that was used when constructing @media. + * + * Returns: (transfer full): a #GstElement. Unref after usage. + */ +GstElement * +gst_rtsp_media_get_element (GstRTSPMedia * media) +{ + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + + return gst_object_ref (media->priv->element); +} + +/** + * gst_rtsp_media_take_pipeline: * @media: a #GstRTSPMedia * @pipeline: (transfer full): a #GstPipeline * @@ -412,6 +658,7 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) { GstRTSPMediaPrivate *priv; GstElement *old; + GstNetTimeProvider *nettime; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); g_return_if_fail (GST_IS_PIPELINE (pipeline)); @@ -421,16 +668,131 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) g_mutex_lock (&priv->lock); old = priv->pipeline; priv->pipeline = GST_ELEMENT_CAST (pipeline); + nettime = priv->nettime; + priv->nettime = NULL; g_mutex_unlock (&priv->lock); if (old) gst_object_unref (old); - gst_object_ref (priv->element); + if (nettime) + gst_object_unref (nettime); + gst_bin_add (GST_BIN_CAST (pipeline), priv->element); } /** + * gst_rtsp_media_set_permissions: + * @media: a #GstRTSPMedia + * @permissions: (transfer none): a #GstRTSPPermissions + * + * Set @permissions on @media. + */ +void +gst_rtsp_media_set_permissions (GstRTSPMedia * media, + GstRTSPPermissions * permissions) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + if (priv->permissions) + gst_rtsp_permissions_unref (priv->permissions); + if ((priv->permissions = permissions)) + gst_rtsp_permissions_ref (permissions); + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_permissions: + * @media: a #GstRTSPMedia + * + * Get the permissions object from @media. + * + * Returns: (transfer full): a #GstRTSPPermissions object, unref after usage. + */ +GstRTSPPermissions * +gst_rtsp_media_get_permissions (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + GstRTSPPermissions *result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + if ((result = priv->permissions)) + gst_rtsp_permissions_ref (result); + g_mutex_unlock (&priv->lock); + + return result; +} + +/** + * gst_rtsp_media_set_suspend_mode: + * @media: a #GstRTSPMedia + * @mode: the new #GstRTSPSuspendMode + * + * Control how @ media will be suspended after the SDP has been generated and + * after a PAUSE request has been performed. + * + * Media must be unprepared when setting the suspend mode. + */ +void +gst_rtsp_media_set_suspend_mode (GstRTSPMedia * media, GstRTSPSuspendMode mode) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) + goto was_prepared; + priv->suspend_mode = mode; + g_rec_mutex_unlock (&priv->state_lock); + + return; + + /* ERRORS */ +was_prepared: + { + GST_WARNING ("media %p was prepared", media); + g_rec_mutex_unlock (&priv->state_lock); + } +} + +/** + * gst_rtsp_media_get_suspend_mode: + * @media: a #GstRTSPMedia + * + * Get how @media will be suspended. + * + * Returns: #GstRTSPSuspendMode. + */ +GstRTSPSuspendMode +gst_rtsp_media_get_suspend_mode (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + GstRTSPSuspendMode res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_SUSPEND_MODE_NONE); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + res = priv->suspend_mode; + g_rec_mutex_unlock (&priv->state_lock); + + return res; +} + +/** * gst_rtsp_media_set_shared: * @media: a #GstRTSPMedia * @shared: the new value @@ -525,6 +887,65 @@ gst_rtsp_media_is_reusable (GstRTSPMedia * media) return res; } +static void +do_set_profiles (GstRTSPStream * stream, GstRTSPProfile * profiles) +{ + gst_rtsp_stream_set_profiles (stream, *profiles); +} + +/** + * gst_rtsp_media_set_profiles: + * @media: a #GstRTSPMedia + * @profiles: the new flags + * + * Configure the allowed lower transport for @media. + */ +void +gst_rtsp_media_set_profiles (GstRTSPMedia * media, GstRTSPProfile profiles) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->profiles = profiles; + g_ptr_array_foreach (priv->streams, (GFunc) do_set_profiles, &profiles); + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_profiles: + * @media: a #GstRTSPMedia + * + * Get the allowed profiles of @media. + * + * Returns: a #GstRTSPProfile + */ +GstRTSPProfile +gst_rtsp_media_get_profiles (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + GstRTSPProfile res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_PROFILE_UNKNOWN); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->profiles; + g_mutex_unlock (&priv->lock); + + return res; +} + +static void +do_set_protocols (GstRTSPStream * stream, GstRTSPLowerTrans * protocols) +{ + gst_rtsp_stream_set_protocols (stream, *protocols); +} + /** * gst_rtsp_media_set_protocols: * @media: a #GstRTSPMedia @@ -543,6 +964,7 @@ gst_rtsp_media_set_protocols (GstRTSPMedia * media, GstRTSPLowerTrans protocols) g_mutex_lock (&priv->lock); priv->protocols = protocols; + g_ptr_array_foreach (priv->streams, (GFunc) do_set_protocols, &protocols); g_mutex_unlock (&priv->lock); } @@ -669,66 +1091,57 @@ gst_rtsp_media_get_buffer_size (GstRTSPMedia * media) } /** - * gst_rtsp_media_set_auth: + * gst_rtsp_media_use_time_provider: * @media: a #GstRTSPMedia - * @auth: a #GstRTSPAuth + * @time_provider: if a #GstNetTimeProvider should be used * - * configure @auth to be used as the authentication manager of @media. + * Set @media to provide a #GstNetTimeProvider. */ void -gst_rtsp_media_set_auth (GstRTSPMedia * media, GstRTSPAuth * auth) +gst_rtsp_media_use_time_provider (GstRTSPMedia * media, gboolean time_provider) { GstRTSPMediaPrivate *priv; - GstRTSPAuth *old; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; - GST_LOG_OBJECT (media, "set auth %p", auth); - g_mutex_lock (&priv->lock); - if ((old = priv->auth) != auth) - priv->auth = auth ? g_object_ref (auth) : NULL; - else - old = NULL; + priv->time_provider = time_provider; g_mutex_unlock (&priv->lock); - - if (old) - g_object_unref (old); } /** - * gst_rtsp_media_get_auth: + * gst_rtsp_media_is_time_provider: * @media: a #GstRTSPMedia * - * Get the #GstRTSPAuth used as the authentication manager of @media. + * Check if @media can provide a #GstNetTimeProvider for its pipeline clock. * - * Returns: (transfer full): the #GstRTSPAuth of @media. g_object_unref() after - * usage. + * Use gst_rtsp_media_get_time_provider() to get the network clock. + * + * Returns: %TRUE if @media can provide a #GstNetTimeProvider. */ -GstRTSPAuth * -gst_rtsp_media_get_auth (GstRTSPMedia * media) +gboolean +gst_rtsp_media_is_time_provider (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; - GstRTSPAuth *result; + gboolean res; - g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; - g_mutex_lock (&priv->lock); - if ((result = priv->auth)) - g_object_ref (result); + g_mutex_unlock (&priv->lock); + res = priv->time_provider; g_mutex_unlock (&priv->lock); - return result; + return res; } /** * gst_rtsp_media_set_address_pool: * @media: a #GstRTSPMedia - * @pool: a #GstRTSPAddressPool + * @pool: (transfer none): a #GstRTSPAddressPool * * configure @pool to be used as the address pool of @media. */ @@ -789,10 +1202,10 @@ gst_rtsp_media_get_address_pool (GstRTSPMedia * media) * gst_rtsp_media_collect_streams: * @media: a #GstRTSPMedia * - * Find all payloader elements, they should be named pay%d in the + * Find all payloader elements, they should be named pay\%d in the * element of @media, and create #GstRTSPStreams for them. * - * Collect all dynamic elements, named dynpay%d, and add them to + * Collect all dynamic elements, named dynpay\%d, and add them to * the list of dynamic elements. */ void @@ -833,7 +1246,6 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media) name = g_strdup_printf ("dynpay%d", i); if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) { /* a stream that will dynamically create pads to provide RTP packets */ - GST_INFO ("found dynamic element %d, %p", i, elem); g_mutex_lock (&priv->lock); @@ -856,7 +1268,7 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media) * @srcpad should be a pad of an element inside @media->element. * * Returns: (transfer none): a new #GstRTSPStream that remains valid for as long - * as @media exists. + * as @media exists. */ GstRTSPStream * gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, @@ -889,6 +1301,8 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, stream = gst_rtsp_stream_new (idx, payloader, srcpad); if (priv->pool) gst_rtsp_stream_set_address_pool (stream, priv->pool); + gst_rtsp_stream_set_profiles (stream, priv->profiles); + gst_rtsp_stream_set_protocols (stream, priv->protocols); g_ptr_array_add (priv->streams, stream); g_mutex_unlock (&priv->lock); @@ -899,6 +1313,30 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, return stream; } +static void +gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream) +{ + GstRTSPMediaPrivate *priv; + GstPad *srcpad; + + priv = media->priv; + + g_mutex_lock (&priv->lock); + /* remove the ghostpad */ + srcpad = gst_rtsp_stream_get_srcpad (stream); + gst_element_remove_pad (priv->element, srcpad); + gst_object_unref (srcpad); + /* now remove the stream */ + g_object_ref (stream); + g_ptr_array_remove (priv->streams, stream); + g_mutex_unlock (&priv->lock); + + g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM], 0, + stream, NULL); + + g_object_unref (stream); +} + /** * gst_rtsp_media_n_streams: * @media: a #GstRTSPMedia @@ -931,8 +1369,8 @@ gst_rtsp_media_n_streams (GstRTSPMedia * media) * * Retrieve the stream with index @idx from @media. * - * Returns: (transfer none): the #GstRTSPStream at index @idx or %NULL when a stream with - * that index did not exist. + * Returns: (nullable) (transfer none): the #GstRTSPStream at index + * @idx or %NULL when a stream with that index did not exist. */ GstRTSPStream * gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx) @@ -955,31 +1393,89 @@ gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx) } /** - * gst_rtsp_media_get_range_string: + * gst_rtsp_media_find_stream: * @media: a #GstRTSPMedia - * @play: for the PLAY request + * @control: the control of the stream * - * Get the current range as a string. @media must be prepared with - * gst_rtsp_media_prepare (). + * Find a stream in @media with @control as the control uri. * - * Returns: The range as a string, g_free() after usage. + * Returns: (nullable) (transfer none): the #GstRTSPStream with + * control uri @control or %NULL when a stream with that control did + * not exist. */ -gchar * -gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play) +GstRTSPStream * +gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control) { GstRTSPMediaPrivate *priv; - gchar *result; - GstRTSPTimeRange range; + GstRTSPStream *res; + gint i; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + g_return_val_if_fail (control != NULL, NULL); + + priv = media->priv; + + res = NULL; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *test; + + test = g_ptr_array_index (priv->streams, i); + if (gst_rtsp_stream_has_control (test, control)) { + res = test; + break; + } + } + g_mutex_unlock (&priv->lock); + + return res; +} + +/* called with state-lock */ +static gboolean +default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, + GstRTSPRangeUnit unit) +{ + return gst_rtsp_range_convert_units (range, unit); +} + +/** + * gst_rtsp_media_get_range_string: + * @media: a #GstRTSPMedia + * @play: for the PLAY request + * @unit: the unit to use for the string + * + * Get the current range as a string. @media must be prepared with + * gst_rtsp_media_prepare (). + * + * Returns: (transfer full): The range as a string, g_free() after usage. + */ +gchar * +gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play, + GstRTSPRangeUnit unit) +{ + GstRTSPMediaClass *klass; + GstRTSPMediaPrivate *priv; + gchar *result; + GstRTSPTimeRange range; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + g_return_val_if_fail (klass->convert_range != NULL, FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); - if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && + priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) goto not_prepared; g_mutex_lock (&priv->lock); + + /* Update the range value with current position/duration */ + collect_media_stats (media); + /* make copy */ range = priv->range; @@ -990,6 +1486,9 @@ gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play) g_mutex_unlock (&priv->lock); g_rec_mutex_unlock (&priv->state_lock); + if (!klass->convert_range (media, &range, unit)) + goto conversion_failed; + result = gst_rtsp_range_to_string (&range); return result; @@ -1001,12 +1500,79 @@ not_prepared: g_rec_mutex_unlock (&priv->state_lock); return NULL; } +conversion_failed: + { + GST_WARNING ("range conversion to unit %d failed", unit); + return NULL; + } +} + +static void +stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media) +{ + gst_rtsp_stream_set_blocked (stream, media->priv->blocked); +} + +static void +media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked) +{ + GstRTSPMediaPrivate *priv = media->priv; + + GST_DEBUG ("media %p set blocked %d", media, blocked); + priv->blocked = blocked; + g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media); +} + +static void +gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) +{ + GstRTSPMediaPrivate *priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->status = status; + GST_DEBUG ("setting new status to %d", status); + g_cond_broadcast (&priv->cond); + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_status: + * @media: a #GstRTSPMedia + * + * Get the status of @media. When @media is busy preparing, this function waits + * until @media is prepared or in error. + * + * Returns: the status of @media. + */ +GstRTSPMediaStatus +gst_rtsp_media_get_status (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPMediaStatus result; + gint64 end_time; + + g_mutex_lock (&priv->lock); + end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND; + /* while we are preparing, wait */ + while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) { + GST_DEBUG ("waiting for status change"); + if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) { + GST_DEBUG ("timeout, assuming error status"); + priv->status = GST_RTSP_MEDIA_STATUS_ERROR; + } + } + /* could be success or error */ + result = priv->status; + GST_DEBUG ("got status %d", result); + g_mutex_unlock (&priv->lock); + + return result; } /** * gst_rtsp_media_seek: * @media: a #GstRTSPMedia - * @range: a #GstRTSPTimeRange + * @range: (transfer none): a #GstRTSPTimeRange * * Seek the pipeline of @media to @range. @media must be prepared with * gst_rtsp_media_prepare(). @@ -1016,14 +1582,18 @@ not_prepared: gboolean gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) { + GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; - GstSeekFlags flags; gboolean res; GstClockTime start, stop; GstSeekType start_type, stop_type; + GstQuery *query; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); g_return_val_if_fail (range != NULL, FALSE); + g_return_val_if_fail (klass->convert_range != NULL, FALSE); priv = media->priv; @@ -1031,26 +1601,33 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) goto not_prepared; + /* Update the seekable state of the pipeline in case it changed */ + query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_element_query (priv->pipeline, query)) { + GstFormat format; + gboolean seekable; + gint64 start, end; + + gst_query_parse_seeking (query, &format, &seekable, &start, &end); + priv->seekable = seekable; + } + gst_query_unref (query); + if (!priv->seekable) goto not_seekable; - /* depends on the current playing state of the pipeline. We might need to - * queue this until we get EOS. */ - flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_KEY_UNIT; - start_type = stop_type = GST_SEEK_TYPE_NONE; - if (!gst_rtsp_range_get_times (range, &start, &stop)) + if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT)) goto not_supported; + gst_rtsp_range_get_times (range, &start, &stop); GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop)); - if (priv->range_start == start) - start = GST_CLOCK_TIME_NONE; - else if (start != GST_CLOCK_TIME_NONE) + if (start != GST_CLOCK_TIME_NONE) start_type = GST_SEEK_TYPE_SET; if (priv->range_stop == stop) @@ -1059,18 +1636,58 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) stop_type = GST_SEEK_TYPE_SET; if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) { + GstSeekFlags flags; + GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + if (priv->blocked) + media_streams_set_blocked (media, TRUE); + + /* depends on the current playing state of the pipeline. We might need to + * queue this until we get EOS. */ + flags = GST_SEEK_FLAG_FLUSH; + + /* if range start was not supplied we must continue from current position. + * but since we're doing a flushing seek, let us query the current position + * so we end up at exactly the same position after the seek. */ + if (range->min.type == GST_RTSP_TIME_END) { /* Yepp, that's right! */ + gint64 position; + gboolean ret = FALSE; + + if (klass->query_position) + ret = klass->query_position (media, &position); + + if (!ret) { + GST_WARNING ("position query failed"); + } else { + GST_DEBUG ("doing accurate seek to %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + start = position; + start_type = GST_SEEK_TYPE_SET; + flags |= GST_SEEK_FLAG_ACCURATE; + } + } else { + /* only set keyframe flag when modifying start */ + if (start_type != GST_SEEK_TYPE_NONE) + flags |= GST_SEEK_FLAG_KEY_UNIT; + } + + /* FIXME, we only do forwards playback, no trick modes yet */ res = gst_element_seek (priv->pipeline, 1.0, GST_FORMAT_TIME, flags, start_type, start, stop_type, stop); /* and block for the seek to complete */ GST_INFO ("done seeking %d", res); - gst_element_get_state (priv->pipeline, NULL, NULL, -1); - GST_INFO ("prerolled again"); + g_rec_mutex_unlock (&priv->state_lock); - collect_media_stats (media); + /* wait until pipeline is prerolled again, this will also collect stats */ + if (!wait_preroll (media)) + goto preroll_failed; + + g_rec_mutex_lock (&priv->state_lock); + GST_INFO ("prerolled again"); } else { GST_INFO ("no seek needed"); res = TRUE; @@ -1090,60 +1707,70 @@ not_seekable: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("pipeline is not seekable"); - return TRUE; + return FALSE; } not_supported: { g_rec_mutex_unlock (&priv->state_lock); - GST_WARNING ("seek unit %d not supported", range->unit); + GST_WARNING ("conversion to npt not supported"); + return FALSE; + } +preroll_failed: + { + GST_WARNING ("failed to preroll after seek"); return FALSE; } } static void -gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) +stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked) +{ + *blocked &= gst_rtsp_stream_is_blocking (stream); +} + +static gboolean +media_streams_blocking (GstRTSPMedia * media) +{ + gboolean blocking = TRUE; + + g_ptr_array_foreach (media->priv->streams, (GFunc) stream_collect_blocking, + &blocking); + + return blocking; +} + +static GstStateChangeReturn +set_state (GstRTSPMedia * media, GstState state) { GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn ret; - g_mutex_lock (&priv->lock); - priv->status = status; - GST_DEBUG ("setting new status to %d", status); - g_cond_broadcast (&priv->cond); - g_mutex_unlock (&priv->lock); + GST_INFO ("set state to %s for media %p", gst_element_state_get_name (state), + media); + ret = gst_element_set_state (priv->pipeline, state); + + return ret; } -/** - * gst_rtsp_media_get_status: - * @media: a #GstRTSPMedia - * - * Get the status of @media. When @media is busy preparing, this function waits - * until @media is prepared or in error. - * - * Returns: the status of @media. - */ -GstRTSPMediaStatus -gst_rtsp_media_get_status (GstRTSPMedia * media) +static GstStateChangeReturn +set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state) { GstRTSPMediaPrivate *priv = media->priv; - GstRTSPMediaStatus result; - gint64 end_time; + GstStateChangeReturn ret; - g_mutex_lock (&priv->lock); - end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND; - /* while we are preparing, wait */ - while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) { - GST_DEBUG ("waiting for status change"); - if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) { - GST_DEBUG ("timeout, assuming error status"); - priv->status = GST_RTSP_MEDIA_STATUS_ERROR; - } - } - /* could be success or error */ - result = priv->status; - GST_DEBUG ("got status %d", result); - g_mutex_unlock (&priv->lock); + GST_INFO ("set target state to %s for media %p", + gst_element_state_get_name (state), media); + priv->target_state = state; - return result; + g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_TARGET_STATE], 0, + priv->target_state, NULL); + + if (do_state) + ret = set_state (media, state); + else + ret = GST_STATE_CHANGE_SUCCESS; + + return ret; } /* called with state-lock */ @@ -1174,7 +1801,7 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) /* if the desired state is playing, go back */ if (priv->target_state == GST_STATE_PLAYING) { GST_INFO ("Buffering done, setting pipeline to PLAYING"); - gst_element_set_state (priv->pipeline, GST_STATE_PLAYING); + set_state (media, GST_STATE_PLAYING); } else { GST_INFO ("Buffering done"); } @@ -1184,7 +1811,7 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) if (priv->target_state == GST_STATE_PLAYING) { /* we were not buffering but PLAYING, PAUSE the pipeline. */ GST_INFO ("Buffering, setting pipeline to PAUSED ..."); - gst_element_set_state (priv->pipeline, GST_STATE_PAUSED); + set_state (media, GST_STATE_PAUSED); } else { GST_INFO ("Buffering ..."); } @@ -1223,21 +1850,36 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) break; } case GST_MESSAGE_ELEMENT: + { + const GstStructure *s; + + s = gst_message_get_structure (message); + if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) { + GST_DEBUG ("media received blocking message"); + if (priv->blocked && media_streams_blocking (media)) { + GST_DEBUG ("media is blocking"); + collect_media_stats (media); + + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + } + } break; + } case GST_MESSAGE_STREAM_STATUS: break; case GST_MESSAGE_ASYNC_DONE: - if (!priv->adding) { + if (priv->adding) { /* when we are dynamically adding pads, the addition of the udpsrc will * temporarily produce ASYNC_DONE messages. We have to ignore them and * wait for the final ASYNC_DONE after everything prerolled */ + GST_INFO ("%p: ignoring ASYNC_DONE", media); + } else { GST_INFO ("%p: got ASYNC_DONE", media); collect_media_stats (media); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); - } else { - GST_INFO ("%p: ignoring ASYNC_DONE", media); } break; case GST_MESSAGE_EOS: @@ -1282,19 +1924,62 @@ watch_destroyed (GstRTSPMedia * media) g_object_unref (media); } +static GstElement * +find_payload_element (GstElement * payloader) +{ + GstElement *pay = NULL; + + if (GST_IS_BIN (payloader)) { + GstIterator *iter; + GValue item = { 0 }; + + iter = gst_bin_iterate_recurse (GST_BIN (payloader)); + while (gst_iterator_next (iter, &item) == GST_ITERATOR_OK) { + GstElement *element = (GstElement *) g_value_get_object (&item); + GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element); + const gchar *klass; + + klass = + gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS); + if (klass == NULL) + continue; + + if (strstr (klass, "Payloader") && strstr (klass, "RTP")) { + pay = gst_object_ref (element); + g_value_unset (&item); + break; + } + g_value_unset (&item); + } + gst_iterator_free (iter); + } else { + pay = g_object_ref (payloader); + } + + return pay; +} + /* called from streaming threads */ static void pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPStream *stream; + GstElement *pay; - /* FIXME, element is likely not a payloader, find the payloader here */ - stream = gst_rtsp_media_create_stream (media, element, pad); + /* find the real payload element */ + pay = find_payload_element (element); + stream = gst_rtsp_media_create_stream (media, pay, pad); + gst_object_unref (pay); GST_INFO ("pad added %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream); g_rec_mutex_lock (&priv->state_lock); + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) + goto not_preparing; + + g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream); + /* we will be adding elements below that will cause ASYNC_DONE to be * posted in the bus. We want to ignore those messages until the * pipeline really prerolled. */ @@ -1302,26 +1987,57 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) /* join the element in the PAUSED state because this callback is * called from the streaming thread and it is PAUSED */ - gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), - priv->rtpbin, GST_STATE_PAUSED); + if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), + priv->rtpbin, GST_STATE_PAUSED)) { + GST_WARNING ("failed to join bin element"); + } priv->adding = FALSE; g_rec_mutex_unlock (&priv->state_lock); + + return; + + /* ERRORS */ +not_preparing: + { + gst_rtsp_media_remove_stream (media, stream); + g_rec_mutex_unlock (&priv->state_lock); + GST_INFO ("ignore pad because we are not preparing"); + return; + } } static void -no_more_pads_cb (GstElement * element, GstRTSPMedia * media) +pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; + GstRTSPStream *stream; + + stream = g_object_get_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream"); + if (stream == NULL) + return; + + GST_INFO ("pad removed %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream); + + g_rec_mutex_lock (&priv->state_lock); + gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin); + g_rec_mutex_unlock (&priv->state_lock); + + gst_rtsp_media_remove_stream (media, stream); +} + +static void +remove_fakesink (GstRTSPMediaPrivate * priv) +{ GstElement *fakesink; g_mutex_lock (&priv->lock); - GST_INFO ("no more pads"); - if ((fakesink = priv->fakesink)) { + if ((fakesink = priv->fakesink)) gst_object_ref (fakesink); - priv->fakesink = NULL; - g_mutex_unlock (&priv->lock); + priv->fakesink = NULL; + g_mutex_unlock (&priv->lock); + if (fakesink) { gst_bin_remove (GST_BIN (priv->pipeline), fakesink); gst_element_set_state (fakesink, GST_STATE_NULL); gst_object_unref (fakesink); @@ -1329,75 +2045,96 @@ no_more_pads_cb (GstElement * element, GstRTSPMedia * media) } } -/** - * gst_rtsp_media_prepare: - * @media: a #GstRTSPMedia - * - * Prepare @media for streaming. This function will create the pipeline and - * other objects to manage the streaming. - * - * It will preroll the pipeline and collect vital information about the streams - * such as the duration. - * - * Returns: %TRUE on success. - */ -gboolean -gst_rtsp_media_prepare (GstRTSPMedia * media) +static void +no_more_pads_cb (GstElement * element, GstRTSPMedia * media) { - GstRTSPMediaPrivate *priv; - GstStateChangeReturn ret; - GstRTSPMediaStatus status; - guint i; - GstRTSPMediaClass *klass; - GstBus *bus; - GList *walk; + GstRTSPMediaPrivate *priv = media->priv; - g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + GST_INFO ("no more pads"); + remove_fakesink (priv); +} - priv = media->priv; +typedef struct _DynPaySignalHandlers DynPaySignalHandlers; - g_rec_mutex_lock (&priv->state_lock); - priv->prepare_count++; +struct _DynPaySignalHandlers +{ + gulong pad_added_handler; + gulong pad_removed_handler; + gulong no_more_pads_handler; +}; - if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) - goto was_prepared; - - if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) - goto wait_status; +static gboolean +start_preroll (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn ret; - if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED) - goto not_unprepared; + GST_INFO ("setting pipeline to PAUSED for media %p", media); + /* first go to PAUSED */ + ret = set_target_state (media, GST_STATE_PAUSED, TRUE); - if (!priv->reusable && priv->reused) - goto is_reused; + switch (ret) { + case GST_STATE_CHANGE_SUCCESS: + GST_INFO ("SUCCESS state change for media %p", media); + priv->seekable = TRUE; + break; + case GST_STATE_CHANGE_ASYNC: + GST_INFO ("ASYNC state change for media %p", media); + priv->seekable = TRUE; + break; + case GST_STATE_CHANGE_NO_PREROLL: + /* we need to go to PLAYING */ + GST_INFO ("NO_PREROLL state change: live media %p", media); + /* FIXME we disable seeking for live streams for now. We should perform a + * seeking query in preroll instead */ + priv->seekable = FALSE; + priv->is_live = TRUE; + /* start blocked to make sure nothing goes to the sink */ + media_streams_set_blocked (media, TRUE); + ret = set_state (media, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) + goto state_failed; + break; + case GST_STATE_CHANGE_FAILURE: + goto state_failed; + } - priv->rtpbin = gst_element_factory_make ("rtpbin", NULL); - if (priv->rtpbin == NULL) - goto no_rtpbin; + return TRUE; - GST_INFO ("preparing media %p", media); +state_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +} - /* reset some variables */ - priv->is_live = FALSE; - priv->seekable = FALSE; - priv->buffering = FALSE; - /* we're preparing now */ - priv->status = GST_RTSP_MEDIA_STATUS_PREPARING; +static gboolean +wait_preroll (GstRTSPMedia * media) +{ + GstRTSPMediaStatus status; - bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline)); + GST_DEBUG ("wait to preroll pipeline"); - /* add the pipeline bus to our custom mainloop */ - priv->source = gst_bus_create_watch (bus); - gst_object_unref (bus); + /* wait until pipeline is prerolled */ + status = gst_rtsp_media_get_status (media); + if (status == GST_RTSP_MEDIA_STATUS_ERROR) + goto preroll_failed; - g_source_set_callback (priv->source, (GSourceFunc) bus_message, - g_object_ref (media), (GDestroyNotify) watch_destroyed); + return TRUE; - klass = GST_RTSP_MEDIA_GET_CLASS (media); - priv->id = g_source_attach (priv->source, klass->context); +preroll_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +} - /* add stuff to the bin */ - gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin); +static gboolean +start_prepare (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + guint i; + GList *walk; /* link streams we already have, other streams might appear when we have * dynamic elements */ @@ -1406,17 +2143,26 @@ gst_rtsp_media_prepare (GstRTSPMedia * media) stream = g_ptr_array_index (priv->streams, i); - gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), - priv->rtpbin, GST_STATE_NULL); + if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), + priv->rtpbin, GST_STATE_NULL)) { + goto join_bin_failed; + } } for (walk = priv->dynamic; walk; walk = g_list_next (walk)) { GstElement *elem = walk->data; + DynPaySignalHandlers *handlers = g_slice_new (DynPaySignalHandlers); GST_INFO ("adding callbacks for dynamic element %p", elem); - g_signal_connect (elem, "pad-added", (GCallback) pad_added_cb, media); - g_signal_connect (elem, "no-more-pads", (GCallback) no_more_pads_cb, media); + handlers->pad_added_handler = g_signal_connect (elem, "pad-added", + (GCallback) pad_added_cb, media); + handlers->pad_removed_handler = g_signal_connect (elem, "pad-removed", + (GCallback) pad_removed_cb, media); + handlers->no_more_pads_handler = g_signal_connect (elem, "no-more-pads", + (GCallback) no_more_pads_cb, media); + + g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers); /* we add a fakesink here in order to make the state change async. We remove * the fakesink again in the no-more-pads callback. */ @@ -1424,42 +2170,160 @@ gst_rtsp_media_prepare (GstRTSPMedia * media) gst_bin_add (GST_BIN (priv->pipeline), priv->fakesink); } - GST_INFO ("setting pipeline to PAUSED for media %p", media); - /* first go to PAUSED */ - ret = gst_element_set_state (priv->pipeline, GST_STATE_PAUSED); - priv->target_state = GST_STATE_PAUSED; + if (!start_preroll (media)) + goto preroll_failed; - switch (ret) { - case GST_STATE_CHANGE_SUCCESS: - GST_INFO ("SUCCESS state change for media %p", media); - priv->seekable = TRUE; - break; - case GST_STATE_CHANGE_ASYNC: - GST_INFO ("ASYNC state change for media %p", media); - priv->seekable = TRUE; - break; - case GST_STATE_CHANGE_NO_PREROLL: - /* we need to go to PLAYING */ - GST_INFO ("NO_PREROLL state change: live media %p", media); - /* FIXME we disable seeking for live streams for now. We should perform a - * seeking query in preroll instead */ - priv->seekable = FALSE; - priv->is_live = TRUE; - ret = gst_element_set_state (priv->pipeline, GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) - goto state_failed; - break; - case GST_STATE_CHANGE_FAILURE: - goto state_failed; + return FALSE; + +join_bin_failed: + { + GST_WARNING ("failed to join bin element"); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + return FALSE; + } +preroll_failed: + { + GST_WARNING ("failed to preroll pipeline"); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + return FALSE; } +} + +static gboolean +default_prepare (GstRTSPMedia * media, GstRTSPThread * thread) +{ + GstRTSPMediaPrivate *priv; + GstRTSPMediaClass *klass; + GstBus *bus; + GMainContext *context; + GSource *source; + + priv = media->priv; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + + if (!klass->create_rtpbin) + goto no_create_rtpbin; + + priv->rtpbin = klass->create_rtpbin (media); + if (priv->rtpbin != NULL) { + gboolean success = TRUE; + + if (klass->setup_rtpbin) + success = klass->setup_rtpbin (media, priv->rtpbin); + + if (success == FALSE) { + gst_object_unref (priv->rtpbin); + priv->rtpbin = NULL; + } + } + if (priv->rtpbin == NULL) + goto no_rtpbin; + + priv->thread = thread; + context = (thread != NULL) ? (thread->context) : NULL; + + bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline)); + + /* add the pipeline bus to our custom mainloop */ + priv->source = gst_bus_create_watch (bus); + gst_object_unref (bus); + + g_source_set_callback (priv->source, (GSourceFunc) bus_message, + g_object_ref (media), (GDestroyNotify) watch_destroyed); + + priv->id = g_source_attach (priv->source, context); + + /* add stuff to the bin */ + gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin); + + /* do remainder in context */ + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL); + g_source_attach (source, context); + g_source_unref (source); + + return TRUE; + + /* ERRORS */ +no_create_rtpbin: + { + GST_ERROR ("no create_rtpbin function"); + g_critical ("no create_rtpbin vmethod function set"); + return FALSE; + } +no_rtpbin: + { + GST_WARNING ("no rtpbin element"); + g_warning ("failed to create element 'rtpbin', check your installation"); + return FALSE; + } +} + +/** + * gst_rtsp_media_prepare: + * @media: a #GstRTSPMedia + * @thread: (transfer full) (allow-none): a #GstRTSPThread to run the + * bus handler or %NULL + * + * Prepare @media for streaming. This function will create the objects + * to manage the streaming. A pipeline must have been set on @media with + * gst_rtsp_media_take_pipeline(). + * + * It will preroll the pipeline and collect vital information about the streams + * such as the duration. + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) +{ + GstRTSPMediaPrivate *priv; + GstRTSPMediaClass *klass; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + priv->prepare_count++; + + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED || + priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED) + goto was_prepared; + + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) + goto is_preparing; + + if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED) + goto not_unprepared; + + if (!priv->reusable && priv->reused) + goto is_reused; + + GST_INFO ("preparing media %p", media); + + /* reset some variables */ + priv->is_live = FALSE; + priv->seekable = FALSE; + priv->buffering = FALSE; + + /* we're preparing now */ + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + if (klass->prepare) { + if (!klass->prepare (media, thread)) + goto prepare_failed; + } + wait_status: g_rec_mutex_unlock (&priv->state_lock); /* now wait for all pads to be prerolled, FIXME, we should somehow be * able to do this async so that we don't block the server thread. */ - status = gst_rtsp_media_get_status (media); - if (status == GST_RTSP_MEDIA_STATUS_ERROR) - goto state_failed; + if (!wait_preroll (media)) + goto preroll_failed; g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARED], 0, NULL); @@ -1468,15 +2332,28 @@ wait_status: return TRUE; /* OK */ +is_preparing: + { + /* we are not going to use the giving thread, so stop it. */ + if (thread) + gst_rtsp_thread_stop (thread); + goto wait_status; + } was_prepared: { GST_LOG ("media %p was prepared", media); + /* we are not going to use the giving thread, so stop it. */ + if (thread) + gst_rtsp_thread_stop (thread); g_rec_mutex_unlock (&priv->state_lock); return TRUE; } /* ERRORS */ not_unprepared: { + /* we are not going to use the giving thread, so stop it. */ + if (thread) + gst_rtsp_thread_stop (thread); GST_WARNING ("media %p was not unprepared", media); priv->prepare_count--; g_rec_mutex_unlock (&priv->state_lock); @@ -1484,24 +2361,28 @@ not_unprepared: } is_reused: { + /* we are not going to use the giving thread, so stop it. */ + if (thread) + gst_rtsp_thread_stop (thread); priv->prepare_count--; g_rec_mutex_unlock (&priv->state_lock); GST_WARNING ("can not reuse media %p", media); return FALSE; } -no_rtpbin: +prepare_failed: { + /* we are not going to use the giving thread, so stop it. */ + if (thread) + gst_rtsp_thread_stop (thread); priv->prepare_count--; g_rec_mutex_unlock (&priv->state_lock); - GST_WARNING ("no rtpbin element"); - g_warning ("failed to create element 'rtpbin', check your installation"); + GST_ERROR ("failed to prepare media"); return FALSE; } -state_failed: +preroll_failed: { GST_WARNING ("failed to preroll pipeline"); gst_rtsp_media_unprepare (media); - g_rec_mutex_unlock (&priv->state_lock); return FALSE; } } @@ -1512,10 +2393,16 @@ finish_unprepare (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; gint i; + GList *walk; GST_DEBUG ("shutting down"); - gst_element_set_state (priv->pipeline, GST_STATE_NULL); + /* release the lock on shutdown, otherwise pad_added_cb might try to + * acquire the lock and then we deadlock */ + g_rec_mutex_unlock (&priv->state_lock); + set_state (media, GST_STATE_NULL); + g_rec_mutex_lock (&priv->state_lock); + remove_fakesink (priv); for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *stream; @@ -1526,16 +2413,34 @@ finish_unprepare (GstRTSPMedia * media) gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin); } - g_ptr_array_set_size (priv->streams, 0); + + /* remove the pad signal handlers */ + for (walk = priv->dynamic; walk; walk = g_list_next (walk)) { + GstElement *elem = walk->data; + DynPaySignalHandlers *handlers; + + handlers = + g_object_steal_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers"); + g_assert (handlers != NULL); + + g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_added_handler); + g_signal_handler_disconnect (G_OBJECT (elem), + handlers->pad_removed_handler); + g_signal_handler_disconnect (G_OBJECT (elem), + handlers->no_more_pads_handler); + + g_slice_free (DynPaySignalHandlers, handlers); + } gst_bin_remove (GST_BIN (priv->pipeline), priv->rtpbin); priv->rtpbin = NULL; - gst_object_unref (priv->pipeline); - priv->pipeline = NULL; + if (priv->nettime) + gst_object_unref (priv->nettime); + priv->nettime = NULL; priv->reused = TRUE; - priv->status = GST_RTSP_MEDIA_STATUS_UNPREPARED; + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARED); /* when the media is not reusable, this will effectively unref the media and * recreate it */ @@ -1547,6 +2452,10 @@ finish_unprepare (GstRTSPMedia * media) g_source_destroy (priv->source); g_source_unref (priv->source); } + if (priv->thread) { + GST_DEBUG ("stop thread"); + gst_rtsp_thread_stop (priv->thread); + } } /* called with state-lock */ @@ -1561,8 +2470,7 @@ default_unprepare (GstRTSPMedia * media) gst_element_send_event (priv->pipeline, gst_event_new_eos ()); /* we need to go to playing again for the EOS to propagate, normally in this * state, nothing is receiving data from us anymore so this is ok. */ - gst_element_set_state (priv->pipeline, GST_STATE_PLAYING); - priv->status = GST_RTSP_MEDIA_STATUS_UNPREPARING; + set_state (media, GST_STATE_PLAYING); } else { finish_unprepare (media); } @@ -1598,9 +2506,13 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media) goto is_busy; GST_INFO ("unprepare media %p", media); - priv->target_state = GST_STATE_NULL; + if (priv->blocked) + media_streams_set_blocked (media, FALSE); + set_target_state (media, GST_STATE_NULL, FALSE); success = TRUE; + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING); + if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) { GstRTSPMediaClass *klass; @@ -1628,11 +2540,429 @@ is_busy: } } +/* should be called with state-lock */ +static GstClock * +get_clock_unlocked (GstRTSPMedia * media) +{ + if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) { + GST_DEBUG_OBJECT (media, "media was not prepared"); + return NULL; + } + return gst_pipeline_get_clock (GST_PIPELINE_CAST (media->priv->pipeline)); +} + +/** + * gst_rtsp_media_get_clock: + * @media: a #GstRTSPMedia + * + * Get the clock that is used by the pipeline in @media. + * + * @media must be prepared before this method returns a valid clock object. + * + * Returns: (transfer full): the #GstClock used by @media. unref after usage. + */ +GstClock * +gst_rtsp_media_get_clock (GstRTSPMedia * media) +{ + GstClock *clock; + GstRTSPMediaPrivate *priv; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + clock = get_clock_unlocked (media); + g_rec_mutex_unlock (&priv->state_lock); + + return clock; +} + +/** + * gst_rtsp_media_get_base_time: + * @media: a #GstRTSPMedia + * + * Get the base_time that is used by the pipeline in @media. + * + * @media must be prepared before this method returns a valid base_time. + * + * Returns: the base_time used by @media. + */ +GstClockTime +gst_rtsp_media_get_base_time (GstRTSPMedia * media) +{ + GstClockTime result; + GstRTSPMediaPrivate *priv; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_CLOCK_TIME_NONE); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) + goto not_prepared; + + result = gst_element_get_base_time (media->priv->pipeline); + g_rec_mutex_unlock (&priv->state_lock); + + return result; + + /* ERRORS */ +not_prepared: + { + g_rec_mutex_unlock (&priv->state_lock); + GST_DEBUG_OBJECT (media, "media was not prepared"); + return GST_CLOCK_TIME_NONE; + } +} + +/** + * gst_rtsp_media_get_time_provider: + * @media: a #GstRTSPMedia + * @address: (allow-none): an address or %NULL + * @port: a port or 0 + * + * Get the #GstNetTimeProvider for the clock used by @media. The time provider + * will listen on @address and @port for client time requests. + * + * Returns: (transfer full): the #GstNetTimeProvider of @media. + */ +GstNetTimeProvider * +gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address, + guint16 port) +{ + GstRTSPMediaPrivate *priv; + GstNetTimeProvider *provider = NULL; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + if (priv->time_provider) { + if ((provider = priv->nettime) == NULL) { + GstClock *clock; + + if (priv->time_provider && (clock = get_clock_unlocked (media))) { + provider = gst_net_time_provider_new (clock, address, port); + gst_object_unref (clock); + + priv->nettime = provider; + } + } + } + g_rec_mutex_unlock (&priv->state_lock); + + if (provider) + gst_object_ref (provider); + + return provider; +} + +static gboolean +default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info) +{ + return gst_rtsp_sdp_from_media (sdp, info, media); +} + +/** + * gst_rtsp_media_setup_sdp: + * @media: a #GstRTSPMedia + * @sdp: (transfer none): a #GstSDPMessage + * @info: (transfer none): a #GstSDPInfo + * + * Add @media specific info to @sdp. @info is used to configure the connection + * information in the SDP. + * + * Returns: TRUE on success. + */ +gboolean +gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, + GstSDPInfo * info) +{ + GstRTSPMediaPrivate *priv; + GstRTSPMediaClass *klass; + gboolean res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + g_return_val_if_fail (sdp != NULL, FALSE); + g_return_val_if_fail (info != NULL, FALSE); + + priv = media->priv; + + g_rec_mutex_lock (&priv->state_lock); + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + + if (!klass->setup_sdp) + goto no_setup_sdp; + + res = klass->setup_sdp (media, sdp, info); + + g_rec_mutex_unlock (&priv->state_lock); + + return res; + + /* ERRORS */ +no_setup_sdp: + { + g_rec_mutex_unlock (&priv->state_lock); + GST_ERROR ("no setup_sdp function"); + g_critical ("no setup_sdp vmethod function set"); + return FALSE; + } +} + +static void +do_set_seqnum (GstRTSPStream * stream) +{ + guint16 seq_num; + seq_num = gst_rtsp_stream_get_current_seqnum (stream); + gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1); +} + +/* call with state_lock */ +gboolean +default_suspend (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn ret; + + switch (priv->suspend_mode) { + case GST_RTSP_SUSPEND_MODE_NONE: + GST_DEBUG ("media %p no suspend", media); + break; + case GST_RTSP_SUSPEND_MODE_PAUSE: + GST_DEBUG ("media %p suspend to PAUSED", media); + ret = set_target_state (media, GST_STATE_PAUSED, TRUE); + if (ret == GST_STATE_CHANGE_FAILURE) + goto state_failed; + break; + case GST_RTSP_SUSPEND_MODE_RESET: + GST_DEBUG ("media %p suspend to NULL", media); + ret = set_target_state (media, GST_STATE_NULL, TRUE); + if (ret == GST_STATE_CHANGE_FAILURE) + goto state_failed; + /* Because payloader needs to set the sequence number as + * monotonic, we need to preserve the sequence number + * after pause. (otherwise going from pause to play, which + * is actually from NULL to PLAY will create a new sequence + * number. */ + g_ptr_array_foreach (priv->streams, (GFunc) do_set_seqnum, NULL); + break; + default: + break; + } + + /* let the streams do the state changes freely, if any */ + media_streams_set_blocked (media, FALSE); + + return TRUE; + + /* ERRORS */ +state_failed: + { + GST_WARNING ("failed changing pipeline's state for media %p", media); + return FALSE; + } +} + +/** + * gst_rtsp_media_suspend: + * @media: a #GstRTSPMedia + * + * Suspend @media. The state of the pipeline managed by @media is set to + * GST_STATE_NULL but all streams are kept. @media can be prepared again + * with gst_rtsp_media_unsuspend() + * + * @media must be prepared with gst_rtsp_media_prepare(); + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_suspend (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPMediaClass *klass; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + GST_FIXME ("suspend for dynamic pipelines needs fixing"); + + g_rec_mutex_lock (&priv->state_lock); + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) + goto not_prepared; + + /* don't attempt to suspend when something is busy */ + if (priv->n_active > 0) + goto done; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + if (klass->suspend) { + if (!klass->suspend (media)) + goto suspend_failed; + } + + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_SUSPENDED); +done: + g_rec_mutex_unlock (&priv->state_lock); + + return TRUE; + + /* ERRORS */ +not_prepared: + { + g_rec_mutex_unlock (&priv->state_lock); + GST_WARNING ("media %p was not prepared", media); + return FALSE; + } +suspend_failed: + { + g_rec_mutex_unlock (&priv->state_lock); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + GST_WARNING ("failed to suspend media %p", media); + return FALSE; + } +} + +/* call with state_lock */ +gboolean +default_unsuspend (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + + switch (priv->suspend_mode) { + case GST_RTSP_SUSPEND_MODE_NONE: + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + break; + case GST_RTSP_SUSPEND_MODE_PAUSE: + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + break; + case GST_RTSP_SUSPEND_MODE_RESET: + { + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + if (!start_preroll (media)) + goto start_failed; + g_rec_mutex_unlock (&priv->state_lock); + + if (!wait_preroll (media)) + goto preroll_failed; + + g_rec_mutex_lock (&priv->state_lock); + } + default: + break; + } + + return TRUE; + + /* ERRORS */ +start_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +preroll_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +} + +/** + * gst_rtsp_media_unsuspend: + * @media: a #GstRTSPMedia + * + * Unsuspend @media if it was in a suspended state. This method does nothing + * when the media was not in the suspended state. + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_unsuspend (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPMediaClass *klass; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + g_rec_mutex_lock (&priv->state_lock); + if (priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) + goto done; + + klass = GST_RTSP_MEDIA_GET_CLASS (media); + if (klass->unsuspend) { + if (!klass->unsuspend (media)) + goto unsuspend_failed; + } + +done: + g_rec_mutex_unlock (&priv->state_lock); + + return TRUE; + + /* ERRORS */ +unsuspend_failed: + { + g_rec_mutex_unlock (&priv->state_lock); + GST_WARNING ("failed to unsuspend media %p", media); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + return FALSE; + } +} + +/* must be called with state-lock */ +static void +media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) +{ + GstRTSPMediaPrivate *priv = media->priv; + + if (state == GST_STATE_NULL) { + gst_rtsp_media_unprepare (media); + } else { + GST_INFO ("state %s media %p", gst_element_state_get_name (state), media); + set_target_state (media, state, FALSE); + /* when we are buffering, don't update the state yet, this will be done + * when buffering finishes */ + if (priv->buffering) { + GST_INFO ("Buffering busy, delay state change"); + } else { + if (state == GST_STATE_PLAYING) + /* make sure pads are not blocking anymore when going to PLAYING */ + media_streams_set_blocked (media, FALSE); + + set_state (media, state); + + /* and suspend after pause */ + if (state == GST_STATE_PAUSED) + gst_rtsp_media_suspend (media); + } + } +} + +/** + * gst_rtsp_media_set_pipeline_state: + * @media: a #GstRTSPMedia + * @state: the target state of the pipeline + * + * Set the state of the pipeline managed by @media to @state + */ +void +gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state) +{ + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + g_rec_mutex_lock (&media->priv->state_lock); + media_set_pipeline_state_locked (media, state); + g_rec_mutex_unlock (&media->priv->state_lock); +} + /** * gst_rtsp_media_set_state: * @media: a #GstRTSPMedia * @state: the target state of the media - * @transports: a #GPtrArray of #GstRTSPStreamTransport pointers + * @transports: (transfer none) (element-type GstRtspServer.RTSPStreamTransport): + * a #GPtrArray of #GstRTSPStreamTransport pointers * * Set the state of @media to @state and for the transports in @transports. * @@ -1655,7 +2985,10 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, priv = media->priv; g_rec_mutex_lock (&priv->state_lock); - if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) + if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR) + goto error_status; + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && + priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) goto not_prepared; /* NULL and READY are the same */ @@ -1713,16 +3046,9 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, media, do_state); if (priv->target_state != state) { - if (do_state) { - if (state == GST_STATE_NULL) { - gst_rtsp_media_unprepare (media); - } else { - GST_INFO ("state %s media %p", gst_element_state_get_name (state), - media); - priv->target_state = state; - gst_element_set_state (priv->pipeline, state); - } - } + if (do_state) + media_set_pipeline_state_locked (media, state); + g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state, NULL); } @@ -1743,4 +3069,24 @@ not_prepared: g_rec_mutex_unlock (&priv->state_lock); return FALSE; } +error_status: + { + GST_WARNING ("media %p in error status while changing to state %d", + media, state); + if (state == GST_STATE_NULL) { + for (i = 0; i < transports->len; i++) { + GstRTSPStreamTransport *trans; + + /* we need a non-NULL entry in the array */ + trans = g_ptr_array_index (transports, i); + if (trans == NULL) + continue; + + gst_rtsp_stream_transport_set_active (trans, FALSE); + } + priv->n_active = 0; + } + g_rec_mutex_unlock (&priv->state_lock); + return FALSE; + } }