/* GStreamer * Copyright (C) 2008 Wim Taymans * Copyright (C) 2015 Centricular Ltd * Author: Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:rtsp-media * @short_description: The media pipeline * @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession, * #GstRTSPSessionMedia * * a #GstRTSPMedia contains the complete GStreamer pipeline to manage the * streaming to the clients. The actual data transfer is done by the * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia. * * The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the * client does a DESCRIBE or SETUP of a resource. * * A media is created with gst_rtsp_media_new() that takes the element that will * provide the streaming elements. For each of the streams, a new #GstRTSPStream * object needs to be made with the gst_rtsp_media_create_stream() which takes * the payloader element and the source pad that produces the RTP stream. * * The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The * prepare method will add rtpbin and sinks and sources to send and receive RTP * and RTCP packets from the clients. Each stream srcpad is connected to an * input into the internal rtpbin. * * It is also possible to dynamically create #GstRTSPStream objects during the * prepare phase. With gst_rtsp_media_get_status() you can check the status of * the prepare phase. * * After the media is prepared, it is ready for streaming. It will usually be * managed in a session with gst_rtsp_session_manage_media(). See * #GstRTSPSession and #GstRTSPSessionMedia. * * The state of the media can be controlled with gst_rtsp_media_set_state (). * Seeking can be done with gst_rtsp_media_seek(). * * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to * cleanly shut down. * * With gst_rtsp_media_set_shared(), the media can be shared between multiple * clients. With gst_rtsp_media_set_reusable() you can control if the pipeline * can be prepared again after an unprepare. * * Last reviewed on 2013-07-11 (1.0.0) */ #include #include #include #include #include #include #include #define AES_128_KEY_LEN 16 #define AES_256_KEY_LEN 32 #define HMAC_32_KEY_LEN 4 #define HMAC_80_KEY_LEN 10 #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; GCond cond; /* protected by lock */ GstRTSPPermissions *permissions; gboolean shared; gboolean suspend_mode; gboolean reusable; GstRTSPProfile profiles; GstRTSPLowerTrans protocols; gboolean reused; gboolean eos_shutdown; guint buffer_size; GstRTSPAddressPool *pool; gboolean blocked; GstRTSPTransportMode transport_mode; GstElement *element; GRecMutex state_lock; /* locking order: state lock, lock */ GPtrArray *streams; /* protected by lock */ GList *dynamic; /* protected by lock */ GstRTSPMediaStatus status; /* protected by lock */ gint prepare_count; gint n_active; gboolean adding; /* the pipeline for the media */ GstElement *pipeline; GstElement *fakesink; /* protected by lock */ GSource *source; guint id; GstRTSPThread *thread; gboolean time_provider; GstNetTimeProvider *nettime; gboolean is_live; gboolean seekable; gboolean buffering; GstState target_state; /* RTP session manager */ GstElement *rtpbin; /* the range of media */ GstRTSPTimeRange range; /* protected by lock */ GstClockTime range_start; GstClockTime range_stop; GList *payloads; /* protected by lock */ GstClockTime rtx_time; /* protected by lock */ guint latency; /* protected by lock */ }; #define DEFAULT_SHARED FALSE #define DEFAULT_SUSPEND_MODE GST_RTSP_SUSPEND_MODE_NONE #define DEFAULT_REUSABLE FALSE #define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \ GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_EOS_SHUTDOWN FALSE #define DEFAULT_BUFFER_SIZE 0x80000 #define DEFAULT_TIME_PROVIDER FALSE #define DEFAULT_LATENCY 200 #define DEFAULT_TRANSPORT_MODE GST_RTSP_TRANSPORT_MODE_PLAY /* define to dump received RTCP packets */ #undef DUMP_STATS enum { PROP_0, PROP_SHARED, PROP_SUSPEND_MODE, PROP_REUSABLE, PROP_PROFILES, PROP_PROTOCOLS, PROP_EOS_SHUTDOWN, PROP_BUFFER_SIZE, PROP_ELEMENT, PROP_TIME_PROVIDER, PROP_LATENCY, PROP_TRANSPORT_MODE, PROP_LAST }; enum { SIGNAL_NEW_STREAM, SIGNAL_REMOVED_STREAM, SIGNAL_PREPARED, SIGNAL_UNPREPARED, SIGNAL_TARGET_STATE, SIGNAL_NEW_STATE, SIGNAL_LAST }; GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug); #define GST_CAT_DEFAULT rtsp_media_debug static void gst_rtsp_media_get_property (GObject * object, guint propid, GValue * value, GParamSpec * pspec); static void gst_rtsp_media_set_property (GObject * object, guint propid, const GValue * value, GParamSpec * pspec); static void gst_rtsp_media_finalize (GObject * obj); static gboolean default_handle_message (GstRTSPMedia * media, GstMessage * message); static void finish_unprepare (GstRTSPMedia * media); static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread); static gboolean default_unprepare (GstRTSPMedia * media); static gboolean default_suspend (GstRTSPMedia * media); static gboolean default_unsuspend (GstRTSPMedia * media); static gboolean default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, GstRTSPRangeUnit unit); static gboolean default_query_position (GstRTSPMedia * media, gint64 * position); static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop); static GstElement *default_create_rtpbin (GstRTSPMedia * media); static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info); static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp); static gboolean wait_preroll (GstRTSPMedia * media); static GstElement * find_payload_element (GstElement * payloader); static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 }; #define C_ENUM(v) ((gint) v) 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; } #define C_FLAGS(v) ((guint) v) GType gst_rtsp_transport_mode_get_type (void) { static gsize id = 0; static const GFlagsValue values[] = { {C_FLAGS (GST_RTSP_TRANSPORT_MODE_PLAY), "GST_RTSP_TRANSPORT_MODE_PLAY", "play"}, {C_FLAGS (GST_RTSP_TRANSPORT_MODE_RECORD), "GST_RTSP_TRANSPORT_MODE_RECORD", "record"}, {0, NULL, NULL} }; if (g_once_init_enter (&id)) { GType tmp = g_flags_register_static ("GstRTSPTransportMode", values); g_once_init_leave (&id, tmp); } return (GType) id; } G_DEFINE_TYPE (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; gobject_class->set_property = gst_rtsp_media_set_property; gobject_class->finalize = gst_rtsp_media_finalize; g_object_class_install_property (gobject_class, PROP_SHARED, g_param_spec_boolean ("shared", "Shared", "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, DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_EOS_SHUTDOWN, g_param_spec_boolean ("eos-shutdown", "EOS Shutdown", "Send an EOS event to the pipeline before unpreparing", DEFAULT_EOS_SHUTDOWN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE, g_param_spec_uint ("buffer-size", "Buffer Size", "The kernel UDP buffer size to use", 0, G_MAXUINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ELEMENT, g_param_spec_object ("element", "The Element", "The GstBin to use for streaming the media", GST_TYPE_ELEMENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_TIME_PROVIDER, g_param_spec_boolean ("time-provider", "Time Provider", "Use a NetTimeProvider for clients", DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_LATENCY, g_param_spec_uint ("latency", "Latency", "Latency used for receiving media in milliseconds", 0, G_MAXUINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TRANSPORT_MODE, g_param_spec_flags ("transport-mode", "Transport Mode", "If this media pipeline can be used for PLAY or RECORD", GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_rtsp_media_signals[SIGNAL_NEW_STREAM] = g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM); gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM] = g_signal_new ("removed-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, removed_stream), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM); gst_rtsp_media_signals[SIGNAL_PREPARED] = g_signal_new ("prepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, prepared), NULL, NULL, g_cclosure_marshal_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_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_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; klass->handle_sdp = default_handle_sdp; } static void gst_rtsp_media_init (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = GST_RTSP_MEDIA_GET_PRIVATE (media); media->priv = priv; priv->streams = g_ptr_array_new_with_free_func (g_object_unref); g_mutex_init (&priv->lock); g_cond_init (&priv->cond); g_rec_mutex_init (&priv->state_lock); priv->shared = DEFAULT_SHARED; priv->suspend_mode = DEFAULT_SUSPEND_MODE; priv->reusable = DEFAULT_REUSABLE; priv->profiles = DEFAULT_PROFILES; priv->protocols = DEFAULT_PROTOCOLS; priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN; priv->buffer_size = DEFAULT_BUFFER_SIZE; priv->time_provider = DEFAULT_TIME_PROVIDER; priv->transport_mode = DEFAULT_TRANSPORT_MODE; } static void gst_rtsp_media_finalize (GObject * obj) { GstRTSPMediaPrivate *priv; GstRTSPMedia *media; media = GST_RTSP_MEDIA (obj); priv = media->priv; GST_INFO ("finalize media %p", media); if (priv->permissions) gst_rtsp_permissions_unref (priv->permissions); g_ptr_array_unref (priv->streams); g_list_free_full (priv->dynamic, gst_object_unref); if (priv->pipeline) gst_object_unref (priv->pipeline); if (priv->nettime) gst_object_unref (priv->nettime); gst_object_unref (priv->element); if (priv->pool) g_object_unref (priv->pool); if (priv->payloads) g_list_free (priv->payloads); g_mutex_clear (&priv->lock); g_cond_clear (&priv->cond); g_rec_mutex_clear (&priv->state_lock); G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj); } static void gst_rtsp_media_get_property (GObject * object, guint propid, GValue * value, GParamSpec * pspec) { GstRTSPMedia *media = GST_RTSP_MEDIA (object); switch (propid) { case PROP_ELEMENT: g_value_set_object (value, media->priv->element); break; 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; case PROP_EOS_SHUTDOWN: g_value_set_boolean (value, gst_rtsp_media_is_eos_shutdown (media)); break; case PROP_BUFFER_SIZE: g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media)); break; case PROP_TIME_PROVIDER: g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media)); break; case PROP_LATENCY: g_value_set_uint (value, gst_rtsp_media_get_latency (media)); break; case PROP_TRANSPORT_MODE: g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } } static void gst_rtsp_media_set_property (GObject * object, guint propid, const GValue * value, GParamSpec * pspec) { GstRTSPMedia *media = GST_RTSP_MEDIA (object); switch (propid) { case PROP_ELEMENT: media->priv->element = g_value_get_object (value); gst_object_ref_sink (media->priv->element); break; case PROP_SHARED: gst_rtsp_media_set_shared (media, g_value_get_boolean (value)); break; case PROP_SUSPEND_MODE: gst_rtsp_media_set_suspend_mode (media, g_value_get_enum (value)); break; case PROP_REUSABLE: gst_rtsp_media_set_reusable (media, g_value_get_boolean (value)); break; case PROP_PROFILES: gst_rtsp_media_set_profiles (media, g_value_get_flags (value)); break; case PROP_PROTOCOLS: gst_rtsp_media_set_protocols (media, g_value_get_flags (value)); break; case PROP_EOS_SHUTDOWN: gst_rtsp_media_set_eos_shutdown (media, g_value_get_boolean (value)); break; case PROP_BUFFER_SIZE: gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value)); break; case PROP_TIME_PROVIDER: gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value)); break; case PROP_LATENCY: gst_rtsp_media_set_latency (media, g_value_get_uint (value)); break; case PROP_TRANSPORT_MODE: gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } } 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 = 0, stop = -1; if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) return; priv->range.unit = GST_RTSP_RANGE_NPT; GST_INFO ("collect media stats"); if (priv->is_live) { priv->range.min.type = GST_RTSP_TIME_NOW; priv->range.min.seconds = -1; priv->range_start = -1; priv->range.max.type = GST_RTSP_TIME_END; priv->range.max.seconds = -1; priv->range_stop = -1; } else { GstRTSPMediaClass *klass; gboolean ret; klass = GST_RTSP_MEDIA_GET_CLASS (media); /* get the position */ ret = FALSE; if (klass->query_position) ret = klass->query_position (media, &position); if (!ret) { GST_INFO ("position query failed"); position = 0; } /* get the current segment stop */ ret = FALSE; if (klass->query_stop) ret = klass->query_stop (media, &stop); if (!ret) { GST_INFO ("stop query failed"); stop = -1; } GST_INFO ("stats: position %" GST_TIME_FORMAT ", stop %" GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (stop)); if (position == -1) { priv->range.min.type = GST_RTSP_TIME_NOW; priv->range.min.seconds = -1; priv->range_start = -1; } else { priv->range.min.type = GST_RTSP_TIME_SECONDS; priv->range.min.seconds = ((gdouble) position) / GST_SECOND; priv->range_start = position; } if (stop == -1) { priv->range.max.type = GST_RTSP_TIME_END; priv->range.max.seconds = -1; priv->range_stop = -1; } else { priv->range.max.type = GST_RTSP_TIME_SECONDS; priv->range.max.seconds = ((gdouble) stop) / GST_SECOND; priv->range_stop = stop; } } } /** * gst_rtsp_media_new: * @element: (transfer full): a #GstElement * * Create a new #GstRTSPMedia instance. @element is the bin element that * provides the different streams. The #GstRTSPMedia object contains the * element to produce RTP data for one or more related (audio/video/..) * streams. * * Ownership is taken of @element. * * Returns: (transfer full): a new #GstRTSPMedia object. */ GstRTSPMedia * gst_rtsp_media_new (GstElement * element) { GstRTSPMedia *result; g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); result = g_object_new (GST_TYPE_RTSP_MEDIA, "element", element, NULL); return result; } /** * gst_rtsp_media_get_element: * @media: a #GstRTSPMedia * * Get the element that was used when constructing @media. * * Returns: (transfer full): a #GstElement. Unref after usage. */ GstElement * gst_rtsp_media_get_element (GstRTSPMedia * media) { g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); return gst_object_ref (media->priv->element); } /** * gst_rtsp_media_take_pipeline: * @media: a #GstRTSPMedia * @pipeline: (transfer full): a #GstPipeline * * Set @pipeline as the #GstPipeline for @media. Ownership is * taken of @pipeline. */ void gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline) { GstRTSPMediaPrivate *priv; GstElement *old; GstNetTimeProvider *nettime; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); g_return_if_fail (GST_IS_PIPELINE (pipeline)); priv = media->priv; g_mutex_lock (&priv->lock); old = priv->pipeline; priv->pipeline = GST_ELEMENT_CAST (pipeline); nettime = priv->nettime; priv->nettime = NULL; g_mutex_unlock (&priv->lock); if (old) gst_object_unref (old); if (nettime) gst_object_unref (nettime); gst_bin_add (GST_BIN_CAST (pipeline), priv->element); } /** * gst_rtsp_media_set_permissions: * @media: a #GstRTSPMedia * @permissions: (transfer none): a #GstRTSPPermissions * * Set @permissions on @media. */ void gst_rtsp_media_set_permissions (GstRTSPMedia * media, GstRTSPPermissions * permissions) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); if (priv->permissions) gst_rtsp_permissions_unref (priv->permissions); if ((priv->permissions = permissions)) gst_rtsp_permissions_ref (permissions); g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_permissions: * @media: a #GstRTSPMedia * * Get the permissions object from @media. * * Returns: (transfer full): a #GstRTSPPermissions object, unref after usage. */ GstRTSPPermissions * gst_rtsp_media_get_permissions (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstRTSPPermissions *result; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); priv = media->priv; g_mutex_lock (&priv->lock); if ((result = priv->permissions)) gst_rtsp_permissions_ref (result); g_mutex_unlock (&priv->lock); return result; } /** * gst_rtsp_media_set_suspend_mode: * @media: a #GstRTSPMedia * @mode: the new #GstRTSPSuspendMode * * Control how @ media will be suspended after the SDP has been generated and * after a PAUSE request has been performed. * * Media must be unprepared when setting the suspend mode. */ void gst_rtsp_media_set_suspend_mode (GstRTSPMedia * media, GstRTSPSuspendMode mode) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) goto was_prepared; priv->suspend_mode = mode; g_rec_mutex_unlock (&priv->state_lock); return; /* ERRORS */ was_prepared: { GST_WARNING ("media %p was prepared", media); g_rec_mutex_unlock (&priv->state_lock); } } /** * gst_rtsp_media_get_suspend_mode: * @media: a #GstRTSPMedia * * Get how @media will be suspended. * * Returns: #GstRTSPSuspendMode. */ GstRTSPSuspendMode gst_rtsp_media_get_suspend_mode (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstRTSPSuspendMode res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_SUSPEND_MODE_NONE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); res = priv->suspend_mode; g_rec_mutex_unlock (&priv->state_lock); return res; } /** * gst_rtsp_media_set_shared: * @media: a #GstRTSPMedia * @shared: the new value * * Set or unset if the pipeline for @media can be shared will multiple clients. * When @shared is %TRUE, client requests for this media will share the media * pipeline. */ void gst_rtsp_media_set_shared (GstRTSPMedia * media, gboolean shared) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->shared = shared; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_is_shared: * @media: a #GstRTSPMedia * * Check if the pipeline for @media can be shared between multiple clients. * * Returns: %TRUE if the media can be shared between clients. */ gboolean gst_rtsp_media_is_shared (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->shared; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_reusable: * @media: a #GstRTSPMedia * @reusable: the new value * * Set or unset if the pipeline for @media can be reused after the pipeline has * been unprepared. */ void gst_rtsp_media_set_reusable (GstRTSPMedia * media, gboolean reusable) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->reusable = reusable; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_is_reusable: * @media: a #GstRTSPMedia * * Check if the pipeline for @media can be reused after an unprepare. * * Returns: %TRUE if the media can be reused */ gboolean gst_rtsp_media_is_reusable (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->reusable; g_mutex_unlock (&priv->lock); 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 * @protocols: the new flags * * Configure the allowed lower transport for @media. */ void gst_rtsp_media_set_protocols (GstRTSPMedia * media, GstRTSPLowerTrans protocols) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->protocols = protocols; g_ptr_array_foreach (priv->streams, (GFunc) do_set_protocols, &protocols); g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_protocols: * @media: a #GstRTSPMedia * * Get the allowed protocols of @media. * * Returns: a #GstRTSPLowerTrans */ GstRTSPLowerTrans gst_rtsp_media_get_protocols (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstRTSPLowerTrans res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_LOWER_TRANS_UNKNOWN); priv = media->priv; g_mutex_lock (&priv->lock); res = priv->protocols; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_eos_shutdown: * @media: a #GstRTSPMedia * @eos_shutdown: the new value * * Set or unset if an EOS event will be sent to the pipeline for @media before * it is unprepared. */ void gst_rtsp_media_set_eos_shutdown (GstRTSPMedia * media, gboolean eos_shutdown) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->eos_shutdown = eos_shutdown; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_is_eos_shutdown: * @media: a #GstRTSPMedia * * Check if the pipeline for @media will send an EOS down the pipeline before * unpreparing. * * Returns: %TRUE if the media will send EOS before unpreparing. */ gboolean gst_rtsp_media_is_eos_shutdown (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->eos_shutdown; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_buffer_size: * @media: a #GstRTSPMedia * @size: the new value * * Set the kernel UDP buffer size. */ void gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); GST_LOG_OBJECT (media, "set buffer size %u", size); priv = media->priv; g_mutex_lock (&priv->lock); priv->buffer_size = size; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_buffer_size: * @media: a #GstRTSPMedia * * Get the kernel UDP buffer size. * * Returns: the kernel UDP buffer size. */ guint gst_rtsp_media_get_buffer_size (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->buffer_size; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_retransmission_time: * @media: a #GstRTSPMedia * @time: the new value * * Set the amount of time to store retransmission packets. */ void gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time) { GstRTSPMediaPrivate *priv; guint i; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); GST_LOG_OBJECT (media, "set retransmission time %" G_GUINT64_FORMAT, time); priv = media->priv; g_mutex_lock (&priv->lock); priv->rtx_time = time; for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); 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); } /** * gst_rtsp_media_get_retransmission_time: * @media: a #GstRTSPMedia * * Get the amount of time to store retransmission data. * * Returns: the amount of time to store retransmission data. */ GstClockTime gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstClockTime res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_mutex_unlock (&priv->lock); res = priv->rtx_time; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_latncy: * @media: a #GstRTSPMedia * @latency: latency in milliseconds * * Configure the latency used for receiving media. */ void gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); GST_LOG_OBJECT (media, "set latency %ums", latency); priv = media->priv; g_mutex_lock (&priv->lock); priv->latency = latency; if (priv->rtpbin) g_object_set (priv->rtpbin, "latency", latency, NULL); g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_latency: * @media: a #GstRTSPMedia * * Get the latency that is used for receiving media. * * Returns: latency in milliseconds */ guint gst_rtsp_media_get_latency (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; guint res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_mutex_unlock (&priv->lock); res = priv->latency; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_use_time_provider: * @media: a #GstRTSPMedia * @time_provider: if a #GstNetTimeProvider should be used * * Set @media to provide a #GstNetTimeProvider. */ void gst_rtsp_media_use_time_provider (GstRTSPMedia * media, gboolean time_provider) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->time_provider = time_provider; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_is_time_provider: * @media: a #GstRTSPMedia * * Check if @media can provide a #GstNetTimeProvider for its pipeline clock. * * Use gst_rtsp_media_get_time_provider() to get the network clock. * * Returns: %TRUE if @media can provide a #GstNetTimeProvider. */ gboolean gst_rtsp_media_is_time_provider (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; gboolean res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_mutex_unlock (&priv->lock); res = priv->time_provider; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_set_address_pool: * @media: a #GstRTSPMedia * @pool: (transfer none): a #GstRTSPAddressPool * * configure @pool to be used as the address pool of @media. */ void gst_rtsp_media_set_address_pool (GstRTSPMedia * media, GstRTSPAddressPool * pool) { GstRTSPMediaPrivate *priv; GstRTSPAddressPool *old; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; GST_LOG_OBJECT (media, "set address pool %p", pool); g_mutex_lock (&priv->lock); if ((old = priv->pool) != pool) priv->pool = pool ? g_object_ref (pool) : NULL; else old = NULL; g_ptr_array_foreach (priv->streams, (GFunc) gst_rtsp_stream_set_address_pool, pool); g_mutex_unlock (&priv->lock); if (old) g_object_unref (old); } /** * gst_rtsp_media_get_address_pool: * @media: a #GstRTSPMedia * * Get the #GstRTSPAddressPool used as the address pool of @media. * * Returns: (transfer full): the #GstRTSPAddressPool of @media. g_object_unref() after * usage. */ GstRTSPAddressPool * gst_rtsp_media_get_address_pool (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstRTSPAddressPool *result; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); priv = media->priv; g_mutex_lock (&priv->lock); if ((result = priv->pool)) g_object_ref (result); 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++) { GList *iter = g_list_find (payloads, GINT_TO_POINTER (i)); if (!iter) return GPOINTER_TO_UINT (i); } return 0; } /** * gst_rtsp_media_collect_streams: * @media: a #GstRTSPMedia * * 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 * the list of dynamic elements. * * Find all depayloader elements, they should be named depay\%d in the * element of @media, and create #GstRTSPStreams for them. */ void gst_rtsp_media_collect_streams (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstElement *element, *elem; GstPad *pad; gint i; gboolean have_elem; gboolean more_elem_remaining = TRUE; GstRTSPTransportMode mode = 0; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; element = priv->element; have_elem = FALSE; for (i = 0; more_elem_remaining; i++) { gchar *name; more_elem_remaining = FALSE; 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); /* create the stream */ 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); have_elem = TRUE; more_elem_remaining = TRUE; mode |= GST_RTSP_TRANSPORT_MODE_PLAY; } g_free (name); 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); priv->dynamic = g_list_prepend (priv->dynamic, elem); g_mutex_unlock (&priv->lock); have_elem = TRUE; more_elem_remaining = TRUE; mode |= GST_RTSP_TRANSPORT_MODE_PLAY; } g_free (name); name = g_strdup_printf ("depay%d", i); if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) { GST_INFO ("found stream %d with depayloader %p", i, elem); /* take the pad of the payloader */ pad = gst_element_get_static_pad (elem, "sink"); /* create the stream */ gst_rtsp_media_create_stream (media, elem, pad); gst_object_unref (pad); gst_object_unref (elem); have_elem = TRUE; more_elem_remaining = TRUE; mode |= GST_RTSP_TRANSPORT_MODE_RECORD; } g_free (name); } if (have_elem) { if (priv->transport_mode != mode) GST_WARNING ("found different mode than expected (0x%02x != 0x%02d)", priv->transport_mode, mode); } } /** * gst_rtsp_media_create_stream: * @media: a #GstRTSPMedia * @payloader: a #GstElement * @pad: a #GstPad * * Create a new stream in @media that provides RTP data on @pad. * @pad 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. */ GstRTSPStream * gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader, GstPad * pad) { GstRTSPMediaPrivate *priv; GstRTSPStream *stream; GstPad *ghostpad; gchar *name; gint idx; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL); g_return_val_if_fail (GST_IS_PAD (pad), NULL); priv = media->priv; g_mutex_lock (&priv->lock); idx = priv->streams->len; GST_DEBUG ("media %p: creating stream with index %d", media, idx); 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); g_free (name); stream = gst_rtsp_stream_new (idx, payloader, ghostpad); 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); gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time); g_ptr_array_add (priv->streams, stream); if (GST_PAD_IS_SRC (pad)) { gint i, n; if (priv->payloads) g_list_free (priv->payloads); priv->payloads = _find_payload_types (media); n = priv->streams->len; for (i = 0; i < n; i++) { GstRTSPStream *stream = g_ptr_array_index (priv->streams, i); guint rtx_pt = _next_available_pt (priv->payloads); if (rtx_pt == 0) { GST_WARNING ("Ran out of space of dynamic payload types"); break; } gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt); priv->payloads = g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt)); } } g_mutex_unlock (&priv->lock); g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STREAM], 0, stream, NULL); return stream; } static void gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream) { GstRTSPMediaPrivate *priv; GstPad *srcpad; priv = media->priv; g_mutex_lock (&priv->lock); /* remove the ghostpad */ srcpad = gst_rtsp_stream_get_srcpad (stream); gst_element_remove_pad (priv->element, srcpad); gst_object_unref (srcpad); /* now remove the stream */ g_object_ref (stream); g_ptr_array_remove (priv->streams, stream); g_mutex_unlock (&priv->lock); g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM], 0, stream, NULL); g_object_unref (stream); } /** * gst_rtsp_media_n_streams: * @media: a #GstRTSPMedia * * Get the number of streams in this media. * * Returns: The number of streams. */ guint gst_rtsp_media_n_streams (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; guint res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0); priv = media->priv; g_mutex_lock (&priv->lock); res = priv->streams->len; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_get_stream: * @media: a #GstRTSPMedia * @idx: the stream index * * Retrieve the stream with index @idx from @media. * * 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) { GstRTSPMediaPrivate *priv; GstRTSPStream *res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); priv = media->priv; g_mutex_lock (&priv->lock); if (idx < priv->streams->len) res = g_ptr_array_index (priv->streams, idx); else res = NULL; g_mutex_unlock (&priv->lock); return res; } /** * gst_rtsp_media_find_stream: * @media: a #GstRTSPMedia * @control: the control of the stream * * Find a stream in @media with @control as the control uri. * * 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) { GstRTSPMediaPrivate *priv; GstRTSPStream *res; gint i; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); g_return_val_if_fail (control != NULL, NULL); priv = media->priv; res = NULL; g_mutex_lock (&priv->lock); for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *test; test = g_ptr_array_index (priv->streams, i); if (gst_rtsp_stream_has_control (test, control)) { res = test; break; } } g_mutex_unlock (&priv->lock); return res; } /* called with state-lock */ static gboolean default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range, GstRTSPRangeUnit unit) { return gst_rtsp_range_convert_units (range, unit); } /** * gst_rtsp_media_get_range_string: * @media: a #GstRTSPMedia * @play: for the PLAY request * @unit: the unit to use for the string * * Get the current range as a string. @media must be prepared with * gst_rtsp_media_prepare (). * * Returns: (transfer full): The range as a string, g_free() after usage. */ gchar * gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play, GstRTSPRangeUnit unit) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; gchar *result; GstRTSPTimeRange range; klass = GST_RTSP_MEDIA_GET_CLASS (media); g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); g_return_val_if_fail (klass->convert_range != NULL, FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED && priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) goto not_prepared; g_mutex_lock (&priv->lock); /* Update the range value with current position/duration */ collect_media_stats (media); /* make copy */ range = priv->range; if (!play && priv->n_active > 0) { range.min.type = GST_RTSP_TIME_NOW; range.min.seconds = -1; } g_mutex_unlock (&priv->lock); g_rec_mutex_unlock (&priv->state_lock); if (!klass->convert_range (media, &range, unit)) goto conversion_failed; result = gst_rtsp_range_to_string (&range); return result; /* ERRORS */ not_prepared: { GST_WARNING ("media %p was not prepared", media); g_rec_mutex_unlock (&priv->state_lock); return NULL; } conversion_failed: { GST_WARNING ("range conversion to unit %d failed", unit); return NULL; } } static void stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media) { gst_rtsp_stream_set_blocked (stream, media->priv->blocked); } static void media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked) { GstRTSPMediaPrivate *priv = media->priv; GST_DEBUG ("media %p set blocked %d", media, blocked); priv->blocked = blocked; g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media); } static void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) { GstRTSPMediaPrivate *priv = media->priv; g_mutex_lock (&priv->lock); priv->status = status; GST_DEBUG ("setting new status to %d", status); g_cond_broadcast (&priv->cond); g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_status: * @media: a #GstRTSPMedia * * Get the status of @media. When @media is busy preparing, this function waits * until @media is prepared or in error. * * Returns: the status of @media. */ GstRTSPMediaStatus gst_rtsp_media_get_status (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPMediaStatus result; gint64 end_time; g_mutex_lock (&priv->lock); end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND; /* while we are preparing, wait */ while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) { GST_DEBUG ("waiting for status change"); if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) { GST_DEBUG ("timeout, assuming error status"); priv->status = GST_RTSP_MEDIA_STATUS_ERROR; } } /* could be success or error */ result = priv->status; GST_DEBUG ("got status %d", result); g_mutex_unlock (&priv->lock); return result; } /** * gst_rtsp_media_seek: * @media: a #GstRTSPMedia * @range: (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) { GstRTSPMediaClass *klass; GstRTSPMediaPrivate *priv; gboolean res; GstClockTime start, stop; GstSeekType start_type, stop_type; GstQuery *query; gint64 current_position; klass = GST_RTSP_MEDIA_GET_CLASS (media); g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); g_return_val_if_fail (range != NULL, FALSE); g_return_val_if_fail (klass->convert_range != NULL, FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) goto not_prepared; /* 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); } if (!priv->seekable) goto not_seekable; start_type = stop_type = GST_SEEK_TYPE_NONE; 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)); current_position = -1; if (klass->query_position) klass->query_position (media, ¤t_position); GST_INFO ("current media position %" GST_TIME_FORMAT, GST_TIME_ARGS (current_position)); 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) 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)); /* 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! */ if (current_position == -1) { GST_WARNING ("current position unknown"); } else { GST_DEBUG ("doing accurate seek to %" GST_TIME_FORMAT, 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"); res = TRUE; } else { gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); if (priv->blocked) media_streams_set_blocked (media, TRUE); /* 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); if (!res) goto seek_failed; g_rec_mutex_unlock (&priv->state_lock); /* 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; } g_rec_mutex_unlock (&priv->state_lock); return res; /* ERRORS */ not_prepared: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("media %p is not prepared", media); return FALSE; } not_seekable: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("pipeline is not seekable"); return FALSE; } not_supported: { g_rec_mutex_unlock (&priv->state_lock); GST_WARNING ("conversion to npt not supported"); return FALSE; } seek_failed: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("seeking failed"); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); return FALSE; } preroll_failed: { GST_WARNING ("failed to preroll after seek"); return FALSE; } } static void stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked) { *blocked &= gst_rtsp_stream_is_blocking (stream); } static gboolean media_streams_blocking (GstRTSPMedia * media) { gboolean blocking = TRUE; g_ptr_array_foreach (media->priv->streams, (GFunc) stream_collect_blocking, &blocking); return blocking; } static GstStateChangeReturn set_state (GstRTSPMedia * media, GstState state) { GstRTSPMediaPrivate *priv = media->priv; GstStateChangeReturn ret; GST_INFO ("set state to %s for media %p", gst_element_state_get_name (state), media); ret = gst_element_set_state (priv->pipeline, state); return ret; } 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 */ static gboolean default_handle_message (GstRTSPMedia * media, GstMessage * message) { GstRTSPMediaPrivate *priv = media->priv; GstMessageType type; type = GST_MESSAGE_TYPE (message); switch (type) { case GST_MESSAGE_STATE_CHANGED: { GstState old, new, pending; if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->pipeline)) break; gst_message_parse_state_changed (message, &old, &new, &pending); 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) { GST_INFO ("%p: went to PAUSED, prepared now", media); 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_BUFFERING: { gint percent; gst_message_parse_buffering (message, &percent); /* no state management needed for live pipelines */ if (priv->is_live) break; if (percent == 100) { /* a 100% message means buffering is done */ priv->buffering = FALSE; /* if the desired state is playing, go back */ if (priv->target_state == GST_STATE_PLAYING) { GST_INFO ("Buffering done, setting pipeline to PLAYING"); set_state (media, GST_STATE_PLAYING); } else { GST_INFO ("Buffering done"); } } else { /* buffering busy */ if (priv->buffering == FALSE) { if (priv->target_state == GST_STATE_PLAYING) { /* we were not buffering but PLAYING, PAUSE the pipeline. */ GST_INFO ("Buffering, setting pipeline to PAUSED ..."); set_state (media, GST_STATE_PAUSED); } else { GST_INFO ("Buffering ..."); } } priv->buffering = TRUE; } break; } case GST_MESSAGE_LATENCY: { gst_bin_recalculate_latency (GST_BIN_CAST (priv->pipeline)); break; } case GST_MESSAGE_ERROR: { GError *gerror; gchar *debug; gst_message_parse_error (message, &gerror, &debug); GST_WARNING ("%p: got error %s (%s)", media, gerror->message, debug); g_error_free (gerror); g_free (debug); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); break; } case GST_MESSAGE_WARNING: { GError *gerror; gchar *debug; gst_message_parse_warning (message, &gerror, &debug); GST_WARNING ("%p: got warning %s (%s)", media, gerror->message, debug); g_error_free (gerror); g_free (debug); break; } case GST_MESSAGE_ELEMENT: { const GstStructure *s; s = gst_message_get_structure (message); if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) { GST_DEBUG ("media received blocking message"); if (priv->blocked && media_streams_blocking (media)) { GST_DEBUG ("media is blocking"); collect_media_stats (media); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); } } break; } case GST_MESSAGE_STREAM_STATUS: break; case GST_MESSAGE_ASYNC_DONE: if (priv->adding) { /* when we are dynamically adding pads, the addition of the udpsrc will * temporarily produce ASYNC_DONE messages. We have to ignore them and * wait for the final ASYNC_DONE after everything prerolled */ GST_INFO ("%p: ignoring ASYNC_DONE", media); } else { GST_INFO ("%p: got ASYNC_DONE", media); collect_media_stats (media); if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); } break; case GST_MESSAGE_EOS: GST_INFO ("%p: got EOS", media); if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARING) { GST_DEBUG ("shutting down after EOS"); finish_unprepare (media); } break; default: GST_INFO ("%p: got message type %d (%s)", media, type, gst_message_type_get_name (type)); break; } return TRUE; } static gboolean bus_message (GstBus * bus, GstMessage * message, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPMediaClass *klass; gboolean ret; klass = GST_RTSP_MEDIA_GET_CLASS (media); g_rec_mutex_lock (&priv->state_lock); if (klass->handle_message) ret = klass->handle_message (media, message); else ret = FALSE; g_rec_mutex_unlock (&priv->state_lock); return ret; } static void watch_destroyed (GstRTSPMedia * media) { GST_DEBUG_OBJECT (media, "source destroyed"); 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; /* 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. */ 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), 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 pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPStream *stream; stream = g_object_get_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream"); if (stream == NULL) return; GST_INFO ("pad removed %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream); g_rec_mutex_lock (&priv->state_lock); gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin); g_rec_mutex_unlock (&priv->state_lock); gst_rtsp_media_remove_stream (media, stream); } static void remove_fakesink (GstRTSPMediaPrivate * priv) { GstElement *fakesink; g_mutex_lock (&priv->lock); 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"); } } static void no_more_pads_cb (GstElement * element, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GST_INFO ("no more pads"); remove_fakesink (priv); } typedef struct _DynPaySignalHandlers DynPaySignalHandlers; struct _DynPaySignalHandlers { gulong pad_added_handler; gulong pad_removed_handler; gulong no_more_pads_handler; }; static gboolean 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; 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; 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 GstElement * request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPStream *stream = NULL; guint i; 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; } g_mutex_unlock (&priv->lock); return gst_rtsp_stream_request_aux_sender (stream, sessid); } static gboolean start_prepare (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; guint i; GList *walk; /* link streams we already have, other streams might appear when we have * dynamic elements */ for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *stream; 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 (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin, GST_STATE_NULL)) { goto join_bin_failed; } } if (priv->rtpbin) g_object_set (priv->rtpbin, "do-retransmission", priv->rtx_time > 0, NULL); for (walk = priv->dynamic; walk; walk = g_list_next (walk)) { GstElement *elem = walk->data; DynPaySignalHandlers *handlers = g_slice_new (DynPaySignalHandlers); GST_INFO ("adding callbacks for dynamic element %p", elem); handlers->pad_added_handler = g_signal_connect (elem, "pad-added", (GCallback) pad_added_cb, media); handlers->pad_removed_handler = g_signal_connect (elem, "pad-removed", (GCallback) pad_removed_cb, media); handlers->no_more_pads_handler = g_signal_connect (elem, "no-more-pads", (GCallback) no_more_pads_cb, media); g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers); } /* we add a fakesink here in order to make the state change async. We remove * the fakesink again in the no-more-pads callback. */ priv->fakesink = gst_element_factory_make ("fakesink", "fakesink"); gst_bin_add (GST_BIN (priv->pipeline), priv->fakesink); if (!start_preroll (media)) goto preroll_failed; return FALSE; join_bin_failed: { GST_WARNING ("failed to join bin element"); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); return FALSE; } preroll_failed: { GST_WARNING ("failed to preroll pipeline"); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); return FALSE; } } static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread) { GstRTSPMediaPrivate *priv; GstRTSPMediaClass *klass; GstBus *bus; GMainContext *context; GSource *source; priv = media->priv; klass = GST_RTSP_MEDIA_GET_CLASS (media); if (!klass->create_rtpbin) goto no_create_rtpbin; priv->rtpbin = klass->create_rtpbin (media); if (priv->rtpbin != NULL) { gboolean success = TRUE; g_object_set (priv->rtpbin, "latency", priv->latency, NULL); if (klass->setup_rtpbin) success = klass->setup_rtpbin (media, priv->rtpbin); if (success == FALSE) { gst_object_unref (priv->rtpbin); priv->rtpbin = NULL; } } if (priv->rtpbin == NULL) goto no_rtpbin; priv->thread = thread; context = (thread != NULL) ? (thread->context) : NULL; bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline)); /* add the pipeline bus to our custom mainloop */ priv->source = gst_bus_create_watch (bus); gst_object_unref (bus); g_source_set_callback (priv->source, (GSourceFunc) bus_message, g_object_ref (media), (GDestroyNotify) watch_destroyed); priv->id = g_source_attach (priv->source, context); /* add stuff to the bin */ gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin); /* do remainder in context */ source = g_idle_source_new (); g_source_set_callback (source, (GSourceFunc) start_prepare, media, NULL); g_source_attach (source, context); g_source_unref (source); return TRUE; /* ERRORS */ no_create_rtpbin: { GST_ERROR ("no create_rtpbin function"); g_critical ("no create_rtpbin vmethod function set"); return FALSE; } no_rtpbin: { GST_WARNING ("no rtpbin element"); g_warning ("failed to create element 'rtpbin', check your installation"); return FALSE; } } /** * gst_rtsp_media_prepare: * @media: a #GstRTSPMedia * @thread: (transfer full) (allow-none): a #GstRTSPThread to run the * bus handler or %NULL * * Prepare @media for streaming. This function will create the objects * to manage the streaming. A pipeline must have been set on @media with * gst_rtsp_media_take_pipeline(). * * It will preroll the pipeline and collect vital information about the streams * such as the duration. * * Returns: %TRUE on success. */ gboolean gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread) { GstRTSPMediaPrivate *priv; GstRTSPMediaClass *klass; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); priv->prepare_count++; if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED || priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED) goto was_prepared; if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) goto is_preparing; if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED) goto not_unprepared; if (!priv->reusable && priv->reused) goto is_reused; GST_INFO ("preparing media %p", media); /* reset some variables */ priv->is_live = FALSE; priv->seekable = FALSE; priv->buffering = FALSE; /* we're preparing now */ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); klass = GST_RTSP_MEDIA_GET_CLASS (media); if (klass->prepare) { if (!klass->prepare (media, thread)) goto prepare_failed; } wait_status: g_rec_mutex_unlock (&priv->state_lock); /* now wait for all pads to be prerolled, FIXME, we should somehow be * able to do this async so that we don't block the server thread. */ if (!wait_preroll (media)) goto preroll_failed; g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARED], 0, NULL); GST_INFO ("object %p is prerolled", media); 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); return FALSE; } 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; } 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_ERROR ("failed to prepare media"); return FALSE; } preroll_failed: { GST_WARNING ("failed to preroll pipeline"); gst_rtsp_media_unprepare (media); return FALSE; } } /* must be called with state-lock */ static void finish_unprepare (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; gint i; GList *walk; GST_DEBUG ("shutting down"); /* release the lock on shutdown, otherwise pad_added_cb might try to * acquire the lock and then we deadlock */ g_rec_mutex_unlock (&priv->state_lock); set_state (media, GST_STATE_NULL); g_rec_mutex_lock (&priv->state_lock); remove_fakesink (priv); for (i = 0; i < priv->streams->len; i++) { GstRTSPStream *stream; GST_INFO ("Removing elements of stream %d from pipeline", i); stream = g_ptr_array_index (priv->streams, i); gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin); } /* remove the pad signal handlers */ for (walk = priv->dynamic; walk; walk = g_list_next (walk)) { GstElement *elem = walk->data; DynPaySignalHandlers *handlers; handlers = g_object_steal_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers"); g_assert (handlers != NULL); g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_added_handler); g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_removed_handler); g_signal_handler_disconnect (G_OBJECT (elem), handlers->no_more_pads_handler); g_slice_free (DynPaySignalHandlers, handlers); } gst_bin_remove (GST_BIN (priv->pipeline), priv->rtpbin); priv->rtpbin = NULL; if (priv->nettime) gst_object_unref (priv->nettime); priv->nettime = NULL; priv->reused = TRUE; 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 */ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_UNPREPARED], 0, NULL); /* the source has the last ref to the media */ if (priv->source) { GST_DEBUG ("destroy source"); g_source_destroy (priv->source); g_source_unref (priv->source); } if (priv->thread) { GST_DEBUG ("stop thread"); gst_rtsp_thread_stop (priv->thread); } } /* called with state-lock */ static gboolean default_unprepare (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING); if (priv->eos_shutdown) { GST_DEBUG ("sending EOS for shutdown"); /* ref so that we don't disappear */ 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. */ set_state (media, GST_STATE_PLAYING); } else { finish_unprepare (media); } return TRUE; } /** * gst_rtsp_media_unprepare: * @media: a #GstRTSPMedia * * Unprepare @media. After this call, the media should be prepared again before * it can be used again. If the media is set to be non-reusable, a new instance * must be created. * * Returns: %TRUE on success. */ gboolean gst_rtsp_media_unprepare (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; gboolean success; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARED) goto was_unprepared; priv->prepare_count--; if (priv->prepare_count > 0) 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; if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) { GstRTSPMediaClass *klass; klass = GST_RTSP_MEDIA_GET_CLASS (media); if (klass->unprepare) success = klass->unprepare (media); } else { finish_unprepare (media); } g_rec_mutex_unlock (&priv->state_lock); return success; was_unprepared: { g_rec_mutex_unlock (&priv->state_lock); GST_INFO ("media %p was already unprepared", media); return TRUE; } is_busy: { GST_INFO ("media %p still prepared %d times", media, priv->prepare_count); g_rec_mutex_unlock (&priv->state_lock); return TRUE; } } /* should be called with state-lock */ static GstClock * get_clock_unlocked (GstRTSPMedia * media) { if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) { GST_DEBUG_OBJECT (media, "media was not prepared"); return NULL; } return gst_pipeline_get_clock (GST_PIPELINE_CAST (media->priv->pipeline)); } /** * gst_rtsp_media_get_clock: * @media: a #GstRTSPMedia * * Get the clock that is used by the pipeline in @media. * * @media must be prepared before this method returns a valid clock object. * * Returns: (transfer full): the #GstClock used by @media. unref after usage. */ GstClock * gst_rtsp_media_get_clock (GstRTSPMedia * media) { GstClock *clock; GstRTSPMediaPrivate *priv; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); clock = get_clock_unlocked (media); g_rec_mutex_unlock (&priv->state_lock); return clock; } /** * gst_rtsp_media_get_base_time: * @media: a #GstRTSPMedia * * Get the base_time that is used by the pipeline in @media. * * @media must be prepared before this method returns a valid base_time. * * Returns: the base_time used by @media. */ GstClockTime gst_rtsp_media_get_base_time (GstRTSPMedia * media) { GstClockTime result; GstRTSPMediaPrivate *priv; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_CLOCK_TIME_NONE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) goto not_prepared; result = gst_element_get_base_time (media->priv->pipeline); g_rec_mutex_unlock (&priv->state_lock); return result; /* ERRORS */ not_prepared: { g_rec_mutex_unlock (&priv->state_lock); GST_DEBUG_OBJECT (media, "media was not prepared"); return GST_CLOCK_TIME_NONE; } } /** * gst_rtsp_media_get_time_provider: * @media: a #GstRTSPMedia * @address: (allow-none): an address or %NULL * @port: a port or 0 * * Get the #GstNetTimeProvider for the clock used by @media. The time provider * will listen on @address and @port for client time requests. * * Returns: (transfer full): the #GstNetTimeProvider of @media. */ GstNetTimeProvider * gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address, guint16 port) { GstRTSPMediaPrivate *priv; GstNetTimeProvider *provider = NULL; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); if (priv->time_provider) { if ((provider = priv->nettime) == NULL) { GstClock *clock; if (priv->time_provider && (clock = get_clock_unlocked (media))) { provider = gst_net_time_provider_new (clock, address, port); gst_object_unref (clock); priv->nettime = provider; } } } g_rec_mutex_unlock (&priv->state_lock); if (provider) gst_object_ref (provider); return provider; } static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info) { return gst_rtsp_sdp_from_media (sdp, info, media); } /** * gst_rtsp_media_setup_sdp: * @media: a #GstRTSPMedia * @sdp: (transfer none): a #GstSDPMessage * @info: (transfer none): a #GstSDPInfo * * Add @media specific info to @sdp. @info is used to configure the connection * information in the SDP. * * Returns: TRUE on success. */ gboolean gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info) { GstRTSPMediaPrivate *priv; GstRTSPMediaClass *klass; gboolean res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); g_return_val_if_fail (sdp != NULL, FALSE); g_return_val_if_fail (info != NULL, FALSE); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); klass = GST_RTSP_MEDIA_GET_CLASS (media); if (!klass->setup_sdp) goto no_setup_sdp; res = klass->setup_sdp (media, sdp, info); g_rec_mutex_unlock (&priv->state_lock); return res; /* ERRORS */ no_setup_sdp: { g_rec_mutex_unlock (&priv->state_lock); GST_ERROR ("no setup_sdp function"); g_critical ("no setup_sdp vmethod function set"); return FALSE; } } static 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; gint j; const gchar *reserved_keys[] = { "media", "payload", "clock-rate", "encoding-name", "encoding-params" }; /* 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]); /* skip keys from the fmtp, which we already use ourselves for the * caps. Some software is adding random things like clock-rate into * the fmtp, and we would otherwise here set a string-typed clock-rate * in the caps... and thus fail to create valid RTP caps */ for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) { if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) { key = ""; break; } } 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) { 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; SKIP_SPACES (p); if (*p == '\0') return FALSE; PARSE_STRING (p, " ", kmpid); if (!g_str_equal (kmpid, "mikey")) return FALSE; data = g_base64_decode (p, &size); if (data == NULL) return FALSE; msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); g_free (data); if (msg == NULL) return FALSE; 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; } } } 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); res = TRUE; done: gst_mikey_message_unref (msg); return res; } /* * Mapping SDP attributes to caps * * prepend 'a-' to IANA registered sdp attributes names * (ie: not prefixed with 'x-') in order to avoid * collision with gstreamer standard caps properties names */ static void sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) { if (attributes->len > 0) { GstStructure *s; guint i; s = gst_caps_get_structure (caps, 0); for (i = 0; i < attributes->len; i++) { GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); gchar *tofree, *key; key = attr->key; /* 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; } /* string must be valid UTF8 */ if (!g_utf8_validate (attr->value, -1, NULL)) continue; if (!g_str_has_prefix (key, "x-")) tofree = key = g_strdup_printf ("a-%s", key); else tofree = NULL; GST_DEBUG ("adding caps: %s=%s", key, attr->value); gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); g_free (tofree); } } } static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) { GstRTSPMediaPrivate *priv = media->priv; gint i, medias_len; medias_len = gst_sdp_message_medias_len (sdp); if (medias_len != priv->streams->len) { GST_ERROR ("%p: Media has more or less streams than SDP (%d /= %d)", media, priv->streams->len, medias_len); return FALSE; } for (i = 0; i < medias_len; i++) { const gchar *proto, *media_type; const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i); GstRTSPStream *stream; gint j, formats_len; const gchar *control; GstRTSPProfile profile, profiles; stream = g_ptr_array_index (priv->streams, i); /* TODO: Should we do something with the other SDP information? */ /* get proto */ proto = gst_sdp_media_get_proto (sdp_media); if (proto == NULL) { GST_ERROR ("%p: SDP media %d has no proto", media, i); return FALSE; } 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); return FALSE; } profiles = gst_rtsp_stream_get_profiles (stream); if ((profiles & profile) == 0) { GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i); return FALSE; } formats_len = gst_sdp_media_formats_len (sdp_media); for (j = 0; j < formats_len; j++) { gint pt; GstCaps *caps; GstStructure *s; pt = atoi (gst_sdp_media_get_format (sdp_media, j)); GST_DEBUG (" looking at %d pt: %d", j, pt); /* convert caps */ caps = media_to_caps (pt, sdp_media); if (caps == NULL) { GST_WARNING (" skipping pt %d without caps", pt); continue; } /* do some tweaks */ GST_DEBUG ("mapping sdp session level attributes to caps"); sdp_attributes_to_caps (sdp->attributes, caps); GST_DEBUG ("mapping sdp media level attributes to caps"); sdp_attributes_to_caps (sdp_media->attributes, caps); s = gst_caps_get_structure (caps, 0); gst_structure_set_name (s, media_type); gst_rtsp_stream_set_pt_map (stream, pt, caps); gst_caps_unref (caps); } control = gst_sdp_media_get_attribute_val (sdp_media, "control"); if (control) gst_rtsp_stream_set_control (stream, control); } return TRUE; } /** * gst_rtsp_media_handle_sdp: * @media: a #GstRTSPMedia * @sdp: (transfer none): a #GstSDPMessage * * Configure an SDP on @media for receiving streams * * Returns: TRUE on success. */ gboolean gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp) { 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); priv = media->priv; g_rec_mutex_lock (&priv->state_lock); klass = GST_RTSP_MEDIA_GET_CLASS (media); if (!klass->handle_sdp) goto no_handle_sdp; res = klass->handle_sdp (media, sdp); g_rec_mutex_unlock (&priv->state_lock); return res; /* ERRORS */ no_handle_sdp: { g_rec_mutex_unlock (&priv->state_lock); GST_ERROR ("no handle_sdp function"); g_critical ("no handle_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 */ static gboolean default_suspend (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstStateChangeReturn ret; gboolean unblock = FALSE; 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; unblock = TRUE; 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); unblock = TRUE; break; default: break; } /* let the streams do the state changes freely, if any */ if (unblock) 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 */ static gboolean default_unsuspend (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; switch (priv->suspend_mode) { case GST_RTSP_SUSPEND_MODE_NONE: gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); break; case GST_RTSP_SUSPEND_MODE_PAUSE: gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED); break; case GST_RTSP_SUSPEND_MODE_RESET: { gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); if (!start_preroll (media)) goto start_failed; g_rec_mutex_unlock (&priv->state_lock); if (!wait_preroll (media)) goto preroll_failed; g_rec_mutex_lock (&priv->state_lock); } default: break; } return TRUE; /* ERRORS */ start_failed: { GST_WARNING ("failed to preroll pipeline"); return FALSE; } preroll_failed: { GST_WARNING ("failed to preroll pipeline"); return FALSE; } } /** * gst_rtsp_media_unsuspend: * @media: a #GstRTSPMedia * * Unsuspend @media if it was in a suspended state. This method does nothing * when the media was not in the suspended state. * * Returns: %TRUE on success. */ gboolean gst_rtsp_media_unsuspend (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv = media->priv; GstRTSPMediaClass *klass; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); g_rec_mutex_lock (&priv->state_lock); if (priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) goto done; klass = GST_RTSP_MEDIA_GET_CLASS (media); if (klass->unsuspend) { if (!klass->unsuspend (media)) goto unsuspend_failed; } done: g_rec_mutex_unlock (&priv->state_lock); return TRUE; /* ERRORS */ unsuspend_failed: { g_rec_mutex_unlock (&priv->state_lock); GST_WARNING ("failed to unsuspend media %p", media); gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR); return FALSE; } } /* must be called with state-lock */ static void media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) { GstRTSPMediaPrivate *priv = media->priv; if (state == GST_STATE_NULL) { gst_rtsp_media_unprepare (media); } else { GST_INFO ("state %s media %p", gst_element_state_get_name (state), media); set_target_state (media, state, FALSE); /* when we are buffering, don't update the state yet, this will be done * when buffering finishes */ if (priv->buffering) { GST_INFO ("Buffering busy, delay state change"); } else { if (state == GST_STATE_PLAYING) /* make sure pads are not blocking anymore when going to PLAYING */ media_streams_set_blocked (media, FALSE); set_state (media, state); /* and suspend after pause */ if (state == GST_STATE_PAUSED) gst_rtsp_media_suspend (media); } } } /** * gst_rtsp_media_set_pipeline_state: * @media: a #GstRTSPMedia * @state: the target state of the pipeline * * Set the state of the pipeline managed by @media to @state */ void gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state) { g_return_if_fail (GST_IS_RTSP_MEDIA (media)); g_rec_mutex_lock (&media->priv->state_lock); media_set_pipeline_state_locked (media, state); g_rec_mutex_unlock (&media->priv->state_lock); } /** * gst_rtsp_media_set_state: * @media: a #GstRTSPMedia * @state: the target state of the media * @transports: (transfer none) (element-type GstRtspServer.RTSPStreamTransport): * a #GPtrArray of #GstRTSPStreamTransport pointers * * Set the state of @media to @state and for the transports in @transports. * * @media must be prepared with gst_rtsp_media_prepare(); * * Returns: %TRUE on success. */ gboolean gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state, GPtrArray * transports) { GstRTSPMediaPrivate *priv; gint i; gboolean activate, deactivate, do_state; gint old_active; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); g_return_val_if_fail (transports != NULL, FALSE); priv = media->priv; 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 && priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED) goto not_prepared; /* NULL and READY are the same */ if (state == GST_STATE_READY) state = GST_STATE_NULL; activate = deactivate = FALSE; GST_INFO ("going to state %s media %p, target state %s", gst_element_state_get_name (state), media, gst_element_state_get_name (priv->target_state)); switch (state) { case GST_STATE_NULL: /* we're going from PLAYING or PAUSED to READY or NULL, deactivate */ if (priv->target_state >= GST_STATE_PAUSED) deactivate = TRUE; break; case GST_STATE_PAUSED: /* we're going from PLAYING to PAUSED, READY or NULL, deactivate */ if (priv->target_state == GST_STATE_PLAYING) deactivate = TRUE; break; case GST_STATE_PLAYING: /* we're going to PLAYING, activate */ activate = TRUE; break; default: break; } old_active = priv->n_active; GST_DEBUG ("%d transports, activate %d, deactivate %d", transports->len, activate, deactivate); 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; if (activate) { if (gst_rtsp_stream_transport_set_active (trans, TRUE)) priv->n_active++; } else if (deactivate) { if (gst_rtsp_stream_transport_set_active (trans, FALSE)) priv->n_active--; } } /* 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) do_state = TRUE; else do_state = FALSE; GST_INFO ("state %d active %d media %p do_state %d", state, priv->n_active, media, do_state); if (priv->target_state != 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); } /* remember where we are */ if (state != GST_STATE_NULL && (state == GST_STATE_PAUSED || old_active != priv->n_active)) collect_media_stats (media); g_rec_mutex_unlock (&priv->state_lock); return TRUE; /* ERRORS */ not_prepared: { GST_WARNING ("media %p was not prepared", media); g_rec_mutex_unlock (&priv->state_lock); return FALSE; } error_status: { GST_WARNING ("media %p in error status while changing to state %d", media, state); if (state == GST_STATE_NULL) { for (i = 0; i < transports->len; i++) { GstRTSPStreamTransport *trans; /* we need a non-NULL entry in the array */ trans = g_ptr_array_index (transports, i); if (trans == NULL) continue; gst_rtsp_stream_transport_set_active (trans, FALSE); } priv->n_active = 0; } g_rec_mutex_unlock (&priv->state_lock); return FALSE; } } /** * gst_rtsp_media_set_transport_mode: * @media: a #GstRTSPMedia * @mode: the new value * * Sets if the media pipeline can work in PLAY or RECORD mode */ void gst_rtsp_media_set_transport_mode (GstRTSPMedia * media, GstRTSPTransportMode mode) { GstRTSPMediaPrivate *priv; g_return_if_fail (GST_IS_RTSP_MEDIA (media)); priv = media->priv; g_mutex_lock (&priv->lock); priv->transport_mode = mode; g_mutex_unlock (&priv->lock); } /** * gst_rtsp_media_get_transport_mode: * @media: a #GstRTSPMedia * * Check if the pipeline for @media can be used for PLAY or RECORD methods. * * Returns: The transport mode. */ GstRTSPTransportMode gst_rtsp_media_get_transport_mode (GstRTSPMedia * media) { GstRTSPMediaPrivate *priv; GstRTSPTransportMode res; g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE); priv = media->priv; g_mutex_lock (&priv->lock); res = priv->transport_mode; g_mutex_unlock (&priv->lock); return res; }