X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp-server%2Frtsp-media.c;h=345559d7c3c9993225a111ef714c47d8e6956632;hb=e16867b1618460452d0a637b2ba64e6d88e0c7c9;hp=8b25e07369150ed9356f5f8bb8ae19d076eef288;hpb=b58af93d83221c5cc26c69fd960db8e578f84e0f;p=platform%2Fupstream%2Fgstreamer.git diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 8b25e07..345559d 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -50,7 +50,8 @@ * #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(). + * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full() + * or gst_rtsp_media_seek_trickmode() for finer control of the 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 @@ -62,6 +63,9 @@ * * Last reviewed on 2013-07-11 (1.0.0) */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include #include @@ -81,9 +85,6 @@ #include "rtsp-media.h" -#define GST_RTSP_MEDIA_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaPrivate)) - struct _GstRTSPMediaPrivate { GMutex lock; @@ -100,8 +101,12 @@ struct _GstRTSPMediaPrivate gboolean eos_shutdown; guint buffer_size; GstRTSPAddressPool *pool; + gchar *multicast_iface; + guint max_mcast_ttl; + gboolean bind_mcast_address; gboolean blocked; GstRTSPTransportMode transport_mode; + gboolean stop_on_disconnect; GstElement *element; GRecMutex state_lock; /* locking order: state lock, lock */ @@ -110,20 +115,21 @@ struct _GstRTSPMediaPrivate GstRTSPMediaStatus status; /* protected by lock */ gint prepare_count; gint n_active; - gboolean adding; + gboolean complete; + gboolean finishing_unprepare; /* the pipeline for the media */ GstElement *pipeline; - GstElement *fakesink; /* protected by lock */ GSource *source; guint id; GstRTSPThread *thread; + GList *pending_pipeline_elements; gboolean time_provider; GstNetTimeProvider *nettime; gboolean is_live; - gboolean seekable; + GstClockTimeDiff seekable; gboolean buffering; GstState target_state; @@ -137,7 +143,16 @@ struct _GstRTSPMediaPrivate GList *payloads; /* protected by lock */ GstClockTime rtx_time; /* protected by lock */ + gboolean do_retransmission; /* protected by lock */ guint latency; /* protected by lock */ + GstClock *clock; /* protected by lock */ + gboolean do_rate_control; /* protected by lock */ + GstRTSPPublishClockMode publish_clock_mode; + + /* Dynamic element handling */ + guint nb_dynamic_elements; + guint no_more_pads_pending; + gboolean expected_async_done; }; #define DEFAULT_SHARED FALSE @@ -151,6 +166,12 @@ struct _GstRTSPMediaPrivate #define DEFAULT_TIME_PROVIDER FALSE #define DEFAULT_LATENCY 200 #define DEFAULT_TRANSPORT_MODE GST_RTSP_TRANSPORT_MODE_PLAY +#define DEFAULT_STOP_ON_DISCONNECT TRUE +#define DEFAULT_MAX_MCAST_TTL 255 +#define DEFAULT_BIND_MCAST_ADDRESS FALSE +#define DEFAULT_DO_RATE_CONTROL TRUE + +#define DEFAULT_DO_RETRANSMISSION FALSE /* define to dump received RTCP packets */ #undef DUMP_STATS @@ -169,6 +190,10 @@ enum PROP_TIME_PROVIDER, PROP_LATENCY, PROP_TRANSPORT_MODE, + PROP_STOP_ON_DISCONNECT, + PROP_CLOCK, + PROP_MAX_MCAST_TTL, + PROP_BIND_MCAST_ADDRESS, PROP_LAST }; @@ -211,10 +236,16 @@ static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp); static gboolean wait_preroll (GstRTSPMedia * media); +static GstElement *find_payload_element (GstElement * payloader, GstPad * pad); + static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 }; +static gboolean check_complete (GstRTSPMedia * media); + #define C_ENUM(v) ((gint) v) +#define TRICKMODE_FLAGS (GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED) + GType gst_rtsp_suspend_mode_get_type (void) { @@ -256,15 +287,36 @@ gst_rtsp_transport_mode_get_type (void) return (GType) id; } -G_DEFINE_TYPE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT); +GType +gst_rtsp_publish_clock_mode_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE), + "GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"}, + {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK), + "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK", + "clock"}, + {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET), + "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET", + "clock-and-offset"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values); + g_once_init_leave (&id, tmp); + } + return (GType) id; +} + +G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT); static void gst_rtsp_media_class_init (GstRTSPMediaClass * klass) { GObjectClass *gobject_class; - g_type_class_add_private (klass, sizeof (GstRTSPMediaPrivate)); - gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = gst_rtsp_media_get_property; @@ -327,6 +379,31 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT, + g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect", + "If this media pipeline should be stopped " + "when a client disconnects without TEARDOWN", + DEFAULT_STOP_ON_DISCONNECT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CLOCK, + g_param_spec_object ("clock", "Clock", + "Clock to be used by the media pipeline", + GST_TYPE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_MAX_MCAST_TTL, + g_param_spec_uint ("max-mcast-ttl", "Maximum multicast ttl", + "The maximum time-to-live value of outgoing multicast packets", 1, + 255, DEFAULT_MAX_MCAST_TTL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BIND_MCAST_ADDRESS, + g_param_spec_boolean ("bind-mcast-address", "Bind mcast address", + "Whether the multicast sockets should be bound to multicast addresses " + "or INADDR_ANY", + DEFAULT_BIND_MCAST_ADDRESS, + 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, @@ -376,7 +453,7 @@ gst_rtsp_media_class_init (GstRTSPMediaClass * klass) static void gst_rtsp_media_init (GstRTSPMedia * media) { - GstRTSPMediaPrivate *priv = GST_RTSP_MEDIA_GET_PRIVATE (media); + GstRTSPMediaPrivate *priv = gst_rtsp_media_get_instance_private (media); media->priv = priv; @@ -394,6 +471,13 @@ gst_rtsp_media_init (GstRTSPMedia * media) priv->buffer_size = DEFAULT_BUFFER_SIZE; priv->time_provider = DEFAULT_TIME_PROVIDER; priv->transport_mode = DEFAULT_TRANSPORT_MODE; + priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT; + priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK; + priv->do_retransmission = DEFAULT_DO_RETRANSMISSION; + priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL; + priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS; + priv->do_rate_control = DEFAULT_DO_RATE_CONTROL; + priv->expected_async_done = FALSE; } static void @@ -413,6 +497,7 @@ gst_rtsp_media_finalize (GObject * obj) g_ptr_array_unref (priv->streams); g_list_free_full (priv->dynamic, gst_object_unref); + g_list_free_full (priv->pending_pipeline_elements, gst_object_unref); if (priv->pipeline) gst_object_unref (priv->pipeline); @@ -423,6 +508,9 @@ gst_rtsp_media_finalize (GObject * obj) g_object_unref (priv->pool); if (priv->payloads) g_list_free (priv->payloads); + if (priv->clock) + gst_object_unref (priv->clock); + g_free (priv->multicast_iface); g_mutex_clear (&priv->lock); g_cond_clear (&priv->cond); g_rec_mutex_clear (&priv->state_lock); @@ -470,6 +558,18 @@ gst_rtsp_media_get_property (GObject * object, guint propid, case PROP_TRANSPORT_MODE: g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media)); break; + case PROP_STOP_ON_DISCONNECT: + g_value_set_boolean (value, gst_rtsp_media_is_stop_on_disconnect (media)); + break; + case PROP_CLOCK: + g_value_take_object (value, gst_rtsp_media_get_clock (media)); + break; + case PROP_MAX_MCAST_TTL: + g_value_set_uint (value, gst_rtsp_media_get_max_mcast_ttl (media)); + break; + case PROP_BIND_MCAST_ADDRESS: + g_value_set_boolean (value, gst_rtsp_media_is_bind_mcast_address (media)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } @@ -516,6 +616,20 @@ gst_rtsp_media_set_property (GObject * object, guint propid, case PROP_TRANSPORT_MODE: gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value)); break; + case PROP_STOP_ON_DISCONNECT: + gst_rtsp_media_set_stop_on_disconnect (media, + g_value_get_boolean (value)); + break; + case PROP_CLOCK: + gst_rtsp_media_set_clock (media, g_value_get_object (value)); + break; + case PROP_MAX_MCAST_TTL: + gst_rtsp_media_set_max_mcast_ttl (media, g_value_get_uint (value)); + break; + case PROP_BIND_MCAST_ADDRESS: + gst_rtsp_media_set_bind_mcast_address (media, + g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } @@ -524,6 +638,7 @@ gst_rtsp_media_set_property (GObject * object, guint propid, typedef struct { gint64 position; + gboolean complete_streams_only; gboolean ret; } DoQueryPositionData; @@ -532,10 +647,21 @@ do_query_position (GstRTSPStream * stream, DoQueryPositionData * data) { gint64 tmp; + if (!gst_rtsp_stream_is_sender (stream)) + return; + + if (data->complete_streams_only && !gst_rtsp_stream_is_complete (stream)) { + GST_DEBUG_OBJECT (stream, "stream not complete, do not query position"); + return; + } + if (gst_rtsp_stream_query_position (stream, &tmp)) { - data->position = MAX (data->position, tmp); + data->position = MIN (data->position, tmp); data->ret = TRUE; } + + GST_INFO_OBJECT (stream, "media position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (data->position)); } static gboolean @@ -546,12 +672,24 @@ default_query_position (GstRTSPMedia * media, gint64 * position) priv = media->priv; - data.position = -1; + data.position = G_MAXINT64; data.ret = FALSE; + /* if the media is complete, i.e. one or more streams have been configured + * with sinks, then we want to query the position on those streams only. + * a query on an incmplete stream may return a position that originates from + * an earlier preroll */ + if (check_complete (media)) + data.complete_streams_only = TRUE; + else + data.complete_streams_only = FALSE; + g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data); - *position = data.position; + if (!data.ret) + *position = GST_CLOCK_TIME_NONE; + else + *position = data.position; return data.ret; } @@ -565,7 +703,7 @@ typedef struct static void do_query_stop (GstRTSPStream * stream, DoQueryStopData * data) { - gint64 tmp; + gint64 tmp = 0; if (gst_rtsp_stream_query_stop (stream, &tmp)) { data->stop = MAX (data->stop, tmp); @@ -603,6 +741,74 @@ default_create_rtpbin (GstRTSPMedia * media) /* must be called with state lock */ static void +check_seekable (GstRTSPMedia * media) +{ + GstQuery *query; + GstRTSPMediaPrivate *priv = media->priv; + + /* Update the seekable state of the pipeline in case it changed */ + if (gst_rtsp_media_is_receive_only (media)) { + /* TODO: Seeking for "receive-only"? */ + priv->seekable = -1; + } else { + guint i, n = priv->streams->len; + + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + if (gst_rtsp_stream_get_publish_clock_mode (stream) == + GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) { + priv->seekable = -1; + return; + } + } + } + + 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 ? G_MAXINT64 : 0; + } else if (priv->streams->len) { + gboolean seekable = TRUE; + guint i, n = priv->streams->len; + + GST_DEBUG_OBJECT (media, "Checking %d streams", n); + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + seekable &= gst_rtsp_stream_seekable (stream); + } + priv->seekable = seekable ? G_MAXINT64 : -1; + } + + GST_DEBUG_OBJECT (media, "seekable:%" G_GINT64_FORMAT, priv->seekable); + + gst_query_unref (query); +} + +/* must be called with state lock */ +static gboolean +check_complete (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + + guint i, n = priv->streams->len; + + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + if (gst_rtsp_stream_is_complete (stream)) + return TRUE; + } + + return FALSE; +} + +/* must be called with state lock */ +static void collect_media_stats (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; @@ -670,6 +876,8 @@ collect_media_stats (GstRTSPMedia * media) priv->range.max.seconds = ((gdouble) stop) / GST_SECOND; priv->range_stop = stop; } + + check_seekable (media); } } @@ -728,6 +936,7 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) GstRTSPMediaPrivate *priv; GstElement *old; GstNetTimeProvider *nettime; + GList *l; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); g_return_if_fail (GST_IS_PIPELINE (pipeline)); @@ -748,12 +957,18 @@ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) gst_object_unref (nettime); gst_bin_add (GST_BIN_CAST (pipeline), priv->element); + + for (l = priv->pending_pipeline_elements; l; l = l->next) { + gst_bin_add (GST_BIN_CAST (pipeline), l->data); + } + g_list_free (priv->pending_pipeline_elements); + priv->pending_pipeline_elements = NULL; } /** * gst_rtsp_media_set_permissions: * @media: a #GstRTSPMedia - * @permissions: (transfer none): a #GstRTSPPermissions + * @permissions: (transfer none) (nullable): a #GstRTSPPermissions * * Set @permissions on @media. */ @@ -781,7 +996,7 @@ gst_rtsp_media_set_permissions (GstRTSPMedia * media, * * Get the permissions object from @media. * - * Returns: (transfer full): a #GstRTSPPermissions object, unref after usage. + * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage. */ GstRTSPPermissions * gst_rtsp_media_get_permissions (GstRTSPMedia * media) @@ -1122,6 +1337,7 @@ void gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size) { GstRTSPMediaPrivate *priv; + guint i; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); @@ -1131,6 +1347,11 @@ gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size) g_mutex_lock (&priv->lock); priv->buffer_size = size; + + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + gst_rtsp_stream_set_buffer_size (stream, size); + } g_mutex_unlock (&priv->lock); } @@ -1160,6 +1381,56 @@ gst_rtsp_media_get_buffer_size (GstRTSPMedia * media) } /** + * gst_rtsp_media_set_stop_on_disconnect: + * @media: a #GstRTSPMedia + * @stop_on_disconnect: the new value + * + * Set or unset if the pipeline for @media should be stopped when a + * client disconnects without sending TEARDOWN. + */ +void +gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia * media, + gboolean stop_on_disconnect) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->stop_on_disconnect = stop_on_disconnect; + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_is_stop_on_disconnect: + * @media: a #GstRTSPMedia + * + * Check if the pipeline for @media will be stopped when a client disconnects + * without sending TEARDOWN. + * + * Returns: %TRUE if the media will be stopped when a client disconnects + * without sending TEARDOWN. + */ +gboolean +gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), TRUE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->stop_on_disconnect; + g_mutex_unlock (&priv->lock); + + return res; +} + +/** * gst_rtsp_media_set_retransmission_time: * @media: a #GstRTSPMedia * @time: the new value @@ -1185,9 +1456,6 @@ gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time) gst_rtsp_stream_set_retransmission_time (stream, time); } - - if (priv->rtpbin) - g_object_set (priv->rtpbin, "do-retransmission", time > 0, NULL); g_mutex_unlock (&priv->lock); } @@ -1209,7 +1477,7 @@ gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media) priv = media->priv; - g_mutex_unlock (&priv->lock); + g_mutex_lock (&priv->lock); res = priv->rtx_time; g_mutex_unlock (&priv->lock); @@ -1217,7 +1485,56 @@ gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media) } /** - * gst_rtsp_media_set_latncy: + * gst_rtsp_media_set_do_retransmission: + * + * Set whether retransmission requests will be sent + * + * Since: 1.16 + */ +void +gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media, + gboolean do_retransmission) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->do_retransmission = do_retransmission; + + if (priv->rtpbin) + g_object_set (priv->rtpbin, "do-retransmission", do_retransmission, NULL); + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_do_retransmission: + * + * Returns: Whether retransmission requests will be sent + * + * Since: 1.16 + */ +gboolean +gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->do_retransmission; + g_mutex_unlock (&priv->lock); + + return res; +} + +/** + * gst_rtsp_media_set_latency: * @media: a #GstRTSPMedia * @latency: latency in milliseconds * @@ -1227,6 +1544,7 @@ void gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency) { GstRTSPMediaPrivate *priv; + guint i; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); @@ -1236,8 +1554,20 @@ gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency) g_mutex_lock (&priv->lock); priv->latency = latency; - if (priv->rtpbin) + if (priv->rtpbin) { g_object_set (priv->rtpbin, "latency", latency, NULL); + + for (i = 0; i < media->priv->streams->len; i++) { + GObject *storage = NULL; + + g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage", + i, &storage); + if (storage) + g_object_set (storage, "size-time", + (media->priv->latency + 50) * GST_MSECOND, NULL); + } + } + g_mutex_unlock (&priv->lock); } @@ -1259,7 +1589,7 @@ gst_rtsp_media_get_latency (GstRTSPMedia * media) priv = media->priv; - g_mutex_unlock (&priv->lock); + g_mutex_lock (&priv->lock); res = priv->latency; g_mutex_unlock (&priv->lock); @@ -1307,7 +1637,7 @@ gst_rtsp_media_is_time_provider (GstRTSPMedia * media) priv = media->priv; - g_mutex_unlock (&priv->lock); + g_mutex_lock (&priv->lock); res = priv->time_provider; g_mutex_unlock (&priv->lock); @@ -1315,9 +1645,95 @@ gst_rtsp_media_is_time_provider (GstRTSPMedia * media) } /** + * gst_rtsp_media_set_clock: + * @media: a #GstRTSPMedia + * @clock: (nullable): #GstClock to be used + * + * Configure the clock used for the media. + */ +void +gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock) +{ + GstRTSPMediaPrivate *priv; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL); + + GST_LOG_OBJECT (media, "setting clock %" GST_PTR_FORMAT, clock); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + if (priv->clock) + gst_object_unref (priv->clock); + priv->clock = clock ? gst_object_ref (clock) : NULL; + if (priv->pipeline) { + if (clock) + gst_pipeline_use_clock (GST_PIPELINE_CAST (priv->pipeline), clock); + else + gst_pipeline_auto_clock (GST_PIPELINE_CAST (priv->pipeline)); + } + + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_set_publish_clock_mode: + * @media: a #GstRTSPMedia + * @mode: the clock publish mode + * + * Sets if and how the media clock should be published according to RFC7273. + * + * Since: 1.8 + */ +void +gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media, + GstRTSPPublishClockMode mode) +{ + GstRTSPMediaPrivate *priv; + guint i, n; + + priv = media->priv; + g_mutex_lock (&priv->lock); + priv->publish_clock_mode = mode; + + n = priv->streams->len; + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + gst_rtsp_stream_set_publish_clock_mode (stream, mode); + } + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_publish_clock_mode: + * @media: a #GstRTSPMedia + * + * Gets if and how the media clock should be published according to RFC7273. + * + * Returns: The GstRTSPPublishClockMode + * + * Since: 1.8 + */ +GstRTSPPublishClockMode +gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + GstRTSPPublishClockMode ret; + + priv = media->priv; + g_mutex_lock (&priv->lock); + ret = priv->publish_clock_mode; + g_mutex_unlock (&priv->lock); + + return ret; +} + +/** * gst_rtsp_media_set_address_pool: * @media: a #GstRTSPMedia - * @pool: (transfer none): a #GstRTSPAddressPool + * @pool: (transfer none) (nullable): a #GstRTSPAddressPool * * configure @pool to be used as the address pool of @media. */ @@ -1353,8 +1769,8 @@ gst_rtsp_media_set_address_pool (GstRTSPMedia * media, * * Get the #GstRTSPAddressPool used as the address pool of @media. * - * Returns: (transfer full): the #GstRTSPAddressPool of @media. g_object_unref() after - * usage. + * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @media. + * g_object_unref() after usage. */ GstRTSPAddressPool * gst_rtsp_media_get_address_pool (GstRTSPMedia * media) @@ -1374,26 +1790,211 @@ gst_rtsp_media_get_address_pool (GstRTSPMedia * media) return result; } -static GList * -_find_payload_types (GstRTSPMedia * media) +/** + * gst_rtsp_media_set_multicast_iface: + * @media: a #GstRTSPMedia + * @multicast_iface: (transfer none) (nullable): a multicast interface name + * + * configure @multicast_iface to be used for @media. + */ +void +gst_rtsp_media_set_multicast_iface (GstRTSPMedia * media, + const gchar * multicast_iface) { - gint i, n; - GQueue queue = G_QUEUE_INIT; + GstRTSPMediaPrivate *priv; + gchar *old; - n = media->priv->streams->len; - for (i = 0; i < n; i++) { - GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i); - guint pt = gst_rtsp_stream_get_pt (stream); + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); - g_queue_push_tail (&queue, GUINT_TO_POINTER (pt)); - } + priv = media->priv; - return queue.head; -} + GST_LOG_OBJECT (media, "set multicast interface %s", multicast_iface); -static guint -_next_available_pt (GList * payloads) -{ + g_mutex_lock (&priv->lock); + if ((old = priv->multicast_iface) != multicast_iface) + priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL; + else + old = NULL; + g_ptr_array_foreach (priv->streams, + (GFunc) gst_rtsp_stream_set_multicast_iface, (gchar *) multicast_iface); + g_mutex_unlock (&priv->lock); + + if (old) + g_free (old); +} + +/** + * gst_rtsp_media_get_multicast_iface: + * @media: a #GstRTSPMedia + * + * Get the multicast interface used for @media. + * + * Returns: (transfer full) (nullable): the multicast interface for @media. + * g_free() after usage. + */ +gchar * +gst_rtsp_media_get_multicast_iface (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gchar *result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + if ((result = priv->multicast_iface)) + result = g_strdup (result); + g_mutex_unlock (&priv->lock); + + return result; +} + +/** + * gst_rtsp_media_set_max_mcast_ttl: + * @media: a #GstRTSPMedia + * @ttl: the new multicast ttl value + * + * Set the maximum time-to-live value of outgoing multicast packets. + * + * Returns: %TRUE if the requested ttl has been set successfully. + * + * Since: 1.16 + */ +gboolean +gst_rtsp_media_set_max_mcast_ttl (GstRTSPMedia * media, guint ttl) +{ + GstRTSPMediaPrivate *priv; + guint i; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + GST_LOG_OBJECT (media, "set max mcast ttl %u", ttl); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + + if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) { + GST_WARNING_OBJECT (media, "The reqested mcast TTL value is not valid."); + g_mutex_unlock (&priv->lock); + return FALSE; + } + priv->max_mcast_ttl = ttl; + + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + gst_rtsp_stream_set_max_mcast_ttl (stream, ttl); + } + g_mutex_unlock (&priv->lock); + + return TRUE; +} + +/** + * gst_rtsp_media_get_max_mcast_ttl: + * @media: a #GstRTSPMedia + * + * Get the the maximum time-to-live value of outgoing multicast packets. + * + * Returns: the maximum time-to-live value of outgoing multicast packets. + * + * Since: 1.16 + */ +guint +gst_rtsp_media_get_max_mcast_ttl (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + guint res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->max_mcast_ttl; + g_mutex_unlock (&priv->lock); + + return res; +} + +/** + * gst_rtsp_media_set_bind_mcast_address: + * @media: a #GstRTSPMedia + * @bind_mcast_addr: the new value + * + * Decide whether the multicast socket should be bound to a multicast address or + * INADDR_ANY. + * + * Since: 1.16 + */ +void +gst_rtsp_media_set_bind_mcast_address (GstRTSPMedia * media, + gboolean bind_mcast_addr) +{ + GstRTSPMediaPrivate *priv; + guint i; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->bind_mcast_address = bind_mcast_addr; + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + gst_rtsp_stream_set_bind_mcast_address (stream, bind_mcast_addr); + } + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_is_bind_mcast_address: + * @media: a #GstRTSPMedia + * + * Check if multicast sockets are configured to be bound to multicast addresses. + * + * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses. + * + * Since: 1.16 + */ +gboolean +gst_rtsp_media_is_bind_mcast_address (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean result; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + result = priv->bind_mcast_address; + g_mutex_unlock (&priv->lock); + + return result; +} + +static GList * +_find_payload_types (GstRTSPMedia * media) +{ + gint i, n; + GQueue queue = G_QUEUE_INIT; + + n = media->priv->streams->len; + for (i = 0; i < n; i++) { + GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i); + guint pt = gst_rtsp_stream_get_pt (stream); + + g_queue_push_tail (&queue, GUINT_TO_POINTER (pt)); + } + + return queue.head; +} + +static guint +_next_available_pt (GList * payloads) +{ guint i; for (i = 96; i <= 127; i++) { @@ -1442,12 +2043,24 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media) name = g_strdup_printf ("pay%d", i); if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) { + GstElement *pay; GST_INFO ("found stream %d with payloader %p", i, elem); /* take the pad of the payloader */ pad = gst_element_get_static_pad (elem, "src"); + + /* find the real payload element in case elem is a GstBin */ + pay = find_payload_element (elem, pad); + /* create the stream */ - gst_rtsp_media_create_stream (media, elem, pad); + if (pay == NULL) { + GST_WARNING ("could not find real payloader, using bin"); + gst_rtsp_media_create_stream (media, elem, pad); + } else { + gst_rtsp_media_create_stream (media, pay, pad); + gst_object_unref (pay); + } + gst_object_unref (pad); gst_object_unref (elem); @@ -1466,6 +2079,8 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media) priv->dynamic = g_list_prepend (priv->dynamic, elem); g_mutex_unlock (&priv->lock); + priv->nb_dynamic_elements++; + have_elem = TRUE; more_elem_remaining = TRUE; mode |= GST_RTSP_TRANSPORT_MODE_PLAY; @@ -1497,6 +2112,79 @@ gst_rtsp_media_collect_streams (GstRTSPMedia * media) } } +typedef struct +{ + GstElement *appsink, *appsrc; + GstRTSPStream *stream; +} AppSinkSrcData; + +static GstFlowReturn +appsink_new_sample (GstAppSink * appsink, gpointer user_data) +{ + AppSinkSrcData *data = user_data; + GstSample *sample; + GstFlowReturn ret; + + sample = gst_app_sink_pull_sample (appsink); + if (!sample) + return GST_FLOW_FLUSHING; + + + ret = gst_app_src_push_sample (GST_APP_SRC (data->appsrc), sample); + gst_sample_unref (sample); + return ret; +} + +static GstAppSinkCallbacks appsink_callbacks = { + NULL, + NULL, + appsink_new_sample, +}; + +static GstPadProbeReturn +appsink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + AppSinkSrcData *data = user_data; + + if (GST_IS_EVENT (info->data) + && GST_EVENT_TYPE (info->data) == GST_EVENT_LATENCY) { + GstClockTime min, max; + + if (gst_base_sink_query_latency (GST_BASE_SINK (data->appsink), NULL, NULL, + &min, &max)) { + g_object_set (data->appsrc, "min-latency", min, "max-latency", max, NULL); + GST_DEBUG ("setting latency to min %" GST_TIME_FORMAT " max %" + GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max)); + } + } else if (GST_IS_QUERY (info->data)) { + GstPad *srcpad = gst_element_get_static_pad (data->appsrc, "src"); + if (gst_pad_peer_query (srcpad, GST_QUERY_CAST (info->data))) { + gst_object_unref (srcpad); + return GST_PAD_PROBE_HANDLED; + } + gst_object_unref (srcpad); + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn +appsrc_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + AppSinkSrcData *data = user_data; + + if (GST_IS_QUERY (info->data)) { + GstPad *sinkpad = gst_element_get_static_pad (data->appsink, "sink"); + if (gst_pad_peer_query (sinkpad, GST_QUERY_CAST (info->data))) { + gst_object_unref (sinkpad); + return GST_PAD_PROBE_HANDLED; + } + gst_object_unref (sinkpad); + } + + return GST_PAD_PROBE_OK; +} + /** * gst_rtsp_media_create_stream: * @media: a #GstRTSPMedia @@ -1515,9 +2203,10 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, { GstRTSPMediaPrivate *priv; GstRTSPStream *stream; - GstPad *ghostpad; + GstPad *streampad; gchar *name; gint idx; + AppSinkSrcData *data = NULL; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL); @@ -1528,24 +2217,91 @@ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, g_mutex_lock (&priv->lock); idx = priv->streams->len; - GST_DEBUG ("media %p: creating stream with index %d", media, idx); + GST_DEBUG ("media %p: creating stream with index %d and payloader %" + GST_PTR_FORMAT, media, idx, payloader); if (GST_PAD_IS_SRC (pad)) name = g_strdup_printf ("src_%u", idx); else name = g_strdup_printf ("sink_%u", idx); - ghostpad = gst_ghost_pad_new (name, pad); - gst_pad_set_active (ghostpad, TRUE); - gst_element_add_pad (priv->element, ghostpad); + if ((GST_PAD_IS_SRC (pad) && priv->element->numsinkpads > 0) || + (GST_PAD_IS_SINK (pad) && priv->element->numsrcpads > 0)) { + GstElement *appsink, *appsrc; + GstPad *sinkpad, *srcpad; + + appsink = gst_element_factory_make ("appsink", NULL); + appsrc = gst_element_factory_make ("appsrc", NULL); + + if (GST_PAD_IS_SINK (pad)) { + srcpad = gst_element_get_static_pad (appsrc, "src"); + + gst_bin_add (GST_BIN (priv->element), appsrc); + + gst_pad_link (srcpad, pad); + gst_object_unref (srcpad); + + streampad = gst_element_get_static_pad (appsink, "sink"); + + priv->pending_pipeline_elements = + g_list_prepend (priv->pending_pipeline_elements, appsink); + } else { + sinkpad = gst_element_get_static_pad (appsink, "sink"); + + gst_pad_link (pad, sinkpad); + gst_object_unref (sinkpad); + + streampad = gst_element_get_static_pad (appsrc, "src"); + + priv->pending_pipeline_elements = + g_list_prepend (priv->pending_pipeline_elements, appsrc); + } + + g_object_set (appsrc, "block", TRUE, "format", GST_FORMAT_TIME, "is-live", + TRUE, "emit-signals", FALSE, NULL); + g_object_set (appsink, "sync", FALSE, "async", FALSE, "emit-signals", + FALSE, "buffer-list", TRUE, NULL); + + data = g_new0 (AppSinkSrcData, 1); + data->appsink = appsink; + data->appsrc = appsrc; + + sinkpad = gst_element_get_static_pad (appsink, "sink"); + gst_pad_add_probe (sinkpad, + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, + appsink_pad_probe, data, NULL); + gst_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (appsrc, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM, + appsrc_pad_probe, data, NULL); + gst_object_unref (srcpad); + + gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &appsink_callbacks, + data, NULL); + g_object_set_data_full (G_OBJECT (streampad), "media-appsink-appsrc", data, + g_free); + } else { + streampad = gst_ghost_pad_new (name, pad); + gst_pad_set_active (streampad, TRUE); + gst_element_add_pad (priv->element, streampad); + } g_free (name); - stream = gst_rtsp_stream_new (idx, payloader, ghostpad); + stream = gst_rtsp_stream_new (idx, payloader, streampad); + if (data) + data->stream = stream; if (priv->pool) gst_rtsp_stream_set_address_pool (stream, priv->pool); + gst_rtsp_stream_set_multicast_iface (stream, priv->multicast_iface); + gst_rtsp_stream_set_max_mcast_ttl (stream, priv->max_mcast_ttl); + gst_rtsp_stream_set_bind_mcast_address (stream, priv->bind_mcast_address); gst_rtsp_stream_set_profiles (stream, priv->profiles); gst_rtsp_stream_set_protocols (stream, priv->protocols); gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time); + gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size); + gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode); + gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control); g_ptr_array_add (priv->streams, stream); @@ -1585,13 +2341,28 @@ gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream) { GstRTSPMediaPrivate *priv; GstPad *srcpad; + AppSinkSrcData *data; 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); + data = g_object_get_data (G_OBJECT (srcpad), "media-appsink-appsrc"); + if (data) { + if (GST_OBJECT_PARENT (data->appsrc) == GST_OBJECT_CAST (priv->pipeline)) + gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsrc); + else if (GST_OBJECT_PARENT (data->appsrc) == + GST_OBJECT_CAST (priv->element)) + gst_bin_remove (GST_BIN_CAST (priv->element), data->appsrc); + if (GST_OBJECT_PARENT (data->appsink) == GST_OBJECT_CAST (priv->pipeline)) + gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsink); + else if (GST_OBJECT_PARENT (data->appsink) == + GST_OBJECT_CAST (priv->element)) + gst_bin_remove (GST_BIN_CAST (priv->element), data->appsink); + } else { + gst_element_remove_pad (priv->element, srcpad); + } gst_object_unref (srcpad); /* now remove the stream */ g_object_ref (stream); @@ -1716,7 +2487,7 @@ default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, * 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. + * Returns: (transfer full) (nullable): The range as a string, g_free() after usage. */ gchar * gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play, @@ -1774,6 +2545,77 @@ conversion_failed: } } +/** + * gst_rtsp_media_get_rates: + * @media: a #GstRTSPMedia + * @rate (allow-none): the rate of the current segment + * @applied_rate (allow-none): the applied_rate of the current segment + * + * Get the rate and applied_rate of the current segment. + * + * Returns: %FALSE if looking up the rate and applied rate failed. Otherwise + * %TRUE is returned and @rate and @applied_rate are set to the rate and + * applied_rate of the current segment. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_get_rates (GstRTSPMedia * media, gdouble * rate, + gdouble * applied_rate) +{ + GstRTSPMediaPrivate *priv; + GstRTSPStream *stream; + gdouble save_rate, save_applied_rate; + gboolean result = TRUE; + gboolean first_stream = TRUE; + gint i; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + if (!rate && !applied_rate) { + GST_WARNING_OBJECT (media, "rate and applied_rate are both NULL"); + return FALSE; + } + + priv = media->priv; + + g_mutex_lock (&priv->lock); + + g_assert (priv->streams->len > 0); + for (i = 0; i < priv->streams->len; i++) { + stream = g_ptr_array_index (priv->streams, i); + if (gst_rtsp_stream_is_complete (stream)) { + if (gst_rtsp_stream_get_rates (stream, rate, applied_rate)) { + if (first_stream) { + save_rate = *rate; + save_applied_rate = *applied_rate; + first_stream = FALSE; + } else { + if (save_rate != *rate || save_applied_rate != *applied_rate) { + /* diffrent rate or applied_rate, weird */ + g_assert (FALSE); + result = FALSE; + break; + } + } + } else { + /* complete stream withot rate and applied_rate, weird */ + g_assert (FALSE); + result = FALSE; + break; + } + } + } + + if (!result) { + GST_WARNING_OBJECT (media, + "failed to obtain consistent rate and applied_rate"); + } + + g_mutex_unlock (&priv->lock); + + return result; +} + static void stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media) { @@ -1791,6 +2633,24 @@ media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked) } static void +stream_unblock (GstRTSPStream * stream, GstRTSPMedia * media) +{ + gst_rtsp_stream_set_blocked (stream, FALSE); +} + +static void +media_unblock (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + + GST_DEBUG ("media %p unblocking streams", media); + /* media is not blocked any longer, as it contains active streams, + * streams that are complete */ + priv->blocked = FALSE; + g_ptr_array_foreach (priv->streams, (GFunc) stream_unblock, media); +} + +static void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) { GstRTSPMediaPrivate *priv = media->priv; @@ -1837,31 +2697,43 @@ gst_rtsp_media_get_status (GstRTSPMedia * media) } /** - * gst_rtsp_media_seek: + * gst_rtsp_media_seek_trickmode: * @media: a #GstRTSPMedia * @range: (transfer none): a #GstRTSPTimeRange + * @flags: The minimal set of #GstSeekFlags to use + * @rate: the rate to use in the seek + * @trickmode_interval: The trickmode interval to use for KEY_UNITS trick mode * - * Seek the pipeline of @media to @range. @media must be prepared with - * gst_rtsp_media_prepare(). + * Seek the pipeline of @media to @range with the given @flags and @rate, + * and @trickmode_interval. + * @media must be prepared with gst_rtsp_media_prepare(). + * In order to perform the seek operation, the pipeline must contain all + * needed transport parts (transport sinks). * * Returns: %TRUE on success. + * + * Since: 1.18 */ gboolean -gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) +gst_rtsp_media_seek_trickmode (GstRTSPMedia * media, + GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate, + GstClockTime trickmode_interval) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; gboolean res; GstClockTime start, stop; GstSeekType start_type, stop_type; - GstQuery *query; gint64 current_position; + gboolean force_seek; 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); + /* if there's a range then klass->convert_range must be set */ + g_return_val_if_fail (range == NULL || klass->convert_range != NULL, FALSE); + + GST_DEBUG ("flags=%x rate=%f", flags, rate); priv = media->priv; @@ -1869,36 +2741,39 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) goto not_prepared; + /* check if the media pipeline is complete in order to perform a + * seek operation on it */ + if (!check_complete (media)) + goto not_complete; + /* Update the seekable state of the pipeline in case it changed */ - if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) { - /* TODO: Seeking for RECORD? */ - priv->seekable = FALSE; - } else { - 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); - } + check_seekable (media); + + if (priv->seekable == 0) { + GST_FIXME_OBJECT (media, "Handle going back to 0 for none live" + " not seekable streams."); - if (!priv->seekable) goto not_seekable; + } else if (priv->seekable < 0) { + goto not_seekable; + } start_type = stop_type = GST_SEEK_TYPE_NONE; + start = stop = GST_CLOCK_TIME_NONE; - if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT)) - goto not_supported; - gst_rtsp_range_get_times (range, &start, &stop); + /* if caller provided a range convert it to NPT format + * if no range provided the seek is assumed to be the same position but with + * e.g. the rate changed */ + if (range != NULL) { + 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)); + 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)); + } current_position = -1; if (klass->query_position) @@ -1909,25 +2784,25 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) if (start != GST_CLOCK_TIME_NONE) start_type = GST_SEEK_TYPE_SET; - if (priv->range_stop == stop) - stop = GST_CLOCK_TIME_NONE; - else if (stop != GST_CLOCK_TIME_NONE) + if (stop != GST_CLOCK_TIME_NONE) stop_type = GST_SEEK_TYPE_SET; - if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE) { - GstSeekFlags flags; + /* we force a seek if any trickmode flag is set, or if the rate + * is non-standard, i.e. not 1.0 */ + force_seek = (flags & TRICKMODE_FLAGS) || rate != 1.0; + if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE || force_seek) { GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); /* depends on the current playing state of the pipeline. We might need to * queue this until we get EOS. */ - flags = GST_SEEK_FLAG_FLUSH; + 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! */ + if (range == NULL || range->min.type == GST_RTSP_TIME_END) { if (current_position == -1) { GST_WARNING ("current position unknown"); } else { @@ -1935,25 +2810,72 @@ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) GST_TIME_ARGS (current_position)); start = current_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; } - if (start == current_position && stop_type == GST_SEEK_TYPE_NONE) { - GST_DEBUG ("not seeking because no position change"); + if (start == current_position && stop_type == GST_SEEK_TYPE_NONE && + !force_seek) { + GST_DEBUG ("no position change, no flags set by caller, so not seeking"); res = TRUE; } else { + GstEvent *seek_event; + gboolean unblock = FALSE; + + /* Handle expected async-done before waiting on next async-done. + * + * Since the seek further down in code will cause a preroll and + * a async-done will be generated it's important to wait on async-done + * if that is expected. Otherwise there is the risk that the waiting + * for async-done after the seek is detecting the expected async-done + * instead of the one that corresponds to the seek. Then execution + * continue and act as if the pipeline is prerolled, but it's not. + * + * During wait_preroll message GST_MESSAGE_ASYNC_DONE will come + * and then the state will change from preparing to prepared */ + if (priv->expected_async_done) { + GST_DEBUG (" expected to get async-done, waiting "); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + g_rec_mutex_unlock (&priv->state_lock); + + /* wait until pipeline is prerolled */ + if (!wait_preroll (media)) + goto preroll_failed_expected_async_done; + + g_rec_mutex_lock (&priv->state_lock); + GST_DEBUG (" got expected async-done"); + } + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); - if (priv->blocked) + + if (rate < 0.0) { + GstClockTime temp_time = start; + GstSeekType temp_type = start_type; + + start = stop; + start_type = stop_type; + stop = temp_time; + stop_type = temp_type; + } + + seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type, + start, stop_type, stop); + + gst_event_set_seek_trickmode_interval (seek_event, trickmode_interval); + + if (!media->priv->blocked) { + /* Prevent a race condition with multiple streams, + * where one stream may have time to preroll before others + * have even started flushing, causing async-done to be + * posted too early. + */ media_streams_set_blocked (media, TRUE); + unblock = TRUE; + } + + res = gst_element_send_event (priv->pipeline, seek_event); - /* 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); + if (unblock) + media_streams_set_blocked (media, FALSE); /* and block for the seek to complete */ GST_INFO ("done seeking %d", res); @@ -1984,6 +2906,12 @@ not_prepared: GST_INFO ("media %p is not prepared", media); return FALSE; } +not_complete: + { + g_rec_mutex_unlock (&priv->state_lock); + GST_INFO ("pipeline is not complete"); + return FALSE; + } not_seekable: { g_rec_mutex_unlock (&priv->state_lock); @@ -2008,6 +2936,47 @@ preroll_failed: GST_WARNING ("failed to preroll after seek"); return FALSE; } +preroll_failed_expected_async_done: + { + GST_WARNING ("failed to preroll"); + return FALSE; + } +} + +/** + * gst_rtsp_media_seek_full: + * @media: a #GstRTSPMedia + * @range: (transfer none): a #GstRTSPTimeRange + * @flags: The minimal set of #GstSeekFlags to use + * + * Seek the pipeline of @media to @range with the given @flags. + * @media must be prepared with gst_rtsp_media_prepare(). + * + * Returns: %TRUE on success. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range, + GstSeekFlags flags) +{ + return gst_rtsp_media_seek_trickmode (media, range, flags, 1.0, 0); +} + +/** + * gst_rtsp_media_seek: + * @media: a #GstRTSPMedia + * @range: (transfer none): a #GstRTSPTimeRange + * + * Seek the pipeline of @media to @range. @media must be prepared with + * gst_rtsp_media_prepare(). + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range) +{ + return gst_rtsp_media_seek_trickmode (media, range, GST_SEEK_FLAG_NONE, + 1.0, 0); } static void @@ -2083,8 +3052,9 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) GST_DEBUG ("%p: went from %s to %s (pending %s)", media, gst_element_state_get_name (old), gst_element_state_get_name (new), gst_element_state_get_name (pending)); - if ((priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD) - && old == GST_STATE_READY && new == GST_STATE_PAUSED) { + if (priv->no_more_pads_pending == 0 + && gst_rtsp_media_is_receive_only (media) && old == GST_STATE_READY + && new == GST_STATE_PAUSED) { GST_INFO ("%p: went to PAUSED, prepared now", media); collect_media_stats (media); @@ -2165,8 +3135,9 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) 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"); + if (priv->blocked && media_streams_blocking (media) && + priv->no_more_pads_pending == 0) { + GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "media is blocking"); collect_media_stats (media); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) @@ -2178,15 +3149,13 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) case GST_MESSAGE_STREAM_STATUS: break; case GST_MESSAGE_ASYNC_DONE: - 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->expected_async_done) + priv->expected_async_done = FALSE; + if (priv->complete) { + /* receive the final ASYNC_DONE, that is posted by the media pipeline + * after all the transport parts have been successfully added to + * the media streams. */ + GST_DEBUG_OBJECT (media, "got async-done"); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); } @@ -2233,27 +3202,57 @@ watch_destroyed (GstRTSPMedia * media) g_object_unref (media); } +static gboolean +is_payloader (GstElement * element) +{ + GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element); + const gchar *klass; + + klass = gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS); + if (klass == NULL) + return FALSE; + + if (strstr (klass, "Payloader") && strstr (klass, "RTP")) { + return TRUE; + } + + return FALSE; +} + static GstElement * -find_payload_element (GstElement * payloader) +find_payload_element (GstElement * payloader, GstPad * pad) { GstElement *pay = NULL; if (GST_IS_BIN (payloader)) { GstIterator *iter; GValue item = { 0 }; + gchar *pad_name, *payloader_name; + GstElement *element; + + if ((element = gst_bin_get_by_name (GST_BIN (payloader), "pay"))) { + if (is_payloader (element)) + return element; + gst_object_unref (element); + } + + pad_name = gst_object_get_name (GST_OBJECT (pad)); + payloader_name = g_strdup_printf ("pay_%s", pad_name); + g_free (pad_name); + if ((element = gst_bin_get_by_name (GST_BIN (payloader), payloader_name))) { + g_free (payloader_name); + if (is_payloader (element)) + return element; + gst_object_unref (element); + } else { + g_free (payloader_name); + } 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; + element = (GstElement *) g_value_get_object (&item); - klass = - gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS); - if (klass == NULL) - continue; - - if (strstr (klass, "Payloader") && strstr (klass, "RTP")) { + if (is_payloader (element)) { pay = gst_object_ref (element); g_value_unset (&item); break; @@ -2277,7 +3276,7 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) GstElement *pay; /* find the real payload element */ - pay = find_payload_element (element); + pay = find_payload_element (element, pad); stream = gst_rtsp_media_create_stream (media, pay, pad); gst_object_unref (pay); @@ -2289,11 +3288,6 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) 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. */ - priv->adding = TRUE; - /* join the element in the PAUSED state because this callback is * called from the streaming thread and it is PAUSED */ if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), @@ -2301,7 +3295,9 @@ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) GST_WARNING ("failed to join bin element"); } - priv->adding = FALSE; + if (priv->blocked) + gst_rtsp_stream_set_blocked (stream, TRUE); + g_rec_mutex_unlock (&priv->state_lock); return; @@ -2328,30 +3324,11 @@ pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) 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); - if ((fakesink = priv->fakesink)) - gst_object_ref (fakesink); - 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); - GST_INFO ("removed fakesink"); - } + 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 @@ -2359,8 +3336,10 @@ no_more_pads_cb (GstElement * element, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; - GST_INFO ("no more pads"); - remove_fakesink (priv); + GST_INFO_OBJECT (element, "no more pads"); + g_mutex_lock (&priv->lock); + priv->no_more_pads_pending--; + g_mutex_unlock (&priv->lock); } typedef struct _DynPaySignalHandlers DynPaySignalHandlers; @@ -2379,29 +3358,26 @@ start_preroll (GstRTSPMedia * media) GstStateChangeReturn ret; GST_INFO ("setting pipeline to PAUSED for media %p", media); - /* first go to PAUSED */ + + /* start blocked since it is possible that there are no sink elements yet */ + media_streams_set_blocked (media, TRUE); 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->seekable = -1; priv->is_live = TRUE; - if (!(priv->transport_mode & GST_RTSP_TRANSPORT_MODE_RECORD)) { - /* 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; @@ -2440,6 +3416,90 @@ preroll_failed: } } +static GstElement * +request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPStream *stream = NULL; + guint i; + GstElement *res = NULL; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + stream = g_ptr_array_index (priv->streams, i); + + if (sessid == gst_rtsp_stream_get_index (stream)) + break; + + stream = NULL; + } + g_mutex_unlock (&priv->lock); + + if (stream) + res = gst_rtsp_stream_request_aux_sender (stream, sessid); + + return res; +} + +static GstElement * +request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPStream *stream = NULL; + guint i; + GstElement *res = NULL; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + stream = g_ptr_array_index (priv->streams, i); + + if (sessid == gst_rtsp_stream_get_index (stream)) + break; + + stream = NULL; + } + g_mutex_unlock (&priv->lock); + + if (stream) + res = gst_rtsp_stream_request_aux_receiver (stream, sessid); + + return res; +} + +static GstElement * +request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + GstRTSPStream *stream = NULL; + guint i; + GstElement *res = NULL; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + stream = g_ptr_array_index (priv->streams, i); + + if (sessid == gst_rtsp_stream_get_index (stream)) + break; + + stream = NULL; + } + g_mutex_unlock (&priv->lock); + + if (stream) { + res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid); + } + + return res; +} + +static void +new_storage_cb (GstElement * rtpbin, GObject * storage, guint sessid, + GstRTSPMedia * media) +{ + g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND, + NULL); +} + static gboolean start_prepare (GstRTSPMedia * media) { @@ -2447,6 +3507,15 @@ start_prepare (GstRTSPMedia * media) guint i; GList *walk; + g_rec_mutex_lock (&priv->state_lock); + if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) + goto no_longer_preparing; + + g_signal_connect (priv->rtpbin, "new-storage", G_CALLBACK (new_storage_cb), + media); + g_signal_connect (priv->rtpbin, "request-fec-decoder", + G_CALLBACK (request_fec_decoder), media); + /* link streams we already have, other streams might appear when we have * dynamic elements */ for (i = 0; i < priv->streams->len; i++) { @@ -2454,6 +3523,17 @@ start_prepare (GstRTSPMedia * media) stream = g_ptr_array_index (priv->streams, i); + if (priv->rtx_time > 0) { + /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */ + g_signal_connect (priv->rtpbin, "request-aux-sender", + (GCallback) request_aux_sender, media); + } + + if (priv->do_retransmission) { + g_signal_connect (priv->rtpbin, "request-aux-receiver", + (GCallback) request_aux_receiver, media); + } + if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin, GST_STATE_NULL)) { goto join_bin_failed; @@ -2461,7 +3541,8 @@ start_prepare (GstRTSPMedia * media) } if (priv->rtpbin) - g_object_set (priv->rtpbin, "do-retransmission", priv->rtx_time > 0, NULL); + g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission, + "do-lost", TRUE, NULL); for (walk = priv->dynamic; walk; walk = g_list_next (walk)) { GstElement *elem = walk->data; @@ -2477,28 +3558,39 @@ start_prepare (GstRTSPMedia * media) (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. */ - priv->fakesink = gst_element_factory_make ("fakesink", "fakesink"); - gst_bin_add (GST_BIN (priv->pipeline), priv->fakesink); } - if (!start_preroll (media)) + if (priv->nb_dynamic_elements == 0 && gst_rtsp_media_is_receive_only (media)) { + /* If we are receive_only (RECORD), do not try to preroll, to avoid + * a second ASYNC state change failing */ + priv->is_live = TRUE; + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + } else if (!start_preroll (media)) { goto preroll_failed; + } + + g_rec_mutex_unlock (&priv->state_lock); return FALSE; +no_longer_preparing: + { + GST_INFO ("media is no longer preparing"); + g_rec_mutex_unlock (&priv->state_lock); + return FALSE; + } join_bin_failed: { GST_WARNING ("failed to join bin element"); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + g_rec_mutex_unlock (&priv->state_lock); return FALSE; } preroll_failed: { GST_WARNING ("failed to preroll pipeline"); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); + g_rec_mutex_unlock (&priv->state_lock); return FALSE; } } @@ -2555,7 +3647,8 @@ default_prepare (GstRTSPMedia * media, GstRTSPThread * thread) /* do remainder in context */ source = g_idle_source_new (); - g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL); + g_source_set_callback (source, (GSourceFunc) start_prepare, + g_object_ref (media), (GDestroyNotify) g_object_unref); g_source_attach (source, context); g_source_unref (source); @@ -2621,8 +3714,9 @@ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) /* reset some variables */ priv->is_live = FALSE; - priv->seekable = FALSE; + priv->seekable = -1; priv->buffering = FALSE; + priv->no_more_pads_pending = priv->nb_dynamic_elements; /* we're preparing now */ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); @@ -2711,6 +3805,10 @@ finish_unprepare (GstRTSPMedia * media) gint i; GList *walk; + if (priv->finishing_unprepare) + return; + priv->finishing_unprepare = TRUE; + GST_DEBUG ("shutting down"); /* release the lock on shutdown, otherwise pad_added_cb might try to @@ -2718,7 +3816,8 @@ finish_unprepare (GstRTSPMedia * media) g_rec_mutex_unlock (&priv->state_lock); set_state (media, GST_STATE_NULL); g_rec_mutex_lock (&priv->state_lock); - remove_fakesink (priv); + + media_streams_set_blocked (media, FALSE); for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *stream; @@ -2772,6 +3871,8 @@ finish_unprepare (GstRTSPMedia * media) GST_DEBUG ("stop thread"); gst_rtsp_thread_stop (priv->thread); } + + priv->finishing_unprepare = FALSE; } /* called with state-lock */ @@ -2824,8 +3925,6 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media) goto is_busy; GST_INFO ("unprepare media %p", media); - if (priv->blocked) - media_streams_set_blocked (media, FALSE); set_target_state (media, GST_STATE_NULL, FALSE); success = TRUE; @@ -2836,6 +3935,7 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media) if (klass->unprepare) success = klass->unprepare (media); } else { + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING); finish_unprepare (media); } g_rec_mutex_unlock (&priv->state_lock); @@ -2875,7 +3975,7 @@ get_clock_unlocked (GstRTSPMedia * media) * * @media must be prepared before this method returns a valid clock object. * - * Returns: (transfer full): the #GstClock used by @media. unref after usage. + * Returns: (transfer full) (nullable): the #GstClock used by @media. unref after usage. */ GstClock * gst_rtsp_media_get_clock (GstRTSPMedia * media) @@ -2935,553 +4035,97 @@ not_prepared: /** * 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 const gchar * -rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, - gint pt) -{ - guint i; - - for (i = 0;; i++) { - const gchar *attr; - gint val; - - if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) - break; - - if (sscanf (attr, "%d ", &val) != 1) - continue; - - if (val == pt) - return attr; - } - return NULL; -} - -#define PARSE_INT(p, del, res) \ -G_STMT_START { \ - gchar *t = p; \ - p = strstr (p, del); \ - if (p == NULL) \ - res = -1; \ - else { \ - *p = '\0'; \ - p++; \ - res = atoi (t); \ - } \ -} G_STMT_END - -#define PARSE_STRING(p, del, res) \ -G_STMT_START { \ - gchar *t = p; \ - p = strstr (p, del); \ - if (p == NULL) { \ - res = NULL; \ - p = t; \ - } \ - else { \ - *p = '\0'; \ - p++; \ - res = t; \ - } \ -} G_STMT_END - -#define SKIP_SPACES(p) \ - while (*p && g_ascii_isspace (*p)) \ - p++; - -/* rtpmap contains: - * - * /[/] - */ -static gboolean -parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, - gint * rate, gchar ** params) -{ - gchar *p, *t; - - p = (gchar *) rtpmap; - - PARSE_INT (p, " ", *payload); - if (*payload == -1) - return FALSE; - - SKIP_SPACES (p); - if (*p == '\0') - return FALSE; - - PARSE_STRING (p, "/", *name); - if (*name == NULL) { - GST_DEBUG ("no rate, name %s", p); - /* no rate, assume -1 then, this is not supposed to happen but RealMedia - * streams seem to omit the rate. */ - *name = p; - *rate = -1; - return TRUE; - } - - t = p; - p = strstr (p, "/"); - if (p == NULL) { - *rate = atoi (t); - return TRUE; - } - *p = '\0'; - p++; - *rate = atoi (t); - - t = p; - if (*p == '\0') - return TRUE; - *params = t; - - return TRUE; -} - -/* - * Mapping of caps to and from SDP fields: - * - * a=rtpmap: /[/] - * a=framesize: - - * a=fmtp: [=];... - */ -static GstCaps * -media_to_caps (gint pt, const GstSDPMedia * media) -{ - GstCaps *caps; - const gchar *rtpmap; - const gchar *fmtp; - const gchar *framesize; - gchar *name = NULL; - gint rate = -1; - gchar *params = NULL; - gchar *tmp; - GstStructure *s; - gint payload = 0; - gboolean ret; - - /* get and parse rtpmap */ - rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt); - - if (rtpmap) { - ret = parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); - if (!ret) { - g_warning ("error parsing rtpmap, ignoring"); - rtpmap = NULL; - } - } - /* dynamic payloads need rtpmap or we fail */ - if (rtpmap == NULL && pt >= 96) - goto no_rtpmap; - - /* check if we have a rate, if not, we need to look up the rate from the - * default rates based on the payload types. */ - if (rate == -1) { - const GstRTPPayloadInfo *info; - - if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { - /* dynamic types, use media and encoding_name */ - tmp = g_ascii_strdown (media->media, -1); - info = gst_rtp_payload_info_for_name (tmp, name); - g_free (tmp); - } else { - /* static types, use payload type */ - info = gst_rtp_payload_info_for_pt (pt); - } - - if (info) { - if ((rate = info->clock_rate) == 0) - rate = -1; - } - /* we fail if we cannot find one */ - if (rate == -1) - goto no_rate; - } - - tmp = g_ascii_strdown (media->media, -1); - caps = gst_caps_new_simple ("application/x-unknown", - "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL); - g_free (tmp); - s = gst_caps_get_structure (caps, 0); - - gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL); - - /* encoding name must be upper case */ - if (name != NULL) { - tmp = g_ascii_strup (name, -1); - gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL); - g_free (tmp); - } - - /* params must be lower case */ - if (params != NULL) { - tmp = g_ascii_strdown (params, -1); - gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL); - g_free (tmp); - } - - /* parse optional fmtp: field */ - if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) { - gchar *p; - gint payload = 0; - - p = (gchar *) fmtp; - - /* p is now of the format [=];... */ - PARSE_INT (p, " ", payload); - if (payload != -1 && payload == pt) { - gchar **pairs; - gint i; - - /* [=] are separated with ';' */ - pairs = g_strsplit (p, ";", 0); - for (i = 0; pairs[i]; i++) { - gchar *valpos; - const gchar *val, *key; - - /* the key may not have a '=', the value can have other '='s */ - valpos = strstr (pairs[i], "="); - if (valpos) { - /* we have a '=' and thus a value, remove the '=' with \0 */ - *valpos = '\0'; - /* value is everything between '=' and ';'. We split the pairs at ; - * boundaries so we can take the remainder of the value. Some servers - * put spaces around the value which we strip off here. Alternatively - * we could strip those spaces in the depayloaders should these spaces - * actually carry any meaning in the future. */ - val = g_strstrip (valpos + 1); - } else { - /* simple ;.. is translated into =1;... */ - val = "1"; - } - /* strip the key of spaces, convert key to lowercase but not the value. */ - key = g_strstrip (pairs[i]); - if (strlen (key) > 1) { - tmp = g_ascii_strdown (key, -1); - gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); - g_free (tmp); - } - } - g_strfreev (pairs); - } - } - - /* parse framesize: field */ - if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) { - gchar *p; - - /* p is now of the format - */ - p = (gchar *) framesize; - - PARSE_INT (p, " ", payload); - if (payload != -1 && payload == pt) { - gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); - } - } - return caps; - - /* ERRORS */ -no_rtpmap: - { - g_warning ("rtpmap type not given for dynamic payload %d", pt); - return NULL; - } -no_rate: - { - g_warning ("rate unknown for payload type %d", pt); - return NULL; - } -} - -static gboolean -parse_keymgmt (const gchar * keymgmt, GstCaps * caps) + * @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) { - gboolean res = FALSE; - gchar *p, *kmpid; - gsize size; - guchar *data; - GstMIKEYMessage *msg; - const GstMIKEYPayload *payload; - const gchar *srtp_cipher; - const gchar *srtp_auth; - - p = (gchar *) keymgmt; + GstRTSPMediaPrivate *priv; + GstNetTimeProvider *provider = NULL; - SKIP_SPACES (p); - if (*p == '\0') - return FALSE; + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); - PARSE_STRING (p, " ", kmpid); - if (!g_str_equal (kmpid, "mikey")) - return FALSE; + priv = media->priv; - data = g_base64_decode (p, &size); - if (data == NULL) - return FALSE; + g_rec_mutex_lock (&priv->state_lock); + if (priv->time_provider) { + if ((provider = priv->nettime) == NULL) { + GstClock *clock; - msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); - g_free (data); - if (msg == NULL) - return FALSE; + if (priv->time_provider && (clock = get_clock_unlocked (media))) { + provider = gst_net_time_provider_new (clock, address, port); + gst_object_unref (clock); - srtp_cipher = "aes-128-icm"; - srtp_auth = "hmac-sha1-80"; - - /* check the Security policy if any */ - if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) { - GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload; - guint len, i; - - if (p->proto != GST_MIKEY_SEC_PROTO_SRTP) - goto done; - - len = gst_mikey_payload_sp_get_n_params (payload); - for (i = 0; i < len; i++) { - const GstMIKEYPayloadSPParam *param = - gst_mikey_payload_sp_get_param (payload, i); - - switch (param->type) { - case GST_MIKEY_SP_SRTP_ENC_ALG: - switch (param->val[0]) { - case 0: - srtp_cipher = "null"; - break; - case 2: - case 1: - srtp_cipher = "aes-128-icm"; - break; - default: - break; - } - break; - case GST_MIKEY_SP_SRTP_ENC_KEY_LEN: - switch (param->val[0]) { - case AES_128_KEY_LEN: - srtp_cipher = "aes-128-icm"; - break; - case AES_256_KEY_LEN: - srtp_cipher = "aes-256-icm"; - break; - default: - break; - } - break; - case GST_MIKEY_SP_SRTP_AUTH_ALG: - switch (param->val[0]) { - case 0: - srtp_auth = "null"; - break; - case 2: - case 1: - srtp_auth = "hmac-sha1-80"; - break; - default: - break; - } - break; - case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN: - switch (param->val[0]) { - case HMAC_32_KEY_LEN: - srtp_auth = "hmac-sha1-32"; - break; - case HMAC_80_KEY_LEN: - srtp_auth = "hmac-sha1-80"; - break; - default: - break; - } - break; - case GST_MIKEY_SP_SRTP_SRTP_ENC: - break; - case GST_MIKEY_SP_SRTP_SRTCP_ENC: - break; - default: - break; + priv->nettime = provider; } } } + g_rec_mutex_unlock (&priv->state_lock); - if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0))) - goto done; - else { - GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload; - const GstMIKEYPayload *sub; - GstMIKEYPayloadKeyData *pkd; - GstBuffer *buf; - - if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL) - goto done; - - if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0))) - goto done; - - if (sub->type != GST_MIKEY_PT_KEY_DATA) - goto done; - - pkd = (GstMIKEYPayloadKeyData *) sub; - buf = - gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len), - pkd->key_len); - gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL); - } - - gst_caps_set_simple (caps, - "srtp-cipher", G_TYPE_STRING, srtp_cipher, - "srtp-auth", G_TYPE_STRING, srtp_auth, - "srtcp-cipher", G_TYPE_STRING, srtp_cipher, - "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL); + if (provider) + gst_object_ref (provider); - res = TRUE; -done: - gst_mikey_message_unref (msg); + return provider; +} - return res; +static gboolean +default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info) +{ + return gst_rtsp_sdp_from_media (sdp, info, media); } -/* - * Mapping SDP attributes to caps +/** + * 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. * - * prepend 'a-' to IANA registered sdp attributes names - * (ie: not prefixed with 'x-') in order to avoid - * collision with gstreamer standard caps properties names + * Returns: TRUE on success. */ -static void -sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) +gboolean +gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, + GstSDPInfo * info) { - if (attributes->len > 0) { - GstStructure *s; - guint i; + GstRTSPMediaPrivate *priv; + GstRTSPMediaClass *klass; + gboolean res; - s = gst_caps_get_structure (caps, 0); + 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); - for (i = 0; i < attributes->len; i++) { - GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); - gchar *tofree, *key; + priv = media->priv; - key = attr->key; + g_rec_mutex_lock (&priv->state_lock); - /* skip some of the attribute we already handle */ - if (!strcmp (key, "fmtp")) - continue; - if (!strcmp (key, "rtpmap")) - continue; - if (!strcmp (key, "control")) - continue; - if (!strcmp (key, "range")) - continue; - if (!strcmp (key, "framesize")) - continue; - if (g_str_equal (key, "key-mgmt")) { - parse_keymgmt (attr->value, caps); - continue; - } + klass = GST_RTSP_MEDIA_GET_CLASS (media); - /* string must be valid UTF8 */ - if (!g_utf8_validate (attr->value, -1, NULL)) - continue; + if (!klass->setup_sdp) + goto no_setup_sdp; - if (!g_str_has_prefix (key, "x-")) - tofree = key = g_strdup_printf ("a-%s", key); - else - tofree = NULL; + res = klass->setup_sdp (media, sdp, info); - GST_DEBUG ("adding caps: %s=%s", key, attr->value); - gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); - g_free (tofree); - } + 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; } } @@ -3499,7 +4143,7 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) } for (i = 0; i < medias_len; i++) { - const gchar *proto, *media_type; + const gchar *proto; const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i); GstRTSPStream *stream; gint j, formats_len; @@ -3518,16 +4162,12 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) } if (g_str_equal (proto, "RTP/AVP")) { - media_type = "application/x-rtp"; profile = GST_RTSP_PROFILE_AVP; } else if (g_str_equal (proto, "RTP/SAVP")) { - media_type = "application/x-srtp"; profile = GST_RTSP_PROFILE_SAVP; } else if (g_str_equal (proto, "RTP/AVPF")) { - media_type = "application/x-rtp"; profile = GST_RTSP_PROFILE_AVPF; } else if (g_str_equal (proto, "RTP/SAVPF")) { - media_type = "application/x-srtp"; profile = GST_RTSP_PROFILE_SAVPF; } else { GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i); @@ -3551,7 +4191,7 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) GST_DEBUG (" looking at %d pt: %d", j, pt); /* convert caps */ - caps = media_to_caps (pt, sdp_media); + caps = gst_sdp_media_get_caps_from_media (sdp_media, pt); if (caps == NULL) { GST_WARNING (" skipping pt %d without caps", pt); continue; @@ -3559,12 +4199,15 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) /* do some tweaks */ GST_DEBUG ("mapping sdp session level attributes to caps"); - sdp_attributes_to_caps (sdp->attributes, caps); + gst_sdp_message_attributes_to_caps (sdp, caps); GST_DEBUG ("mapping sdp media level attributes to caps"); - sdp_attributes_to_caps (sdp_media->attributes, caps); + gst_sdp_media_attributes_to_caps (sdp_media, caps); s = gst_caps_get_structure (caps, 0); - gst_structure_set_name (s, media_type); + gst_structure_set_name (s, "application/x-rtp"); + + if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC")) + gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL); gst_rtsp_stream_set_pt_map (stream, pt, caps); gst_caps_unref (caps); @@ -3627,12 +4270,15 @@ 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); + + if (gst_rtsp_stream_is_sender (stream)) { + seq_num = gst_rtsp_stream_get_current_seqnum (stream); + gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1); + } } /* call with state_lock */ -gboolean +static gboolean default_suspend (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; @@ -3664,9 +4310,6 @@ default_suspend (GstRTSPMedia * media) break; } - /* let the streams do the state changes freely, if any */ - media_streams_set_blocked (media, FALSE); - return TRUE; /* ERRORS */ @@ -3736,14 +4379,32 @@ suspend_failed: } /* call with state_lock */ -gboolean +static gboolean default_unsuspend (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; + gboolean preroll_ok; switch (priv->suspend_mode) { case GST_RTSP_SUSPEND_MODE_NONE: - gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + if (gst_rtsp_media_is_receive_only (media)) + break; + if (media_streams_blocking (media)) { + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + /* at this point the media pipeline has been updated and contain all + * specific transport parts: all active streams contain at least one sink + * element and it's safe to unblock all blocked streams */ + media_unblock (media); + } else { + /* streams are not blocked and media is suspended from PAUSED */ + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); + } + g_rec_mutex_unlock (&priv->state_lock); + if (gst_rtsp_media_get_status (media) == GST_RTSP_MEDIA_STATUS_ERROR) { + g_rec_mutex_lock (&priv->state_lock); + goto preroll_failed; + } + g_rec_mutex_lock (&priv->state_lock); break; case GST_RTSP_SUSPEND_MODE_PAUSE: gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); @@ -3751,14 +4412,19 @@ default_unsuspend (GstRTSPMedia * media) case GST_RTSP_SUSPEND_MODE_RESET: { gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + /* at this point the media pipeline has been updated and contain all + * specific transport parts: all active streams contain at least one sink + * element and it's safe to unblock all blocked streams */ + media_unblock (media); if (!start_preroll (media)) goto start_failed; + g_rec_mutex_unlock (&priv->state_lock); + preroll_ok = wait_preroll (media); + g_rec_mutex_lock (&priv->state_lock); - if (!wait_preroll (media)) + if (!preroll_ok) goto preroll_failed; - - g_rec_mutex_lock (&priv->state_lock); } default: break; @@ -3826,6 +4492,8 @@ static void media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) { GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn set_state_ret; + priv->expected_async_done = FALSE; if (state == GST_STATE_NULL) { gst_rtsp_media_unprepare (media); @@ -3839,13 +4507,17 @@ media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) } else { if (state == GST_STATE_PLAYING) /* make sure pads are not blocking anymore when going to PLAYING */ - media_streams_set_blocked (media, FALSE); + media_unblock (media); - set_state (media, state); - - /* and suspend after pause */ - if (state == GST_STATE_PAUSED) + if (state == GST_STATE_PAUSED) { + set_state_ret = set_state (media, state); + if (set_state_ret == GST_STATE_CHANGE_ASYNC) + priv->expected_async_done = TRUE; + /* and suspend after pause */ gst_rtsp_media_suspend (media); + } else { + set_state (media, state); + } } } } @@ -3895,6 +4567,13 @@ 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_PREPARING + && gst_rtsp_media_is_shared (media)) { + g_rec_mutex_unlock (&priv->state_lock); + gst_rtsp_media_get_status (media); + g_rec_mutex_lock (&priv->state_lock); + } if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR) goto error_status; if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && @@ -3953,8 +4632,10 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, /* we just activated the first media, do the playing state change */ if (old_active == 0 && activate) do_state = TRUE; - /* if we have no more active media, do the downward state changes */ - else if (priv->n_active == 0) + /* if we have no more active media and prepare count is not indicate + * that there are new session/sessions ongoing, + * do the downward state changes */ + else if (priv->n_active == 0 && priv->prepare_count <= 1) do_state = TRUE; else do_state = FALSE; @@ -3963,11 +4644,11 @@ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, media, do_state); if (priv->target_state != state) { - if (do_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); + g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state, + NULL); + } } /* remember where we are */ @@ -4054,3 +4735,199 @@ gst_rtsp_media_get_transport_mode (GstRTSPMedia * media) return res; } + +/** + * gst_rtsp_media_seekable: + * @media: a #GstRTSPMedia + * + * Check if the pipeline for @media seek and up to what point in time, + * it can seek. + * + * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning + * and > 0 to indicate the longest duration between any two random access points. + * %G_MAXINT64 means any value is possible. + * + * Since: 1.14 + */ +GstClockTimeDiff +gst_rtsp_media_seekable (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + GstClockTimeDiff res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + /* Currently we are not able to seek on live streams, + * and no stream is seekable only to the beginning */ + g_mutex_lock (&priv->lock); + res = priv->seekable; + g_mutex_unlock (&priv->lock); + + return res; +} + +/** + * gst_rtsp_media_complete_pipeline: + * @media: a #GstRTSPMedia + * @transports: (element-type GstRTSPTransport): a list of #GstRTSPTransport + * + * Add a receiver and sender parts to the pipeline based on the transport from + * SETUP. + * + * Returns: %TRUE if the media pipeline has been sucessfully updated. + * + * Since: 1.14 + */ +gboolean +gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports) +{ + GstRTSPMediaPrivate *priv; + guint i; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + g_return_val_if_fail (transports, FALSE); + + GST_DEBUG_OBJECT (media, "complete pipeline"); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStreamTransport *transport; + GstRTSPStream *stream; + const GstRTSPTransport *rtsp_transport; + + transport = g_ptr_array_index (transports, i); + if (!transport) + continue; + + stream = gst_rtsp_stream_transport_get_stream (transport); + if (!stream) + continue; + + rtsp_transport = gst_rtsp_stream_transport_get_transport (transport); + + if (!gst_rtsp_stream_complete_stream (stream, rtsp_transport)) { + g_mutex_unlock (&priv->lock); + return FALSE; + } + } + + priv->complete = TRUE; + g_mutex_unlock (&priv->lock); + + return TRUE; +} + +/** + * gst_rtsp_media_is_receive_only: + * + * Returns: %TRUE if @media is receive-only, %FALSE otherwise. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_is_receive_only (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + gboolean receive_only = TRUE; + guint i; + + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + if (gst_rtsp_stream_is_sender (stream) || + !gst_rtsp_stream_is_receiver (stream)) { + receive_only = FALSE; + break; + } + } + + return receive_only; +} + +/** + * gst_rtsp_media_has_completed_sender: + * + * See gst_rtsp_stream_is_complete(), gst_rtsp_stream_is_sender(). + * + * Returns: whether @media has at least one complete sender stream. + * Since: 1.18 + */ +gboolean +gst_rtsp_media_has_completed_sender (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv = media->priv; + gboolean sender = FALSE; + guint i; + + g_mutex_lock (&priv->lock); + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + if (gst_rtsp_stream_is_complete (stream)) + if (gst_rtsp_stream_is_sender (stream) || + !gst_rtsp_stream_is_receiver (stream)) { + sender = TRUE; + break; + } + } + g_mutex_unlock (&priv->lock); + + return sender; +} + +/** + * gst_rtsp_media_set_rate_control: + * + * Define whether @media will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +void +gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled) +{ + GstRTSPMediaPrivate *priv; + guint i; + + g_return_if_fail (GST_IS_RTSP_MEDIA (media)); + + GST_LOG_OBJECT (media, "%s rate control", enabled ? "Enabling" : "Disabling"); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + priv->do_rate_control = enabled; + for (i = 0; i < priv->streams->len; i++) { + GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); + + gst_rtsp_stream_set_rate_control (stream, enabled); + + } + g_mutex_unlock (&priv->lock); +} + +/** + * gst_rtsp_media_get_rate_control: + * + * Returns: whether @media will follow the Rate-Control=no behaviour as specified + * in the ONVIF replay spec. + * + * Since: 1.18 + */ +gboolean +gst_rtsp_media_get_rate_control (GstRTSPMedia * media) +{ + GstRTSPMediaPrivate *priv; + gboolean res; + + g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); + + priv = media->priv; + + g_mutex_lock (&priv->lock); + res = priv->do_rate_control; + g_mutex_unlock (&priv->lock); + + return res; +}