blob: ff48389013c05619041cd7b1f9a6dec8ddf66f86 [file] [log] [blame]
/* GStreamer plugin for forward error correction
* Copyright (C) 2017 Pexip
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Mikhail Fludkov <misha@pexip.com>
*/
#include <gst/check/gstharness.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/check/gstcheck.h>
#define PT_RED 100
#define PT_MEDIA 96
#define CLOCKRATE 8000
#define TIMESTAMP_BASE (1000)
#define TIMESTAMP_DIFF (40 * CLOCKRATE / 1000)
#define TIMESTAMP_NTH(i) (TIMESTAMP_BASE + (i) * TIMESTAMP_DIFF)
#define xstr(s) str(s)
#define str(s) #s
#define GST_RTP_RED_ENC_CAPS_STR "application/x-rtp, payload=" xstr(PT_MEDIA)
#define _check_red_received(h, expected) \
G_STMT_START { \
guint received; \
g_object_get ((h)->element, "received", &received, NULL);\
fail_unless_equals_int (expected, received); \
} G_STMT_END
#define _check_red_sent(h, expected) \
G_STMT_START { \
guint sent; \
g_object_get ((h)->element, "sent", &sent, NULL);\
fail_unless_equals_int (expected, sent); \
} G_STMT_END
#define _check_caps(_h_, _nth_, _expected_payload_) \
G_STMT_START { \
GstEvent *_ev_; \
gint _pt_ = -1, _i_; \
GstCaps *_caps_ = NULL; \
\
for (_i_ = 0; _i_ < _nth_; ++_i_) \
gst_event_unref (gst_harness_pull_event (_h_)); \
\
_ev_ = gst_harness_pull_event (_h_); \
fail_unless (NULL != _ev_); \
fail_unless_equals_string ("caps", GST_EVENT_TYPE_NAME(_ev_));\
\
gst_event_parse_caps (_ev_, &_caps_); \
\
gst_structure_get_int ( \
gst_caps_get_structure (_caps_, 0), "payload", &_pt_); \
fail_unless_equals_int (_expected_payload_, _pt_); \
gst_event_unref (_ev_); \
} G_STMT_END
#define _check_nocaps(_h_) \
G_STMT_START { \
GstEvent *_ev_; \
while (NULL != (_ev_ = gst_harness_try_pull_event (_h_))) {\
fail_unless (GST_EVENT_TYPE (_ev_) != GST_EVENT_CAPS, \
"Don't expect to receive caps event"); \
gst_event_unref (_ev_); \
} \
} G_STMT_END
static GstBuffer *
_new_rtp_buffer (gboolean marker, guint8 csrc_count, guint8 pt, guint16 seqnum,
guint32 timestamp, guint32 ssrc, guint payload_len)
{
GstBuffer *buf = gst_rtp_buffer_new_allocate (payload_len, 0, csrc_count);
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));
gst_rtp_buffer_set_marker (&rtp, marker);
gst_rtp_buffer_set_payload_type (&rtp, pt);
gst_rtp_buffer_set_seq (&rtp, seqnum);
gst_rtp_buffer_set_timestamp (&rtp, timestamp);
gst_rtp_buffer_set_ssrc (&rtp, ssrc);
gst_rtp_buffer_unmap (&rtp);
return buf;
}
GST_START_TEST (rtpreddec_passthrough)
{
GstBuffer *bufinp, *bufout;
GstHarness *h = gst_harness_new ("rtpreddec");
gst_harness_set_src_caps_str (h, "application/x-rtp");
/* Passthrough when pt is not set */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 0, TIMESTAMP_NTH (0), 0xabe2b0b, 0);
bufout = gst_harness_push_and_pull (h, bufinp);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
gst_buffer_unref (bufout);
/* Now pt is set */
g_object_set (h->element, "pt", PT_RED, NULL);
/* Passthrough when not RED. RED pt = 100, pushing pt 99 */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_MEDIA, 1, TIMESTAMP_NTH (1), 0xabe2b0b, 0);
bufout = gst_harness_push_and_pull (h, bufinp);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
gst_buffer_unref (bufout);
/* Passthrough when not RTP buffer */
bufinp = gst_buffer_new_wrapped (g_strdup ("hello"), 5);
bufout = gst_harness_push_and_pull (h, bufinp);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
gst_buffer_unref (bufout);
_check_red_received (h, 0);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpreddec_main_block)
{
GstHarness *h = gst_harness_new ("rtpreddec");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 out_data[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
guint8 red_in[] = { PT_MEDIA, 0xa, 0xa, 0xa, 0xa, 0xa };
guint gst_ts = 3454679;
guint csrc_count = 2;
guint seq = 549;
GstBuffer *bufinp, *bufout;
guint bufinp_flags;
g_object_set (h->element, "pt", PT_RED, NULL);
gst_harness_set_src_caps_str (h, "application/x-rtp");
/* RED buffer has Marker bit set, has CSRCS and flags */
bufinp =
_new_rtp_buffer (TRUE, csrc_count, PT_RED, seq, TIMESTAMP_NTH (0),
0xabe2b0b, sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_set_csrc (&rtp, 0, 0x1abe2b0b);
gst_rtp_buffer_set_csrc (&rtp, 1, 0x2abe2b0b);
GST_BUFFER_TIMESTAMP (bufinp) = gst_ts;
GST_BUFFER_FLAG_SET (bufinp, GST_RTP_BUFFER_FLAG_RETRANSMISSION);
GST_BUFFER_FLAG_SET (bufinp, GST_BUFFER_FLAG_DISCONT);
bufinp_flags = GST_BUFFER_FLAGS (bufinp);
gst_rtp_buffer_unmap (&rtp);
/* Checking that pulled buffer has keeps everything from RED buffer */
bufout = gst_harness_push_and_pull (h, bufinp);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (GST_BUFFER_TIMESTAMP (bufout), gst_ts);
fail_unless_equals_int (GST_BUFFER_FLAGS (bufout), bufinp_flags);
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (sizeof (out_data), 0, csrc_count));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp),
TIMESTAMP_NTH (0));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_MEDIA);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq);
fail_unless_equals_int (gst_rtp_buffer_get_csrc_count (&rtp), csrc_count);
fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), 0x0abe2b0b);
fail_unless_equals_int (gst_rtp_buffer_get_csrc (&rtp, 0), 0x1abe2b0b);
fail_unless_equals_int (gst_rtp_buffer_get_csrc (&rtp, 1), 0x2abe2b0b);
fail_unless (gst_rtp_buffer_get_marker (&rtp));
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), out_data,
sizeof (out_data)));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
_check_red_received (h, 1);
gst_harness_teardown (h);
}
GST_END_TEST;
static void
_push_and_check_didnt_go_through (GstHarness * h, GstBuffer * bufinp)
{
gst_harness_push (h, bufinp);
/* Making sure it didn't go through */
fail_unless_equals_int (gst_harness_buffers_received (h), 0);
}
static void
_push_and_check_cant_pull_twice (GstHarness * h,
GstBuffer * bufinp, guint buffers_received)
{
gst_buffer_unref (gst_harness_push_and_pull (h, bufinp));
/* Making sure only one buffer was pushed through */
fail_unless_equals_int (gst_harness_buffers_received (h), buffers_received);
}
static void
_push_and_check_redundant_packet (GstHarness * h, GstBuffer * bufinp,
guint seq, guint timestamp, guint payload_len, gconstpointer payload)
{
GstBuffer *bufout = gst_harness_push_and_pull (h, bufinp);
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless (GST_BUFFER_FLAG_IS_SET (bufout, GST_RTP_BUFFER_FLAG_REDUNDANT));
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (payload_len, 0, 0));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp), timestamp);
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_MEDIA);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq);
fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), 0x0abe2b0b);
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), payload,
payload_len));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
gst_buffer_unref (gst_harness_pull (h));
}
GST_START_TEST (rtpreddec_redundant_block_not_pushed)
{
GstHarness *h = gst_harness_new ("rtpreddec");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
/* Redundant block has valid tsoffset but we have not seen any buffers before */
guint16 ts_offset = TIMESTAMP_DIFF;
guint8 red_in[] = {
0x80 | PT_MEDIA,
(guint8) (ts_offset >> 6),
(guint8) (ts_offset & 0x3f) << 2, 1, /* Redundant block size = 1 */
PT_MEDIA, 0xa, 0xa /* Main block size = 1 */
};
GstBuffer *bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 2, TIMESTAMP_NTH (2), 0xabe2b0b,
sizeof (red_in));
g_object_set (h->element, "pt", PT_RED, NULL);
gst_harness_set_src_caps_str (h, "application/x-rtp");
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_cant_pull_twice (h, bufinp, 1);
/* Redundant block has too large tsoffset */
ts_offset = TIMESTAMP_DIFF * 4;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 3, TIMESTAMP_NTH (3), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_cant_pull_twice (h, bufinp, 2);
/* TS offset is too small */
ts_offset = TIMESTAMP_DIFF / 2;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 4, TIMESTAMP_NTH (4), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_cant_pull_twice (h, bufinp, 3);
/* Now we ts_offset points to the previous buffer we didnt loose */
ts_offset = TIMESTAMP_DIFF;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 5, TIMESTAMP_NTH (5), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_cant_pull_twice (h, bufinp, 4);
_check_red_received (h, 4);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpreddec_redundant_block_pushed)
{
GstHarness *h = gst_harness_new ("rtpreddec");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint16 ts_offset = TIMESTAMP_DIFF;
guint8 red_in[] = {
0x80 | PT_MEDIA,
(guint8) (ts_offset >> 6),
(guint8) (ts_offset & 0x3f) << 2, 5, /* Redundant block size = 5 */
PT_MEDIA, 0x01, 0x02, 0x03, 0x4, 0x5, 0xa /* Main block size = 1 */
};
GstBuffer *bufinp;
g_object_set (h->element, "pt", PT_RED, NULL);
gst_harness_set_src_caps_str (h, "application/x-rtp");
/* Pushing seq=0 */
gst_buffer_unref (gst_harness_push_and_pull (h, _new_rtp_buffer (FALSE, 0,
PT_MEDIA, 0, TIMESTAMP_NTH (0), 0xabe2b0b, 0)));
/* Pushing seq=2, recovering seq=1 (fec distance 1) */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 2, TIMESTAMP_NTH (2), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_redundant_packet (h, bufinp, 1, TIMESTAMP_NTH (1), 5,
red_in + 5);
/* Pushing seq=5, recovering seq=3 (fec distance 2) */
ts_offset = TIMESTAMP_DIFF * 2;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 5, TIMESTAMP_NTH (5), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_redundant_packet (h, bufinp, 3, TIMESTAMP_NTH (3), 5,
red_in + 5);
/* Pushing seq=9, recovering seq=6 (fec distance 3) */
ts_offset = TIMESTAMP_DIFF * 3;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 9, TIMESTAMP_NTH (9), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_redundant_packet (h, bufinp, 6, TIMESTAMP_NTH (6), 5,
red_in + 5);
/* Pushing seq=14, recovering seq=10 (fec distance 4) */
ts_offset = TIMESTAMP_DIFF * 4;
red_in[1] = ts_offset >> 6;
red_in[2] = (ts_offset & 0x3f) << 2;
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 14, TIMESTAMP_NTH (14), 0xabe2b0b,
sizeof (red_in));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &red_in, sizeof (red_in));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_redundant_packet (h, bufinp, 10, TIMESTAMP_NTH (10), 5,
red_in + 5);
_check_red_received (h, 4);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpreddec_invalid)
{
GstBuffer *bufinp;
GstHarness *h = gst_harness_new ("rtpreddec");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
/* 2 block RED packets should have at least 4 bytes for redundant block
* header and 1 byte for the main block header. */
guint8 data[] = {
0x80 | PT_MEDIA, 0, 0, 1, /* 1st block header (redundant block) size=1, timestmapoffset=0 */
PT_MEDIA, /* 2nd block header (main block) size=0 */
};
g_object_set (h->element, "pt", PT_RED, NULL);
gst_harness_set_src_caps_str (h, "application/x-rtp");
/* Single block RED packets should have at least 1 byte of payload to be
* considered valid. This buffer does not have any payload */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 0, TIMESTAMP_NTH (0), 0xabe2b0b, 0);
_push_and_check_didnt_go_through (h, bufinp);
/* Only the first byte with F bit set (indication of redundant block) */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 1, TIMESTAMP_NTH (1), 0xabe2b0b, 1);
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &data, sizeof (data));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_didnt_go_through (h, bufinp);
/* Full 1st block header only */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 2, TIMESTAMP_NTH (2), 0xabe2b0b, 4);
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &data, sizeof (data));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_didnt_go_through (h, bufinp);
/* Both blocks, missing 1 byte of payload for redundant block */
bufinp =
_new_rtp_buffer (FALSE, 0, PT_RED, 3, TIMESTAMP_NTH (3), 0xabe2b0b, 5);
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &data, sizeof (data));
gst_rtp_buffer_unmap (&rtp);
_push_and_check_didnt_go_through (h, bufinp);
_check_red_received (h, 4);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_passthrough)
{
GstBuffer *bufinp, *bufout;
GstHarness *h = gst_harness_new ("rtpredenc");
g_object_set (h->element, "allow-no-red-blocks", FALSE, NULL);
gst_harness_set_src_caps_str (h, GST_RTP_RED_ENC_CAPS_STR);
bufinp =
_new_rtp_buffer (FALSE, 0, PT_MEDIA, 0, TIMESTAMP_NTH (0), 0xabe2b0b, 0);
bufout = gst_harness_push_and_pull (h, bufinp);
_check_caps (h, 1, PT_MEDIA);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
gst_buffer_unref (bufout);
/* Setting pt and allowing RED packets without redundant blocks */
g_object_set (h->element, "pt", PT_RED, "allow-no-red-blocks", TRUE, NULL);
/* Passthrough when not RTP buffer */
bufinp = gst_buffer_new_wrapped (g_strdup ("hello"), 5);
bufout = gst_harness_push_and_pull (h, bufinp);
_check_nocaps (h);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
gst_buffer_unref (bufout);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_payloadless_rtp)
{
GstHarness *h = gst_harness_new ("rtpredenc");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 out_data[] = { PT_MEDIA };
GstBuffer *bufout;
g_object_set (h->element, "pt", PT_RED, "allow-no-red-blocks", TRUE, NULL);
gst_harness_set_src_caps_str (h, GST_RTP_RED_ENC_CAPS_STR);
bufout =
gst_harness_push_and_pull (h, _new_rtp_buffer (TRUE, 0, PT_MEDIA, 0,
TIMESTAMP_NTH (0), 0xabe2b0b, 0));
_check_caps (h, 1, PT_RED);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (sizeof (out_data), 0, 0));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp),
TIMESTAMP_NTH (0));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_RED);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), 0);
fail_unless_equals_int (gst_rtp_buffer_get_csrc_count (&rtp), 0);
fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), 0x0abe2b0b);
fail_unless (gst_rtp_buffer_get_marker (&rtp));
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), out_data,
sizeof (out_data)));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
_check_red_sent (h, 1);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_without_redundant_block)
{
GstHarness *h = gst_harness_new ("rtpredenc");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 in_data[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
guint8 out_data[] = { PT_MEDIA, 0xa, 0xa, 0xa, 0xa, 0xa };
guint gst_ts = 3454679;
guint csrc_count = 2;
guint seq = 549;
guint bufinp_flags;
GstBuffer *bufinp, *bufout;
g_object_set (h->element, "pt", PT_RED, "allow-no-red-blocks", TRUE, NULL);
gst_harness_set_src_caps_str (h, GST_RTP_RED_ENC_CAPS_STR);
/* Media buffer has Marker bit set, has CSRCS and flags */
bufinp =
_new_rtp_buffer (TRUE, csrc_count, PT_MEDIA, seq, TIMESTAMP_NTH (0),
0xabe2b0b, sizeof (in_data));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data, sizeof (in_data));
gst_rtp_buffer_set_csrc (&rtp, 0, 0x1abe2b0b);
gst_rtp_buffer_set_csrc (&rtp, 1, 0x2abe2b0b);
gst_rtp_buffer_unmap (&rtp);
GST_BUFFER_TIMESTAMP (bufinp) = gst_ts;
GST_BUFFER_FLAG_SET (bufinp, GST_RTP_BUFFER_FLAG_RETRANSMISSION);
GST_BUFFER_FLAG_SET (bufinp, GST_BUFFER_FLAG_DISCONT);
bufinp_flags = GST_BUFFER_FLAGS (bufinp);
bufout = gst_harness_push_and_pull (h, bufinp);
/* Checking that pulled buffer has keeps everything from Media buffer */
_check_caps (h, 1, PT_RED);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (GST_BUFFER_TIMESTAMP (bufout), gst_ts);
fail_unless_equals_int (GST_BUFFER_FLAGS (bufout), bufinp_flags);
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (sizeof (out_data), 0, csrc_count));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp),
TIMESTAMP_NTH (0));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_RED);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq);
fail_unless_equals_int (gst_rtp_buffer_get_csrc_count (&rtp), csrc_count);
fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), 0x0abe2b0b);
fail_unless_equals_int (gst_rtp_buffer_get_csrc (&rtp, 0), 0x1abe2b0b);
fail_unless_equals_int (gst_rtp_buffer_get_csrc (&rtp, 1), 0x2abe2b0b);
fail_unless (gst_rtp_buffer_get_marker (&rtp));
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), out_data,
sizeof (out_data)));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
_check_red_sent (h, 1);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_with_redundant_block)
{
GstHarness *h = gst_harness_new ("rtpredenc");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 in_data0[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
guint8 in_data1[] = { 0xb, 0xb, 0xb, 0xb, 0xb };
guint8 in_data2[] = { 0xc, 0xc, 0xc, 0xc, 0xc };
guint timestmapoffset0 = TIMESTAMP_NTH (1) - TIMESTAMP_NTH (0);
guint timestmapoffset1 = TIMESTAMP_NTH (2) - TIMESTAMP_NTH (0);
guint8 out_data0[] = {
/* Redundant block header */
0x80 | PT_MEDIA, /* F=1 | pt=PT_MEDIA */
timestmapoffset0 >> 6, /* timestamp hi 8 bits */
timestmapoffset0 & 0x3f, /* timestamp lo 6 bits | length hi = 0 */
sizeof (in_data0), /* length lo 8 bits */
/* Main block header */
PT_MEDIA, /* F=0 | pt=PT_MEDIA */
/* Redundant block data */
0xa, 0xa, 0xa, 0xa, 0xa,
/* Main block data */
0xb, 0xb, 0xb, 0xb, 0xb
};
guint8 out_data1[] = {
/* Redundant block header */
0x80 | PT_MEDIA, /* F=1 | pt=PT_MEDIA */
timestmapoffset1 >> 6, /* timestamp hi 8 bits */
timestmapoffset1 & 0x3f, /* timestamp lo 6 bits | length hi = 0 */
sizeof (in_data0), /* length lo 8 bits */
/* Main block header */
PT_MEDIA, /* F=0 | pt=PT_MEDIA */
/* Redundant block data */
0xa, 0xa, 0xa, 0xa, 0xa,
/* Main block data */
0xc, 0xc, 0xc, 0xc, 0xc
};
guint seq = 549;
GstBuffer *bufinp, *bufout;
g_object_set (h->element,
"pt", PT_RED, "distance", 2, "allow-no-red-blocks", FALSE, NULL);
gst_harness_set_src_caps_str (h, GST_RTP_RED_ENC_CAPS_STR);
bufinp =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq, TIMESTAMP_NTH (0), 0xabe2b0b,
sizeof (in_data0));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data0, sizeof (in_data0));
gst_rtp_buffer_unmap (&rtp);
bufout = gst_harness_push_and_pull (h, bufinp);
/* The first buffer should go through,
* there were no redundant data to create RED packet */
_check_caps (h, 1, PT_MEDIA);
fail_unless (bufout == bufinp);
fail_unless (gst_buffer_is_writable (bufout));
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_MEDIA);
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
bufinp =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq + 1, TIMESTAMP_NTH (1), 0xabe2b0b,
sizeof (in_data1));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data1, sizeof (in_data1));
gst_rtp_buffer_unmap (&rtp);
bufout = gst_harness_push_and_pull (h, bufinp);
/* The next buffer is RED referencing previous packet */
_check_caps (h, 1, PT_RED);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (sizeof (out_data0), 0, 0));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp),
TIMESTAMP_NTH (1));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_RED);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq + 1);
fail_unless (gst_rtp_buffer_get_marker (&rtp));
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), out_data0,
sizeof (out_data0)));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
bufinp =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq + 2, TIMESTAMP_NTH (2), 0xabe2b0b,
sizeof (in_data2));
fail_unless (gst_rtp_buffer_map (bufinp, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data2, sizeof (in_data2));
gst_rtp_buffer_unmap (&rtp);
bufout = gst_harness_push_and_pull (h, bufinp);
/* The next buffer is RED referencing the packet before the previous */
_check_nocaps (h);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_buffer_get_size (bufout),
gst_rtp_buffer_calc_packet_len (sizeof (out_data1), 0, 0));
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp),
TIMESTAMP_NTH (2));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_RED);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq + 2);
fail_unless (gst_rtp_buffer_get_marker (&rtp));
fail_unless (!memcmp (gst_rtp_buffer_get_payload (&rtp), out_data1,
sizeof (out_data1)));
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
_check_red_sent (h, 2);
gst_harness_teardown (h);
}
GST_END_TEST;
static void
rtpredenc_cant_create_red_packet_base_test (GstBuffer * buffer0,
GstBuffer * buffer1)
{
/* The test configures PexRtpRedEnc to produce RED packets only with redundant
* blocks. The first packet we pull should not be RED just because it is the
* very first one. The second should not be RED because it was impossible
* to create a RED packet for varios reasons:
* - too large redundant block size
* - too large timestamp offset
* - negative timestamp offset */
GstBuffer *bufout;
GstHarness *h = gst_harness_new ("rtpredenc");
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
g_object_set (h->element,
"pt", PT_RED, "distance", 1, "allow-no-red-blocks", FALSE, NULL);
gst_harness_set_src_caps_str (h, GST_RTP_RED_ENC_CAPS_STR);
/* Checking the first pulled buffer is media packet */
bufout = gst_harness_push_and_pull (h, buffer0);
_check_caps (h, 1, PT_MEDIA);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_MEDIA);
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
/* The next buffer should be media packet too */
bufout = gst_harness_push_and_pull (h, buffer1);
_check_nocaps (h);
fail_unless (gst_rtp_buffer_map (bufout, GST_MAP_READ, &rtp));
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), PT_MEDIA);
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (bufout);
_check_red_sent (h, 0);
gst_harness_teardown (h);
}
GST_START_TEST (rtpredenc_negative_timestamp_offset)
{
gboolean with_warping;
guint16 seq0, seq1;
guint32 timestamp0, timestamp1;
GstBuffer *buffer0, *buffer1;
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 in_data[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
with_warping = __i__ != 0;
timestamp0 =
with_warping ? (0xffffffff - TIMESTAMP_DIFF / 2) : TIMESTAMP_BASE;
timestamp1 = timestamp0 + TIMESTAMP_DIFF;
seq0 = with_warping ? 0xffff : 0;
seq1 = seq0 + 1;
/* Two buffers have negative timestamp difference */
buffer0 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq0, timestamp1, 0xabe2b0b,
sizeof (in_data));
buffer1 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq1, timestamp0, 0xabe2b0b,
sizeof (in_data));
fail_unless (gst_rtp_buffer_map (buffer0, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data, sizeof (in_data));
gst_rtp_buffer_unmap (&rtp);
fail_unless (gst_rtp_buffer_map (buffer1, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data, sizeof (in_data));
gst_rtp_buffer_unmap (&rtp);
rtpredenc_cant_create_red_packet_base_test (buffer0, buffer1);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_too_large_timestamp_offset)
{
gboolean with_warping;
guint16 seq0, seq1;
guint32 timestamp0, timestamp1, timestamp_diff;
GstBuffer *buffer0, *buffer1;
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 in_data[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
with_warping = __i__ != 0;
timestamp_diff = 0x4000;
timestamp0 =
with_warping ? (0xffffffff - timestamp_diff / 2) : TIMESTAMP_BASE;
timestamp1 = timestamp0 + timestamp_diff;
seq0 = with_warping ? 0xffff : 0;
seq1 = seq0 + 1;
/* Two buffers have timestamp difference > 14bit long */
buffer0 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq0, timestamp0, 0xabe2b0b,
sizeof (in_data));
buffer1 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq1, timestamp1, 0xabe2b0b,
sizeof (in_data));
fail_unless (gst_rtp_buffer_map (buffer0, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data, sizeof (in_data));
gst_rtp_buffer_unmap (&rtp);
fail_unless (gst_rtp_buffer_map (buffer1, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data, sizeof (in_data));
gst_rtp_buffer_unmap (&rtp);
rtpredenc_cant_create_red_packet_base_test (buffer0, buffer1);
}
GST_END_TEST;
GST_START_TEST (rtpredenc_too_large_length)
{
gboolean with_warping;
guint16 seq0, seq1;
guint32 timestamp0, timestamp1;
GstBuffer *buffer0, *buffer1;
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 in_data0[1024] = { 0, };
guint8 in_data1[] = { 0xa, 0xa, 0xa, 0xa, 0xa };
with_warping = __i__ != 0;
timestamp0 =
with_warping ? (0xffffffff - TIMESTAMP_DIFF / 2) : TIMESTAMP_BASE;
timestamp1 = timestamp0 + TIMESTAMP_DIFF;
seq0 = with_warping ? 0xffff : 0;
seq1 = seq0 + 1;
/* The first buffer is too large to use as a redundant block */
buffer0 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq0, timestamp0, 0xabe2b0b,
sizeof (in_data0));
buffer1 =
_new_rtp_buffer (TRUE, 0, PT_MEDIA, seq1, timestamp1, 0xabe2b0b,
sizeof (in_data1));
fail_unless (gst_rtp_buffer_map (buffer0, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data0, sizeof (in_data0));
gst_rtp_buffer_unmap (&rtp);
fail_unless (gst_rtp_buffer_map (buffer1, GST_MAP_WRITE, &rtp));
memcpy (gst_rtp_buffer_get_payload (&rtp), &in_data1, sizeof (in_data1));
gst_rtp_buffer_unmap (&rtp);
rtpredenc_cant_create_red_packet_base_test (buffer0, buffer1);
}
GST_END_TEST;
static Suite *
rtpred_suite (void)
{
Suite *s = suite_create ("rtpred");
TCase *tc_chain = tcase_create ("decoder");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, rtpreddec_passthrough);
tcase_add_test (tc_chain, rtpreddec_main_block);
tcase_add_test (tc_chain, rtpreddec_redundant_block_not_pushed);
tcase_add_test (tc_chain, rtpreddec_redundant_block_pushed);
tcase_add_test (tc_chain, rtpreddec_invalid);
tc_chain = tcase_create ("encoder");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, rtpredenc_passthrough);
tcase_add_test (tc_chain, rtpredenc_payloadless_rtp);
tcase_add_test (tc_chain, rtpredenc_without_redundant_block);
tcase_add_test (tc_chain, rtpredenc_with_redundant_block);
tcase_add_loop_test (tc_chain, rtpredenc_negative_timestamp_offset, 0, 2);
tcase_add_loop_test (tc_chain, rtpredenc_too_large_timestamp_offset, 0, 2);
tcase_add_loop_test (tc_chain, rtpredenc_too_large_length, 0, 2);
return s;
}
GST_CHECK_MAIN (rtpred)