| /* 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) |