rtprtxreceive: Wait until timeout to clear association requests

If two streams request a retranmission for the same SSRC, ignore the second
one if the first oen is less than one second old, otherwise time out the first
one and ignore the second.
This commit is contained in:
Olivier Crête 2014-05-04 22:32:54 -04:00
parent 0742a5a257
commit b2a52035bf
2 changed files with 74 additions and 28 deletions

View File

@ -122,6 +122,8 @@
#include "gstrtprtxreceive.h" #include "gstrtprtxreceive.h"
#define ASSOC_TIMEOUT (GST_SECOND)
GST_DEBUG_CATEGORY_STATIC (gst_rtp_rtx_receive_debug); GST_DEBUG_CATEGORY_STATIC (gst_rtp_rtx_receive_debug);
#define GST_CAT_DEFAULT gst_rtp_rtx_receive_debug #define GST_CAT_DEFAULT gst_rtp_rtx_receive_debug
@ -237,6 +239,29 @@ gst_rtp_rtx_receive_finalize (GObject * object)
G_OBJECT_CLASS (gst_rtp_rtx_receive_parent_class)->finalize (object); G_OBJECT_CLASS (gst_rtp_rtx_receive_parent_class)->finalize (object);
} }
typedef struct
{
guint32 ssrc;
GstClockTime time;
} SsrcAssoc;
static SsrcAssoc *
ssrc_assoc_new (guint32 ssrc, GstClockTime time)
{
SsrcAssoc *assoc = g_slice_new (SsrcAssoc);
assoc->ssrc = ssrc;
assoc->time = time;
return assoc;
}
static void
ssrc_assoc_free (SsrcAssoc * assoc)
{
g_slice_free (SsrcAssoc, assoc);
}
static void static void
gst_rtp_rtx_receive_init (GstRtpRtxReceive * rtx) gst_rtp_rtx_receive_init (GstRtpRtxReceive * rtx)
{ {
@ -261,7 +286,8 @@ gst_rtp_rtx_receive_init (GstRtpRtxReceive * rtx)
gst_element_add_pad (GST_ELEMENT (rtx), rtx->sinkpad); gst_element_add_pad (GST_ELEMENT (rtx), rtx->sinkpad);
rtx->ssrc2_ssrc1_map = g_hash_table_new (g_direct_hash, g_direct_equal); rtx->ssrc2_ssrc1_map = g_hash_table_new (g_direct_hash, g_direct_equal);
rtx->seqnum_ssrc1_map = g_hash_table_new (g_direct_hash, g_direct_equal); rtx->seqnum_ssrc1_map = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) ssrc_assoc_free);
rtx->rtx_pt_map = g_hash_table_new (g_direct_hash, g_direct_equal); rtx->rtx_pt_map = g_hash_table_new (g_direct_hash, g_direct_equal);
} }
@ -282,7 +308,6 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
if (gst_structure_has_name (s, "GstRTPRetransmissionRequest")) { if (gst_structure_has_name (s, "GstRTPRetransmissionRequest")) {
guint seqnum = 0; guint seqnum = 0;
guint ssrc = 0; guint ssrc = 0;
gpointer ssrc1 = 0;
gpointer ssrc2 = 0; gpointer ssrc2 = 0;
/* retrieve seqnum of the packet that need to be restransmisted */ /* retrieve seqnum of the packet that need to be restransmisted */
@ -314,12 +339,14 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
GST_DEBUG_OBJECT (rtx, "Retransmited stream %" G_GUINT32_FORMAT GST_DEBUG_OBJECT (rtx, "Retransmited stream %" G_GUINT32_FORMAT
" already associated to its master", GPOINTER_TO_UINT (ssrc2)); " already associated to its master", GPOINTER_TO_UINT (ssrc2));
} else { } else {
SsrcAssoc *assoc;
/* not already associated but also we have to check that we have not /* not already associated but also we have to check that we have not
* already considered this request. * already considered this request.
*/ */
if (g_hash_table_lookup_extended (rtx->seqnum_ssrc1_map, if (g_hash_table_lookup_extended (rtx->seqnum_ssrc1_map,
GUINT_TO_POINTER (seqnum), NULL, &ssrc1)) { GUINT_TO_POINTER (seqnum), NULL, (gpointer *) & assoc)) {
if (GPOINTER_TO_UINT (ssrc1) == ssrc) { if (assoc->ssrc == ssrc) {
/* do nothing because we have already considered this request /* do nothing because we have already considered this request
* The jitter may be too impatient of the rtx packet has been * The jitter may be too impatient of the rtx packet has been
* lost too. * lost too.
@ -329,34 +356,45 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
GST_DEBUG_OBJECT (rtx, "Duplicated request seqnum: %" GST_DEBUG_OBJECT (rtx, "Duplicated request seqnum: %"
G_GUINT32_FORMAT ", ssrc1: %" G_GUINT32_FORMAT, seqnum, ssrc); G_GUINT32_FORMAT ", ssrc1: %" G_GUINT32_FORMAT, seqnum, ssrc);
} else { } else {
/* If the association attempt is larger than ASSOC_TIMEOUT,
* then we give up on it, and try this one.
*/
if (!GST_CLOCK_TIME_IS_VALID (rtx->last_time) ||
!GST_CLOCK_TIME_IS_VALID (assoc->time) ||
assoc->time + ASSOC_TIMEOUT < rtx->last_time) {
/* From RFC 4588: /* From RFC 4588:
* the receiver MUST NOT have two outstanding requests for the * the receiver MUST NOT have two outstanding requests for the
* same packet sequence number in two different original streams * same packet sequence number in two different original streams
* before the association is resolved. Otherwise it's impossible * before the association is resolved. Otherwise it's impossible
* to associate a rtx stream and its master stream * to associate a rtx stream and its master stream
*/ */
GST_DEBUG_OBJECT (rtx,
"reject request for seqnum %" G_GUINT32_FORMAT
"of master stream %" G_GUINT32_FORMAT, seqnum, ssrc);
res = TRUE;
/* remove seqnum in order to reuse the spot */ /* remove seqnum in order to reuse the spot */
g_hash_table_remove (rtx->seqnum_ssrc1_map, g_hash_table_remove (rtx->seqnum_ssrc1_map,
GUINT_TO_POINTER (seqnum)); GUINT_TO_POINTER (seqnum));
goto retransmit;
} else {
GST_DEBUG_OBJECT (rtx,
"reject request for seqnum %" G_GUINT32_FORMAT
" of master stream %" G_GUINT32_FORMAT, seqnum, ssrc);
/* do not forward the event as we are rejecting this request */ /* do not forward the event as we are rejecting this request */
GST_OBJECT_UNLOCK (rtx); GST_OBJECT_UNLOCK (rtx);
gst_event_unref (event); gst_event_unref (event);
return res; return TRUE;
}
} }
} else { } else {
retransmit:
/* the request has not been already considered /* the request has not been already considered
* insert it for the first time */ * insert it for the first time */
GST_DEBUG_OBJECT (rtx, GST_DEBUG_OBJECT (rtx,
"packet number %" G_GUINT32_FORMAT " of master stream %" "packet number %" G_GUINT32_FORMAT " of master stream %"
G_GUINT32_FORMAT " needs to be retransmited", seqnum, ssrc); G_GUINT32_FORMAT " needs to be retransmited", seqnum, ssrc);
g_hash_table_insert (rtx->seqnum_ssrc1_map, g_hash_table_insert (rtx->seqnum_ssrc1_map,
GUINT_TO_POINTER (seqnum), GUINT_TO_POINTER (ssrc)); GUINT_TO_POINTER (seqnum),
ssrc_assoc_new (ssrc, rtx->last_time));
} }
} }
@ -463,6 +501,8 @@ gst_rtp_rtx_receive_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
/* check if we have a retransmission packet (this information comes from SDP) */ /* check if we have a retransmission packet (this information comes from SDP) */
GST_OBJECT_LOCK (rtx); GST_OBJECT_LOCK (rtx);
rtx->last_time = GST_BUFFER_PTS (buffer);
is_rtx = is_rtx =
g_hash_table_lookup_extended (rtx->rtx_pt_map, g_hash_table_lookup_extended (rtx->rtx_pt_map,
GUINT_TO_POINTER (payload_type), NULL, NULL); GUINT_TO_POINTER (payload_type), NULL, NULL);
@ -488,17 +528,25 @@ gst_rtp_rtx_receive_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
GPOINTER_TO_UINT (ssrc1)); GPOINTER_TO_UINT (ssrc1));
ssrc2 = ssrc; ssrc2 = ssrc;
} else { } else {
SsrcAssoc *assoc;
/* the current retransmisted packet has its rtx stream not already /* the current retransmisted packet has its rtx stream not already
* associated to a master stream, so retrieve it from our request * associated to a master stream, so retrieve it from our request
* history */ * history */
if (g_hash_table_lookup_extended (rtx->seqnum_ssrc1_map, if (g_hash_table_lookup_extended (rtx->seqnum_ssrc1_map,
GUINT_TO_POINTER (orign_seqnum), NULL, &ssrc1)) { GUINT_TO_POINTER (orign_seqnum), NULL, (gpointer *) & assoc)) {
GST_DEBUG_OBJECT (rtx, GST_DEBUG_OBJECT (rtx,
"associate retransmisted stream %" G_GUINT32_FORMAT "associate retransmisted stream %" G_GUINT32_FORMAT
" to master stream %" G_GUINT32_FORMAT " thanks to packet %" " to master stream %" G_GUINT32_FORMAT " thanks to packet %"
G_GUINT16_FORMAT "", ssrc, GPOINTER_TO_UINT (ssrc1), orign_seqnum); G_GUINT16_FORMAT "", ssrc, assoc->ssrc, orign_seqnum);
ssrc1 = GUINT_TO_POINTER (assoc->ssrc);
ssrc2 = ssrc; ssrc2 = ssrc;
/* just put a guard */
if (GPOINTER_TO_UINT (ssrc1) == ssrc2)
GST_WARNING_OBJECT (rtx, "RTX receiver ssrc2_ssrc1_map bad state, "
"ssrc %" G_GUINT32_FORMAT " are the same\n", ssrc);
/* free the spot so that this seqnum can be used to do another /* free the spot so that this seqnum can be used to do another
* association */ * association */
g_hash_table_remove (rtx->seqnum_ssrc1_map, g_hash_table_remove (rtx->seqnum_ssrc1_map,
@ -508,17 +556,13 @@ gst_rtp_rtx_receive_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
g_hash_table_insert (rtx->ssrc2_ssrc1_map, GUINT_TO_POINTER (ssrc2), g_hash_table_insert (rtx->ssrc2_ssrc1_map, GUINT_TO_POINTER (ssrc2),
ssrc1); ssrc1);
/* just put a guard */
if (GPOINTER_TO_UINT (ssrc1) == ssrc2)
GST_WARNING_OBJECT (rtx, "RTX receiver ssrc2_ssrc1_map bad state, "
"ssrc %" G_GUINT32_FORMAT " are the same\n", ssrc);
/* also do the association between master stream and rtx stream /* also do the association between master stream and rtx stream
* every ssrc are unique so we can use the same hash table * every ssrc are unique so we can use the same hash table
* for both retrieving the ssrc1 from ssrc2 and also ssrc2 from ssrc1 * for both retrieving the ssrc1 from ssrc2 and also ssrc2 from ssrc1
*/ */
g_hash_table_insert (rtx->ssrc2_ssrc1_map, ssrc1, g_hash_table_insert (rtx->ssrc2_ssrc1_map, ssrc1,
GUINT_TO_POINTER (ssrc2)); GUINT_TO_POINTER (ssrc2));
} else { } else {
/* we are not able to associate this rtx packet with a master stream */ /* we are not able to associate this rtx packet with a master stream */
GST_DEBUG_OBJECT (rtx, GST_DEBUG_OBJECT (rtx,

View File

@ -63,6 +63,8 @@ struct _GstRtpRtxReceive
guint num_rtx_requests; guint num_rtx_requests;
guint num_rtx_packets; guint num_rtx_packets;
guint num_rtx_assoc_packets; guint num_rtx_assoc_packets;
GstClockTime last_time;
}; };
struct _GstRtpRtxReceiveClass struct _GstRtpRtxReceiveClass