Commit fd4a9202 authored by Olivier Crête's avatar Olivier Crête 👻

rtpulpfecdec: Implement Microsoft extensions

Microsoft has a slightly difference variant. In particular, they ignore
RTP header extensions when doing FEC and the sequence number is stored as an offset
instead of as the complete number. They also add an extension to the FEC header itself.

Only the XOR variant is implemented. The MS-H264PF document hints at a Reed-Solomon
based variant, but doesn't explain it.

Ref: MS-H264PF https://docs.microsoft.com/en-us/openspecs/office_protocols/ms-h264pf/ab2ee1d8-ce1b-46f3-b128-9751256b6f18
parent 18a7c10d
Pipeline #41036 passed with stages
in 81 minutes and 52 seconds
......@@ -31,6 +31,12 @@
* compatible with libwebrtc as using in Google Chrome and with Microsoft
* Lync / Skype for Business.
*
* It also supports the Microsoft custom header extension described in
* MS-H264PF 2.2.8 and ignores it when it is present.
*
* Another limitation is that only one level of protection is currently
* supported.
*
* This element will work in combination with an upstream #GstRtpStorage
* element and attempt to recover packets declared lost through custom
* 'GstRTPPacketLost' events, usually emitted by #GstRtpJitterBuffer.
......@@ -212,12 +218,13 @@ gst_rtp_ulpfec_dec_recover_from_fec (GstRtpUlpFecDec * self,
guint64 fec_mask = rtp_ulpfec_buffer_get_mask (&info_fec->rtp);
gboolean fec_mask_long = rtp_ulpfec_buffer_get_fechdr (&info_fec->rtp)->L;
guint16 fec_seq_base = rtp_ulpfec_buffer_get_seq_base (&info_fec->rtp);
gboolean is_ms = rtp_ulpfec_buffer_is_ms (&info_fec->rtp);
GstBuffer *ret;
GList *it;
g_array_set_size (self->scratch_buf, 0);
rtp_buffer_to_ulpfec_bitstring (&info_fec->rtp, self->scratch_buf, TRUE,
fec_mask_long);
fec_mask_long, is_ms);
for (it = self->info_media; it; it = it->next) {
RtpUlpFecMapInfo *info = RTP_FEC_MAP_INFO_NTH (self, it->data);
......@@ -228,13 +235,12 @@ gst_rtp_ulpfec_dec_recover_from_fec (GstRtpUlpFecDec * self,
if (fec_mask & packet_mask) {
fec_mask ^= packet_mask;
rtp_buffer_to_ulpfec_bitstring (&info->rtp, self->scratch_buf, FALSE,
fec_mask_long);
fec_mask_long, is_ms);
}
}
ret =
rtp_ulpfec_bitstring_to_media_rtp_buffer (self->scratch_buf,
fec_mask_long, ssrc, seq);
ret = rtp_ulpfec_bitstring_to_media_rtp_buffer (self->scratch_buf,
fec_mask_long, ssrc, seq, is_ms);
if (ret) {
/* We are about to put recovered packet back in self->info_media to be able
* to reuse it later for recovery of other packets
......
......@@ -238,7 +238,7 @@ gst_rtp_ulpfec_enc_stream_ctx_protect (GstRtpUlpFecEncStreamCtx * ctx,
if (tmp_mask & packet_mask) {
tmp_mask ^= packet_mask;
rtp_buffer_to_ulpfec_bitstring (&info->rtp, ctx->scratch_buf, FALSE,
fec_mask_long);
fec_mask_long, FALSE);
}
}
......
......@@ -83,6 +83,13 @@ fec_level_hdr_set_protection_len (RtpUlpFecLevelHeader * fec_lvl_hdr,
fec_lvl_hdr->protection_len = g_htons (len);
}
static RtpMsUlpFecLevelHeader *
fec_hdr_get_ms_ext (const RtpUlpFecHeader * fec_hdr)
{
return (RtpMsUlpFecLevelHeader *) (((guint8 *) fec_hdr) +
sizeof (RtpUlpFecHeader) + fec_level_hdr_get_size (fec_hdr->L));
}
static RtpUlpFecLevelHeader *
fec_hdr_get_level_hdr (RtpUlpFecHeader const *fec_hdr)
{
......@@ -152,7 +159,15 @@ rtp_ulpfec_buffer_get_mask (GstRTPBuffer * rtp)
guint16
rtp_ulpfec_buffer_get_seq_base (GstRTPBuffer * rtp)
{
return g_ntohs (rtp_ulpfec_buffer_get_fechdr (rtp)->seq);
RtpUlpFecHeader *fec_hdr = rtp_ulpfec_buffer_get_fechdr (rtp);
/* If the E bit is set, this is a Microsoft compatible mode, as
* we don't support any other extension.
*/
if (fec_hdr->E)
return gst_rtp_buffer_get_seq (rtp) - g_ntohs (fec_hdr->seq);
else
return g_ntohs (fec_hdr->seq);
}
guint
......@@ -193,13 +208,35 @@ rtp_ulpfec_buffer_is_valid (GstRTPBuffer * rtp)
goto toosmall;
fec_hdr = rtp_ulpfec_buffer_get_fechdr (rtp);
if (fec_hdr->E)
goto invalidcontent;
fec_hdrs_len = rtp_ulpfec_get_headers_len (fec_hdr->L);
if (payload_len < fec_hdrs_len)
goto toosmall;
/* If the Extension bit is set, verify if this is a valid Microsoft
* extension as much as possible.
*/
if (fec_hdr->E) {
RtpMsUlpFecLevelHeader *fec_hdr_ms_ext = fec_hdr_get_ms_ext (fec_hdr);
fec_hdrs_len += 2;
if (payload_len < fec_hdrs_len)
goto toosmall;
if (fec_hdr_ms_ext->A != 0 || fec_hdr_ms_ext->B != 0 ||
fec_hdr_ms_ext->E != 0)
goto invalidcontent;
if (fec_hdr_ms_ext->F != 1 || fec_hdr_ms_ext->G != 0) {
goto ms_reed_solomon_not_supported;
}
if (payload_len <
fec_hdrs_len + rtp_ulpfec_hdr_get_protection_len (fec_hdr))
goto toosmall;
}
fec_packet_len = fec_hdrs_len + rtp_ulpfec_hdr_get_protection_len (fec_hdr);
if (fec_packet_len != payload_len)
goto lengthmismatch;
......@@ -210,24 +247,52 @@ toosmall:
return FALSE;
lengthmismatch:
GST_WARNING ("invalid FEC packet (declared length %u, real length %u)",
GST_WARNING ("invalid FEC packet (declared length %u > real length %u)",
fec_packet_len, payload_len);
return FALSE;
invalidcontent:
GST_WARNING ("FEC Header contains invalid fields: %u", fec_hdr->E);
GST_WARNING ("FEC Header contains invalid extension");
return FALSE;
ms_reed_solomon_not_supported:
GST_WARNING ("Reed-Solomon Microsoft variant not supported ");
return FALSE;
}
/* Checks if it it the Microsoft extension as defined in MS-H264PF
* There are some small differences with what Chrome does.
*/
gboolean
rtp_ulpfec_buffer_is_ms (GstRTPBuffer * rtp)
{
RtpUlpFecHeader *fec_hdr = rtp_ulpfec_buffer_get_fechdr (rtp);
return fec_hdr->E;
}
void
rtp_buffer_to_ulpfec_bitstring (GstRTPBuffer * rtp, GArray * dst_arr,
gboolean fec_buffer, gboolean fec_mask_long)
gboolean fec_buffer, gboolean fec_mask_long, gboolean is_ms)
{
if (G_UNLIKELY (fec_buffer)) {
guint payload_len = gst_rtp_buffer_get_payload_len (rtp);
g_array_set_size (dst_arr, MAX (payload_len, dst_arr->len));
memcpy (dst_arr->data, gst_rtp_buffer_get_payload (rtp), payload_len);
if (is_ms) {
/* The microsoft variant has an extra heaer that we must skip */
char *payload = gst_rtp_buffer_get_payload (rtp);
guint headers_len = rtp_ulpfec_get_headers_len (fec_mask_long);
g_array_set_size (dst_arr, MAX (payload_len - 2, dst_arr->len));
memcpy (dst_arr->data, payload, headers_len);
memcpy (dst_arr->data + headers_len, payload + headers_len + 2,
payload_len - headers_len - 2);
} else {
g_array_set_size (dst_arr, MAX (payload_len, dst_arr->len));
memcpy (dst_arr->data, gst_rtp_buffer_get_payload (rtp), payload_len);
}
} else {
const guint8 *src = rtp->data[0];
guint len = gst_rtp_buffer_get_packet_len (rtp) - MIN_RTP_HEADER_LEN;
......@@ -235,6 +300,12 @@ rtp_buffer_to_ulpfec_bitstring (GstRTPBuffer * rtp, GArray * dst_arr,
guint src_offset = MIN_RTP_HEADER_LEN;
guint8 *dst;
if (is_ms) {
/* In the Microsoft variant, the header extensions are not protected */
len = gst_rtp_buffer_get_payload_len (rtp);
src_offset = (const guint8 *) gst_rtp_buffer_get_payload (rtp) - src;
}
g_array_set_size (dst_arr, MAX (dst_offset + len, dst_arr->len));
dst = (guint8 *) dst_arr->data;
......@@ -246,7 +317,7 @@ rtp_buffer_to_ulpfec_bitstring (GstRTPBuffer * rtp, GArray * dst_arr,
GstBuffer *
rtp_ulpfec_bitstring_to_media_rtp_buffer (GArray * arr,
gboolean fec_mask_long, guint32 ssrc, guint16 seq)
gboolean fec_mask_long, guint32 ssrc, guint16 seq, gboolean is_ms)
{
guint fec_hdrs_len = rtp_ulpfec_get_headers_len (fec_mask_long);
guint payload_len =
......@@ -266,6 +337,8 @@ rtp_ulpfec_bitstring_to_media_rtp_buffer (GArray * arr,
((RtpHeader *) ret_info.data)->version = 2;
((RtpHeader *) ret_info.data)->seq = g_htons (seq);
((RtpHeader *) ret_info.data)->ssrc = g_htonl (ssrc);
if (is_ms)
((RtpHeader *) ret_info.data)->extension = 0;
/* Filling payload */
memcpy (ret_info.data + MIN_RTP_HEADER_LEN,
arr->data + fec_hdrs_len, payload_len);
......
......@@ -120,6 +120,35 @@ typedef struct
guint32 mask_continued;
} ATTRIBUTE_PACKED RtpUlpFecLevelHeader;
/* This second header is the extension header defined by Microsoft in
* MS-H264PF Section 2.2.8.1.4.
*/
typedef struct
{
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
unsigned int E:4; /* Always 0 */
unsigned int D:1; /* Always 0: RTP header second bit */
unsigned int C:1; /* Always 1: RTP header first bit*/
unsigned int B:1; /* C field: always 0 */
unsigned int A:1; /* V field (is reserved2 present?) */
unsigned int G:4; /* Index in the number of packets in one op */
unsigned int F:4; /* Number of FEC packets in prot operation */
#elif G_BYTE_ORDER == G_BIG_ENDIAN
unsigned int A:1; /* V field (is reserved2 present?) */
unsigned int B:1; /* C field: always 0 */
unsigned int C:1; /* RTP header first bit*/
unsigned int D:1; /* RTP header second bit */
unsigned int E:4; /* Always 0 */
unsigned int F:4; /* Number of FEC packets in prot operation */
unsigned int G:4; /* Index in the number of packets in one op */
#else
#error "G_BYTE_ORDER should be big or little endian."
#endif
unsigned int reserved2:16; /* Only present if A == 1 */
} ATTRIBUTE_PACKED RtpMsUlpFecLevelHeader;
#ifdef _MSC_VER
#pragma pack(pop)
#else
......@@ -129,9 +158,11 @@ typedef struct
gboolean rtp_ulpfec_map_info_map (GstBuffer *buffer, RtpUlpFecMapInfo *info);
void rtp_ulpfec_map_info_unmap (RtpUlpFecMapInfo *info);
void rtp_buffer_to_ulpfec_bitstring (GstRTPBuffer *rtp, GArray *dst_arr,
gboolean fec_buffer, gboolean fec_mask_long);
gboolean fec_buffer, gboolean fec_mask_long,
gboolean is_ms);
GstBuffer * rtp_ulpfec_bitstring_to_media_rtp_buffer (GArray *arr,
gboolean fec_mask_long, guint32 ssrc, guint16 seq);
gboolean fec_mask_long, guint32 ssrc, guint16 seq,
gboolean is_ms);
GstBuffer * rtp_ulpfec_bitstring_to_fec_rtp_buffer (GArray *arr, guint16 seq_base, gboolean fec_mask_long,
guint64 fec_mask, gboolean marker, guint8 pt, guint16 seq,
guint32 timestamp, guint32 ssrc);
......@@ -156,6 +187,7 @@ guint64 rtp_ulpfec_buffer_get_mask (GstRTPBuffer *rtp);
guint16 rtp_ulpfec_buffer_get_seq_base (GstRTPBuffer *rtp);
gboolean rtp_ulpfec_mask_is_long (guint64 mask);
gboolean rtp_ulpfec_buffer_is_valid (GstRTPBuffer * rtp);
gboolean rtp_ulpfec_buffer_is_ms (GstRTPBuffer * rtp);
G_END_DECLS
......
......@@ -74,10 +74,14 @@ push_lost_event (GstHarness * h, guint32 seqnum,
}
if (event_goes_through) {
const GstStructure *s = gst_event_get_structure (packet_loss_out);
const GstStructure *s;
guint64 tscopy, durcopy;
gboolean might_have_been_fec;
fail_unless (packet_loss_out != NULL);
s = gst_event_get_structure (packet_loss_out);
fail_unless (gst_structure_has_name (s, "GstRTPPacketLost"));
fail_if (gst_structure_has_field (s, "seqnum"));
fail_unless (gst_structure_get_uint64 (s, "timestamp", &tscopy));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment