diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index bf032dcf92..3084087663 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -296,6 +296,12 @@ static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, GstEvent * event); static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event); +typedef struct +{ + guint8 pt; + GstCaps *caps; +} PtMapItem; + /* commands we send to out loop to notify it of events */ #define CMD_OPEN (1 << 0) #define CMD_PLAY (1 << 1) @@ -1147,15 +1153,6 @@ find_stream_by_channel (GstRTSPStream * stream, gint * channel) return -1; } -static gint -find_stream_by_pt (GstRTSPStream * stream, gint * pt) -{ - if (stream->pt == *pt) - return 0; - - return -1; -} - static gint find_stream_by_udpsrc (GstRTSPStream * stream, gconstpointer a) { @@ -1299,28 +1296,72 @@ gst_rtspsrc_collect_connections (GstRTSPSrc * src, const GstSDPMessage * sdp, } } + +/* m= RTP/AVP + */ static void gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, const GstSDPMedia * media, GstRTSPStream * stream) { - const gchar *payload; + guint i, len; + const gchar *proto; + + /* get proto */ + proto = gst_sdp_media_get_proto (media); + if (proto == NULL) + goto no_proto; + + if (g_str_equal (proto, "RTP/AVP")) + stream->profile = GST_RTSP_PROFILE_AVP; + else if (g_str_equal (proto, "RTP/SAVP")) + stream->profile = GST_RTSP_PROFILE_SAVP; + else if (g_str_equal (proto, "RTP/AVPF")) + stream->profile = GST_RTSP_PROFILE_AVPF; + else if (g_str_equal (proto, "RTP/SAVPF")) + stream->profile = GST_RTSP_PROFILE_SAVPF; + else + goto unknown_proto; + + len = gst_sdp_media_formats_len (media); + for (i = 0; i < len; i++) { + gint pt; + GstCaps *caps; + GstStructure *s; + const gchar *enc; + PtMapItem item; + + pt = atoi (gst_sdp_media_get_format (media, i)); + + GST_DEBUG_OBJECT (src, " looking at %d pt: %d", i, pt); - /* we must have a payload. No payload means we cannot create caps */ - /* FIXME, handle multiple formats. The problem here is that we just want to - * take the first available format that we can handle but in order to do that - * we need to scan for depayloader plugins. Scanning for payloader plugins is - * also suboptimal because the user maybe just wants to save the raw stream - * and then we don't care. */ - if ((payload = gst_sdp_media_get_format (media, 0))) { - stream->pt = atoi (payload); /* convert caps */ - stream->caps = gst_rtspsrc_media_to_caps (stream->pt, media); + caps = gst_rtspsrc_media_to_caps (pt, media); + if (caps == NULL) { + GST_WARNING_OBJECT (src, " skipping pt %d without caps", pt); + continue; + } + /* do some tweaks */ + s = gst_caps_get_structure (caps, 0); + if ((enc = gst_structure_get_string (s, "encoding-name"))) { + stream->is_real = (strstr (enc, "-REAL") != NULL); + if (strcmp (enc, "X-ASF-PF") == 0) + stream->container = TRUE; + } GST_DEBUG ("mapping sdp session level attributes to caps"); - gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, stream->caps); + gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, caps); GST_DEBUG ("mapping sdp media level attributes to caps"); - gst_rtspsrc_sdp_attributes_to_caps (media->attributes, stream->caps); + gst_rtspsrc_sdp_attributes_to_caps (media->attributes, caps); + /* the first pt will be the default */ + if (stream->ptmap->len == 0) + stream->default_pt = pt; + + item.pt = pt; + item.caps = caps; + g_array_append_val (stream->ptmap, item); + +#if 0 if (stream->pt >= 96) { /* If we have a dynamic payload type, see if we have a stream with the * same payload number. If there is one, they are part of the same @@ -1331,6 +1372,19 @@ gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, stream->pt); } } +#endif + } + return; + +no_proto: + { + GST_ERROR_OBJECT (src, "can't find proto in media"); + return; + } +unknown_proto: + { + GST_ERROR_OBJECT (src, "unknown proto in media %s", proto); + return; } } @@ -1375,6 +1429,8 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) stream->discont = TRUE; stream->seqbase = -1; stream->timebase = -1; + stream->profile = GST_RTSP_PROFILE_AVP; + stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem)); /* collect bandwidth information for this steam. FIXME, configure in the RTP * session manager to scale RTCP. */ @@ -1383,6 +1439,7 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) /* collect connection info */ gst_rtspsrc_collect_connections (src, sdp, media, stream); + /* make the payload type map */ gst_rtspsrc_collect_payloads (src, sdp, media, stream); /* collect port number */ @@ -1396,10 +1453,8 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) control_url = gst_sdp_message_get_attribute_val_n (sdp, "control", 0); GST_DEBUG_OBJECT (src, "stream %d, (%p)", stream->id, stream); - GST_DEBUG_OBJECT (src, " pt: %d", stream->pt); GST_DEBUG_OBJECT (src, " port: %d", stream->port); GST_DEBUG_OBJECT (src, " container: %d", stream->container); - GST_DEBUG_OBJECT (src, " caps: %" GST_PTR_FORMAT, stream->caps); GST_DEBUG_OBJECT (src, " control: %s", GST_STR_NULL (control_url)); if (control_url != NULL) { @@ -1447,8 +1502,7 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) GST_DEBUG_OBJECT (src, "free stream %p", stream); - if (stream->caps) - gst_caps_unref (stream->caps); + g_array_free (stream->ptmap, TRUE); g_free (stream->destination); g_free (stream->control_url); @@ -1678,10 +1732,31 @@ gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) } } +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; +} + /* * Mapping of caps to and from SDP fields: * - * m= RTP/AVP * a=rtpmap: /[/] * a=fmtp: [=];... */ @@ -1700,29 +1775,17 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) gboolean ret; /* get and parse rtpmap */ - if ((rtpmap = gst_sdp_media_get_attribute_val (media, "rtpmap"))) { - ret = gst_rtspsrc_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); - if (ret) { - if (payload != pt) { - /* we ignore the rtpmap if the payload type is different. */ - g_warning ("rtpmap of wrong payload type, ignoring"); - name = NULL; - rate = -1; - params = NULL; - } - } else { - /* if we failed to parse the rtpmap for a dynamic payload type, we have an - * error */ - if (pt >= 96) - goto no_rtpmap; - /* else we can ignore */ - g_warning ("error parsing rtpmap, ignoring"); - } - } else { - /* dynamic payloads need rtpmap or we fail */ - if (pt >= 96) - goto no_rtpmap; + rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt); + + /* dynamic payloads need rtpmap or we fail */ + if (rtpmap == NULL && pt >= 96) + goto no_rtpmap; + + ret = gst_rtspsrc_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); + if (!ret) { + g_warning ("error parsing rtpmap, ignoring"); } + /* 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) { @@ -1770,7 +1833,7 @@ gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) } /* parse optional fmtp: field */ - if ((fmtp = gst_sdp_media_get_attribute_val (media, "fmtp"))) { + if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) { gchar *p; gint payload = 0; @@ -2599,6 +2662,20 @@ unknown_stream: } } +static GstCaps * +stream_get_caps_for_pt (GstRTSPStream * stream, guint pt) +{ + guint i, len; + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + if (item->pt == pt) + return item->caps; + } + return NULL; +} + static GstCaps * request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) { @@ -2612,8 +2689,7 @@ request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) if (!stream) goto unknown_stream; - caps = stream->caps; - if (caps) + if ((caps = stream_get_caps_for_pt (stream, pt))) gst_caps_ref (caps); GST_RTSP_STATE_UNLOCK (src); @@ -2767,9 +2843,6 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, /* configure the manager */ if (src->manager == NULL) { GObjectClass *klass; - GstStructure *s; - const gchar *encoding; - gboolean need_slave; if (!(src->manager = gst_element_factory_make (manager, "manager"))) { /* fallback */ @@ -2818,15 +2891,11 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, * with such input times, e.g. container ones, most notably ASF */ /* TODO alternatives are having an event that indicates these shifts, * or having rtsp extensions provide suggestion on buffer mode */ - need_slave = stream->container; - if (stream->caps && (s = gst_caps_get_structure (stream->caps, 0)) && - (encoding = gst_structure_get_string (s, "encoding-name"))) - need_slave = need_slave || (strcmp (encoding, "X-ASF-PF") == 0); /* valid duration implies not likely live pipeline, * so slaving in jitterbuffer does not make much sense * (and might mess things up due to bursts) */ if (GST_CLOCK_TIME_IS_VALID (src->segment.duration) && - src->segment.duration && !need_slave) { + src->segment.duration && !stream->container) { src->use_buffering = TRUE; } else { src->use_buffering = FALSE; @@ -3423,15 +3492,13 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, GstPad *outpad = NULL; GstPadTemplate *template; gchar *name; - GstStructure *s; const gchar *media_type; + guint i, len; src = stream->parent; GST_DEBUG_OBJECT (src, "configuring transport for stream %p", stream); - s = gst_caps_get_structure (stream->caps, 0); - /* get the proper media type for this stream now */ if (gst_rtsp_transport_get_media_type (transport, &media_type) < 0) goto unknown_transport; @@ -3440,7 +3507,18 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, /* configure the final media type */ GST_DEBUG_OBJECT (src, "setting media type to %s", media_type); - gst_structure_set_name (s, media_type); + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + GstStructure *s; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); + + if (item->caps == NULL) + continue; + + s = gst_caps_get_structure (item->caps, 0); + gst_structure_set_name (s, media_type); + } /* try to get and configure a manager, channelpad[0-1] will be configured with * the pads for the manager, or NULL when no manager is needed. */ @@ -3561,8 +3639,11 @@ gst_rtspsrc_activate_streams (GstRTSPSrc * src) /* if we don't have a session manager, set the caps now. If we have a * session, we will get a notification of the pad and the caps. */ if (!src->manager) { + GstCaps *caps; + + caps = stream_get_caps_for_pt (stream, stream->default_pt); GST_DEBUG_OBJECT (src, "setting pad caps for stream %p", stream); - gst_pad_set_caps (stream->srcpad, stream->caps); + gst_pad_set_caps (stream->srcpad, caps); } /* add the pad */ if (!stream->added) { @@ -3604,10 +3685,17 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; - GstCaps *caps; + guint j, len; - if ((caps = stream->caps)) { - caps = gst_caps_make_writable (caps); + len = stream->ptmap->len; + for (j = 0; j < len; j++) { + GstCaps *caps; + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, j); + + if (item->caps == NULL) + continue; + + caps = gst_caps_make_writable (item->caps); /* update caps */ if (stream->timebase != -1) gst_caps_set_simple (caps, "clock-base", G_TYPE_UINT, @@ -3621,9 +3709,10 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL); gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL); - stream->caps = caps; + item->caps = caps; + GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, + item->pt, caps); } - GST_DEBUG_OBJECT (src, "stream %p, caps %" GST_PTR_FORMAT, stream, caps); } if (reset_manager && src->manager) { GST_DEBUG_OBJECT (src, "clear session"); @@ -5330,7 +5419,7 @@ static guint protocol_masks[] = { static GstRTSPResult gst_rtspsrc_create_transports_string (GstRTSPSrc * src, - GstRTSPLowerTrans protocols, gchar ** transports) + GstRTSPLowerTrans protocols, GstRTSPProfile profile, gchar ** transports) { GstRTSPResult res; GString *result; @@ -5354,23 +5443,35 @@ gst_rtspsrc_create_transports_string (GstRTSPSrc * src, add_udp_str = FALSE; /* the default RTSP transports */ - result = g_string_new (""); + result = g_string_new ("RTP"); + + switch (profile) { + case GST_RTSP_PROFILE_AVP: + g_string_append (result, "/AVP"); + break; + case GST_RTSP_PROFILE_SAVP: + g_string_append (result, "/SAVP"); + break; + case GST_RTSP_PROFILE_AVPF: + g_string_append (result, "/AVPF"); + break; + case GST_RTSP_PROFILE_SAVPF: + g_string_append (result, "/SAVPF"); + break; + default: + break; + } + if (protocols & GST_RTSP_LOWER_TRANS_UDP) { GST_DEBUG_OBJECT (src, "adding UDP unicast"); - - g_string_append (result, "RTP/AVP"); if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";unicast;client_port=%%u1-%%u2"); } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) { GST_DEBUG_OBJECT (src, "adding UDP multicast"); - /* we don't have to allocate any UDP ports yet, if the selected transport * turns out to be multicast we can create them and join the multicast * group indicated in the transport reply */ - if (result->len > 0) - g_string_append (result, ","); - g_string_append (result, "RTP/AVP"); if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";multicast"); @@ -5385,9 +5486,7 @@ gst_rtspsrc_create_transports_string (GstRTSPSrc * src, } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) { GST_DEBUG_OBJECT (src, "adding TCP"); - if (result->len > 0) - g_string_append (result, ","); - g_string_append (result, "RTP/AVP/TCP;unicast;interleaved=%%i1-%%i2"); + g_string_append (result, "/TCP;unicast;interleaved=%%i1-%%i2"); } *transports = g_string_free (result, FALSE); @@ -5484,23 +5583,6 @@ failed: } } -static gboolean -gst_rtspsrc_stream_is_real_media (GstRTSPStream * stream) -{ - gboolean res = FALSE; - - if (stream->caps) { - GstStructure *s; - const gchar *enc = NULL; - - s = gst_caps_get_structure (stream->caps, 0); - if ((enc = gst_structure_get_string (s, "encoding-name"))) { - res = (strstr (enc, "-REAL") != NULL); - } - } - return res; -} - /* Perform the SETUP request for all the streams. * * We ask the server for a specific transport, which initially includes all the @@ -5558,11 +5640,17 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) gint retry = 0; guint mask = 0; gboolean selected; + GstCaps *caps; stream = (GstRTSPStream *) walk->data; + caps = stream_get_caps_for_pt (stream, stream->default_pt); + if (caps == NULL) { + GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); + continue; + } /* see if we need to configure this stream */ - if (!gst_rtsp_ext_list_configure_stream (src->extensions, stream->caps)) { + if (!gst_rtsp_ext_list_configure_stream (src->extensions, caps)) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by extension", stream); stream->disabled = TRUE; @@ -5570,7 +5658,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) } g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_SELECT_STREAM], 0, - stream->id, stream->caps, &selected); + stream->id, caps, &selected); if (!selected) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by signal", stream); stream->disabled = TRUE; @@ -5579,11 +5667,11 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) stream->disabled = FALSE; /* merge/overwrite global caps */ - if (stream->caps) { + if (caps) { guint j, num; GstStructure *s; - s = gst_caps_get_structure (stream->caps, 0); + s = gst_caps_get_structure (caps, 0); num = gst_structure_n_fields (src->props); for (j = 0; j < num; j++) { @@ -5633,7 +5721,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* create a string with first transport in line */ transports = NULL; res = gst_rtspsrc_create_transports_string (src, - protocols & protocol_masks[mask], &transports); + protocols & protocol_masks[mask], stream->profile, &transports); if (res < 0 || transports == NULL) goto setup_transport_failed; @@ -5705,7 +5793,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) * but not without checking for lost cause/extension so we can * post a nicer/more useful error message later */ if (!unsupported_real) - unsupported_real = gst_rtspsrc_stream_is_real_media (stream); + unsupported_real = stream->is_real; /* select next available protocol, give up on this stream if none */ mask++; while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) @@ -6619,13 +6707,21 @@ gen_range_header (GstRTSPSrc * src, GstSegment * segment) static void clear_rtp_base (GstRTSPSrc * src, GstRTSPStream * stream) { + guint i, len; + stream->timebase = -1; stream->seqbase = -1; - if (stream->caps) { + + len = stream->ptmap->len; + for (i = 0; i < len; i++) { + PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); GstStructure *s; - stream->caps = gst_caps_make_writable (stream->caps); - s = gst_caps_get_structure (stream->caps, 0); + if (item->caps == NULL) + continue; + + item->caps = gst_caps_make_writable (item->caps); + s = gst_caps_get_structure (item->caps, 0); gst_structure_remove_fields (s, "clock-base", "seqnum-base", NULL); } } diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h index 004d4c2296..1751b30d8c 100644 --- a/gst/rtsp/gstrtspsrc.h +++ b/gst/rtsp/gstrtspsrc.h @@ -108,7 +108,6 @@ struct _GstRTSPStream { /* for interleaved mode */ guint8 channel[2]; - GstCaps *caps; GstPad *channelpad[2]; /* our udp sources */ @@ -125,9 +124,12 @@ struct _GstRTSPStream { GstElement *fakesrc; /* state */ - gint pt; guint port; gboolean container; + gboolean is_real; + guint8 default_pt; + GstRTSPProfile profile; + GArray *ptmap; /* original control url */ gchar *control_url; guint32 ssrc; @@ -149,6 +151,7 @@ struct _GstRTSPStream { gchar *destination; gboolean is_multicast; guint ttl; + }; /**