X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp-server%2Frtsp-media.c;h=6b81e254e5dd1bdcb465c02b636869dde684d2cb;hb=376488d8c7d0a92c56065070f9003e699533c3e5;hp=f37258dda55be3bb03dab148296321688909b7fc;hpb=050b16ad8437313a98944b8d9e4d36e1e659991d;p=platform%2Fupstream%2Fgstreamer.git diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index f37258d..6b81e25 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -80,12 +80,15 @@ struct _GstRTSPMediaPrivate /* protected by lock */ GstRTSPPermissions *permissions; gboolean shared; + gboolean suspend_mode; gboolean reusable; + GstRTSPProfile profiles; GstRTSPLowerTrans protocols; gboolean reused; gboolean eos_shutdown; guint buffer_size; GstRTSPAddressPool *pool; + gboolean blocked; GstElement *element; GRecMutex state_lock; /* locking order: state lock, lock */ @@ -121,9 +124,11 @@ struct _GstRTSPMediaPrivate }; #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 @@ -135,7 +140,9 @@ enum { PROP_0, PROP_SHARED, + PROP_SUSPEND_MODE, PROP_REUSABLE, + PROP_PROFILES, PROP_PROTOCOLS, PROP_EOS_SHUTDOWN, PROP_BUFFER_SIZE, @@ -150,6 +157,7 @@ enum SIGNAL_REMOVED_STREAM, SIGNAL_PREPARED, SIGNAL_UNPREPARED, + SIGNAL_TARGET_STATE, SIGNAL_NEW_STATE, SIGNAL_LAST }; @@ -166,16 +174,46 @@ static void gst_rtsp_media_finalize (GObject * obj); 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_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, - GstRTSPRangeUnit unit); +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 @@ -196,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, @@ -240,25 +288,35 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) 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); + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_INT); GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmedia", 0, "GstRTSPMedia"); 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 @@ -274,7 +332,9 @@ 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; @@ -292,6 +352,9 @@ 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); @@ -323,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; @@ -357,9 +426,15 @@ gst_rtsp_media_set_property (GObject * object, guint propid, 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; @@ -377,12 +452,92 @@ gst_rtsp_media_set_property (GObject * object, guint propid, } } +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) +{ + GstElement *rtpbin; + + rtpbin = gst_element_factory_make ("rtpbin", NULL); + + return rtpbin; +} + /* must be called with state lock */ static void collect_media_stats (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; - gint64 position, stop; + gint64 position = 0, stop = -1; if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) @@ -460,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) @@ -480,7 +635,7 @@ gst_rtsp_media_new (GstElement * element) * * Get the element that was used when constructing @media. * - * Returns: a #GstElement. Unref after usage. + * Returns: (transfer full): a #GstElement. Unref after usage. */ GstElement * gst_rtsp_media_get_element (GstRTSPMedia * media) @@ -529,7 +684,7 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) /** * gst_rtsp_media_set_permissions: * @media: a #GstRTSPMedia - * @permissions: a #GstRTSPPermissions + * @permissions: (transfer none): a #GstRTSPPermissions * * Set @permissions on @media. */ @@ -578,6 +733,66 @@ gst_rtsp_media_get_permissions (GstRTSPMedia * media) } /** + * 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 @@ -672,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 @@ -690,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); } @@ -866,7 +1141,7 @@ gst_rtsp_media_is_time_provider (GstRTSPMedia * media) /** * 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. */ @@ -927,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 @@ -971,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); @@ -994,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, @@ -1027,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); @@ -1093,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) @@ -1123,8 +1399,9 @@ gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx) * * Find a stream in @media with @control as the control uri. * - * Returns: (transfer none): the #GstRTSPStream with control uri @control - * or %NULL when a stream with that control did not exist. + * Returns: (nullable) (transfer none): the #GstRTSPStream with + * control uri @control or %NULL when a stream with that control did + * not exist. */ GstRTSPStream * gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control) @@ -1155,6 +1432,14 @@ gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control) 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 @@ -1164,7 +1449,7 @@ gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control) * Get the current range as a string. @media must be prepared with * gst_rtsp_media_prepare (). * - * Returns: The range as a string, g_free() after usage. + * Returns: (transfer full): The range as a string, g_free() after usage. */ gchar * gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play, @@ -1182,7 +1467,8 @@ gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play, 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); @@ -1221,10 +1507,72 @@ conversion_failed: } } +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(). @@ -1236,10 +1584,10 @@ 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); @@ -1253,13 +1601,21 @@ 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 (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT)) @@ -1271,9 +1627,7 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) 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) @@ -1282,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; @@ -1313,7 +1707,7 @@ not_seekable: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("pipeline is not seekable"); - return TRUE; + return FALSE; } not_supported: { @@ -1321,52 +1715,62 @@ not_supported: 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) { - 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); + *blocked &= gst_rtsp_stream_is_blocking (stream); } -/** - * 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 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; - 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 state to %s for media %p", gst_element_state_get_name (state), + media); + ret = gst_element_set_state (priv->pipeline, state); - return result; + return ret; +} + +static GstStateChangeReturn +set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn ret; + + GST_INFO ("set target state to %s for media %p", + gst_element_state_get_name (state), media); + priv->target_state = state; + + 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 */ @@ -1397,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"); } @@ -1407,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 ..."); } @@ -1446,7 +1850,22 @@ 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: @@ -1505,21 +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); - - g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream); + /* 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. */ @@ -1527,11 +1987,24 @@ 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 @@ -1591,10 +2064,75 @@ struct _DynPaySignalHandlers }; static gboolean -start_prepare (GstRTSPMedia * media) +start_preroll (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstStateChangeReturn ret; + + GST_INFO ("setting pipeline to PAUSED for media %p", media); + /* first go to PAUSED */ + ret = set_target_state (media, GST_STATE_PAUSED, TRUE); + + 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; + } + + return TRUE; + +state_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +} + +static gboolean +wait_preroll (GstRTSPMedia * media) +{ + GstRTSPMediaStatus status; + + GST_DEBUG ("wait to preroll pipeline"); + + /* wait until pipeline is prerolled */ + status = gst_rtsp_media_get_status (media); + if (status == GST_RTSP_MEDIA_STATUS_ERROR) + goto preroll_failed; + + return TRUE; + +preroll_failed: + { + GST_WARNING ("failed to preroll pipeline"); + return FALSE; + } +} + +static gboolean +start_prepare (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; guint i; GList *walk; @@ -1605,8 +2143,10 @@ start_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)) { @@ -1630,38 +2170,18 @@ start_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; - - 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; - } + if (!start_preroll (media)) + goto preroll_failed; return FALSE; -state_failed: +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); @@ -1669,10 +2189,82 @@ state_failed: } } +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: a #GstRTSPThread to run the bus handler or %NULL + * @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 @@ -1687,23 +2279,21 @@ gboolean gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) { GstRTSPMediaPrivate *priv; - GstRTSPMediaStatus status; - GstBus *bus; - GSource *source; + GstRTSPMediaClass *klass; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); - g_return_val_if_fail (GST_IS_RTSP_THREAD (thread), FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); priv->prepare_count++; - if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) + 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 wait_status; + goto is_preparing; if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED) goto not_unprepared; @@ -1711,48 +2301,29 @@ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) if (!priv->reusable && priv->reused) goto is_reused; - priv->rtpbin = gst_element_factory_make ("rtpbin", NULL); - if (priv->rtpbin == NULL) - goto no_rtpbin; - GST_INFO ("preparing media %p", media); /* reset some variables */ priv->is_live = FALSE; priv->seekable = FALSE; priv->buffering = FALSE; - priv->thread = thread; - /* we're preparing now */ - priv->status = GST_RTSP_MEDIA_STATUS_PREPARING; - - 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, thread->context); - /* add stuff to the bin */ - gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin); + /* we're preparing now */ + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); - /* do remainder in context */ - source = g_idle_source_new (); - g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL); - g_source_attach (source, thread->context); - g_source_unref (source); + 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); @@ -1761,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); @@ -1777,20 +2361,25 @@ 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); @@ -1808,7 +2397,11 @@ finish_unprepare (GstRTSPMedia * media) 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++) { @@ -1847,7 +2440,7 @@ finish_unprepare (GstRTSPMedia * media) 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 */ @@ -1877,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); } @@ -1914,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; @@ -1963,7 +2559,7 @@ get_clock_unlocked (GstRTSPMedia * media) * * @media must be prepared before this method returns a valid clock object. * - * Returns: the #GstClock used by @media. unref after usage. + * Returns: (transfer full): the #GstClock used by @media. unref after usage. */ GstClock * gst_rtsp_media_get_clock (GstRTSPMedia * media) @@ -2023,13 +2619,13 @@ not_prepared: /** * gst_rtsp_media_get_time_provider: * @media: a #GstRTSPMedia - * @address: an address or NULL + * @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: the #GstNetTimeProvider of @media. + * Returns: (transfer full): the #GstNetTimeProvider of @media. */ GstNetTimeProvider * gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address, @@ -2063,8 +2659,261 @@ gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address, return provider; } -void -gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state) +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; @@ -2072,22 +2921,48 @@ gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state) gst_rtsp_media_unprepare (media); } else { GST_INFO ("state %s media %p", gst_element_state_get_name (state), media); - priv->target_state = state; + 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 { - gst_element_set_state (priv->pipeline, state); + 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. * @@ -2110,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 */ @@ -2169,7 +3047,7 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, if (priv->target_state != state) { if (do_state) - gst_rtsp_media_set_pipeline_state (media, state); + media_set_pipeline_state_locked (media, state); g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state, NULL); @@ -2191,36 +3069,24 @@ not_prepared: g_rec_mutex_unlock (&priv->state_lock); return FALSE; } -} - -/* called with state-lock */ -static gboolean -default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, - GstRTSPRangeUnit unit) -{ - return gst_rtsp_range_convert_units (range, unit); -} - -static gboolean -default_query_position (GstRTSPMedia * media, gint64 * position) -{ - return gst_element_query_position (media->priv->pipeline, GST_FORMAT_TIME, - position); -} - -static gboolean -default_query_stop (GstRTSPMedia * media, gint64 * stop) -{ - GstQuery *query; - gboolean res; - - query = gst_query_new_segment (GST_FORMAT_TIME); - if ((res = gst_element_query (media->priv->pipeline, query))) { - GstFormat format; - gst_query_parse_segment (query, NULL, &format, NULL, stop); - if (format != GST_FORMAT_TIME) - *stop = -1; +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; } - gst_query_unref (query); - return res; }