diff --git a/CMakeLists.txt b/CMakeLists.txt index 8db85d7..fc87868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,7 +148,7 @@ target_link_libraries(${PROJECT_NAME} target_include_directories(${PROJECT_NAME} PUBLIC $ - PRIVATE $ + PRIVATE $ PUBLIC $ ) diff --git a/docs/README.md b/docs/README.md index a338393..e22e40d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -173,4 +173,4 @@ None of these parameters will however help if you are sending more data than the ## Using uvgRTP RTCP for Congestion Control -When RTCP is enabled in uvgRTP (using `RCE_RTCP`), the fields in [rtcp_report_block](../include/uvgrtp/frame.hh#L106) can be used to estimate the available network bandwidth. Report blocks are sent by all media_stream entities receiving data and can be included in both Sender Reports (when sending and receiving) and Receiver Reports (when only receiving). There exists several algorithms for congestion control, but they are outside the scope of uvgRTP. +When RTCP is enabled in uvgRTP (using `RCE_RTCP`); fraction, lost and jitter fields in [rtcp_report_block](../include/uvgrtp/frame.hh#L106) can be used to detect network congestion. Report blocks are sent by all media_stream entities receiving data and can be included in both Sender Reports (when sending and receiving) and Receiver Reports (when only receiving). There exists several algorithms for congestion control, but they are outside the scope of uvgRTP. diff --git a/src/rtcp.cc b/src/rtcp.cc index 98ca055..7486084 100644 --- a/src/rtcp.cc +++ b/src/rtcp.cc @@ -736,7 +736,7 @@ rtp_error_t uvgrtp::rtcp::install_app_hook(std::function prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { return nullptr; @@ -752,7 +752,7 @@ uvgrtp::frame::rtcp_sender_report* uvgrtp::rtcp::get_sender_packet(uint32_t ssrc uvgrtp::frame::rtcp_receiver_report* uvgrtp::rtcp::get_receiver_packet(uint32_t ssrc) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { return nullptr; @@ -768,7 +768,7 @@ uvgrtp::frame::rtcp_receiver_report* uvgrtp::rtcp::get_receiver_packet(uint32_t uvgrtp::frame::rtcp_sdes_packet* uvgrtp::rtcp::get_sdes_packet(uint32_t ssrc) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { return nullptr; @@ -784,7 +784,7 @@ uvgrtp::frame::rtcp_sdes_packet* uvgrtp::rtcp::get_sdes_packet(uint32_t ssrc) uvgrtp::frame::rtcp_app_packet* uvgrtp::rtcp::get_app_packet(uint32_t ssrc) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { return nullptr; @@ -805,7 +805,7 @@ std::vector uvgrtp::rtcp::get_participants() const for (auto& i : participants_) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); ssrcs.push_back(i.first); } @@ -862,7 +862,7 @@ void uvgrtp::rtcp::zero_stats(uvgrtp::receiver_statistics *stats) bool uvgrtp::rtcp::is_participant(uint32_t ssrc) const { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); return participants_.find(ssrc) != participants_.end(); } @@ -956,7 +956,7 @@ rtp_error_t uvgrtp::rtcp::update_sender_stats(size_t pkt_size) rtp_error_t uvgrtp::rtcp::init_participant_seq(uint32_t ssrc, uint16_t base_seq) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { return RTP_NOT_FOUND; @@ -971,7 +971,7 @@ rtp_error_t uvgrtp::rtcp::init_participant_seq(uint32_t ssrc, uint16_t base_seq) rtp_error_t uvgrtp::rtcp::update_participant_seq(uint32_t ssrc, uint16_t seq) { - std::unique_lock prtcp_lock(participants_mutex_); + std::unique_lock prtcp_lock(participants_mutex_); if (participants_.find(ssrc) == participants_.end()) { UVG_LOG_ERROR("Did not find participant SSRC when updating seq"); @@ -1033,7 +1033,7 @@ rtp_error_t uvgrtp::rtcp::update_participant_seq(uint32_t ssrc, uint16_t seq) rtp_error_t uvgrtp::rtcp::reset_rtcp_state(uint32_t ssrc) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_.find(ssrc) != participants_.end()) { return RTP_SSRC_COLLISION; @@ -1046,13 +1046,13 @@ rtp_error_t uvgrtp::rtcp::reset_rtcp_state(uint32_t ssrc) bool uvgrtp::rtcp::collision_detected(uint32_t ssrc) const { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); return participants_.find(ssrc) == participants_.end(); } void uvgrtp::rtcp::update_session_statistics(const uvgrtp::frame::rtp_frame *frame) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); participants_[frame->header.ssrc]->stats.received_rtp_packet = true; participants_[frame->header.ssrc]->stats.received_pkts += 1; @@ -1385,7 +1385,7 @@ rtp_error_t uvgrtp::rtcp::handle_receiver_report_packet(uint8_t* buffer, size_t& rr_hook_u_(std::unique_ptr(frame)); } else { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); /* Deallocate previous frame from the buffer if it exists, it's going to get overwritten */ if (participants_[frame->ssrc]->rr_frame) { @@ -1439,7 +1439,7 @@ rtp_error_t uvgrtp::rtcp::handle_sender_report_packet(uint8_t* buffer, size_t& r sr_hook_u_(std::unique_ptr(frame)); } else { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); /* Deallocate previous frame from the buffer if it exists, it's going to get overwritten */ if (participants_[frame->ssrc]->sr_frame) { @@ -1507,7 +1507,7 @@ rtp_error_t uvgrtp::rtcp::handle_sdes_packet(uint8_t* packet, size_t& read_ptr, } else if (sdes_hook_u_) { sdes_hook_u_(std::unique_ptr(frame)); } else { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); // Deallocate previous frame from the buffer if it exists, it's going to get overwritten if (participants_[sender_ssrc]->sdes_frame) { @@ -1605,7 +1605,7 @@ rtp_error_t uvgrtp::rtcp::handle_app_packet(uint8_t* packet, size_t& read_ptr, } else if (app_hook_u_) { app_hook_u_(std::unique_ptr(frame)); } else { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if (participants_[frame->ssrc]->app_frame) { delete[] participants_[frame->ssrc]->app_frame->payload; @@ -1640,7 +1640,7 @@ rtp_error_t uvgrtp::rtcp::send_rtcp_packet_to_participants(uint8_t* frame, uint3 if (rtcp_socket_ != nullptr) { - std::lock_guard prtcp_lock(participants_mutex_); + std::lock_guard prtcp_lock(participants_mutex_); if ((ret = rtcp_socket_->sendto(socket_address_, socket_address_ipv6_, frame, frame_size, 0)) != RTP_OK) { UVG_LOG_ERROR("Sending rtcp packet with sendto() failed!"); @@ -1744,7 +1744,7 @@ rtp_error_t uvgrtp::rtcp::generate_report() bool bye_packet = !bye_ssrcs_.empty(); // Unique lock unlocks when exiting the scope - std::unique_lock prtcp_lock(participants_mutex_); + std::unique_lock prtcp_lock(participants_mutex_); uint8_t reports = 0; for (auto& p : participants_) { @@ -1756,10 +1756,11 @@ rtp_error_t uvgrtp::rtcp::generate_report() std::vector< std::shared_ptr> outgoing_apps_; if (hooked_app_) { std::lock_guard grd(send_app_mutex_); - for (auto& [name, hook] : outgoing_app_hooks_) { - uint32_t p_len = 0; - uint8_t subtype = 0; - + for (auto& p : outgoing_app_hooks_) { + uint32_t p_len = 0; + uint8_t subtype = 0; + std::string name = p.first; + auto hook = p.second; std::unique_ptr pload = hook(subtype, p_len); if (p_len > 0 && sizeof(pload.get()) != 0) { std::shared_ptr app_pkt = std::make_shared(name.data(), subtype, p_len, std::move(pload)); @@ -1851,8 +1852,13 @@ rtp_error_t uvgrtp::rtcp::generate_report() /* Calculate number of packets lost */ uint32_t lost = expected - p.second->stats.received_pkts; - lost = std::clamp(lost, uint32_t(8388607), uint32_t(8388608)); - + // clamp lost at 0x7fffff for positive loss and 0x800000 for negative loss + if (lost > 8388608) { + lost = 8388608; + } + else if (lost < 8388607) { + lost = 8388607; + } uint32_t expected_interval = expected - p.second->stats.expected_prior; p.second->stats.expected_prior = expected; uint32_t received_interval = p.second->stats.received_pkts - p.second->stats.received_prior; @@ -1998,7 +2004,12 @@ rtp_error_t uvgrtp::rtcp::send_app_packet(const char* name, uint8_t subtype, { packet_mutex_.lock(); - std::unique_ptr pl = std::make_unique(*payload); + std::unique_ptr pl = std::make_unique(payload_len); + + for (uint32_t c = 0; c < payload_len; ++c) { + pl[c] = payload[c]; + } + if (!app_packets_[name].empty()) { UVG_LOG_DEBUG("Adding a new APP packet for sending when %llu packets are waiting to be sent", diff --git a/src/socket.cc b/src/socket.cc index 1e05b41..44a5e0e 100644 --- a/src/socket.cc +++ b/src/socket.cc @@ -110,38 +110,140 @@ rtp_error_t uvgrtp::socket::bind(short family, unsigned host, short port) return bind(local_address_); } +static bool is_multicast(sockaddr_in& local_address) +{ + // Multicast addresses ranges from 224.0.0.0 to 239.255.255.255 (0xE0000000 to 0xEFFFFFFF) + auto addr = local_address.sin_addr.s_addr; + return (ntohl(addr) & 0xF0000000) == 0xE0000000; +} + rtp_error_t uvgrtp::socket::bind(sockaddr_in& local_address) { local_address_ = local_address; UVG_LOG_DEBUG("Binding to address %s", sockaddr_to_string(local_address_).c_str()); - if (::bind(socket_, (struct sockaddr*)&local_address_, sizeof(local_address_)) < 0) { + if (!::is_multicast(local_address_)) { + // Regular address + if (::bind(socket_, (struct sockaddr*)&local_address_, sizeof(local_address_)) < 0) { #ifdef _WIN32 - win_get_last_error(); + win_get_last_error(); #else - fprintf(stderr, "%s\n", strerror(errno)); + fprintf(stderr, "%s\n", strerror(errno)); #endif - UVG_LOG_ERROR("Binding to port %u failed!", ntohs(local_address_.sin_port)); - return RTP_BIND_ERROR; + UVG_LOG_ERROR("Binding to port %u failed!", ntohs(local_address_.sin_port)); + return RTP_BIND_ERROR; + } + } else { + // Multicast address + // Reuse address to enabled receiving the same stream multiple times + const int enable = 1; + if (::setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(int)) < 0) { +#ifdef _WIN32 + win_get_last_error(); +#else + fprintf(stderr, "%s\n", strerror(errno)); +#endif + + UVG_LOG_ERROR("Reuse address failed!"); + } + + // Bind with empty address + auto bind_addr_in = local_address_; + bind_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + + if (::bind(socket_, (struct sockaddr*)&bind_addr_in, sizeof(bind_addr_in)) < 0) { +#ifdef _WIN32 + win_get_last_error(); +#else + fprintf(stderr, "%s\n", strerror(errno)); +#endif + UVG_LOG_ERROR("Binding to port %u failed!", ntohs(bind_addr_in.sin_port)); + return RTP_BIND_ERROR; + } + + // Join multicast membership + struct ip_mreq mreq{}; + mreq.imr_multiaddr.s_addr = local_address_.sin_addr.s_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (::setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)) < 0) { +#ifdef _WIN32 + win_get_last_error(); +#else + fprintf(stderr, "%s\n", strerror(errno)); +#endif + UVG_LOG_ERROR("Multicast join failed!"); + return RTP_BIND_ERROR; + } } return RTP_OK; } +static bool is_multicast(sockaddr_in6& local_address) +{ + // Multicast IP addresses have their first byte equals to 0xFF + auto addr = local_address.sin6_addr.s6_addr; + return addr[0] == 0xFF; +} + rtp_error_t uvgrtp::socket::bind_ip6(sockaddr_in6& local_address) { local_ip6_address_ = local_address; UVG_LOG_DEBUG("Binding to address %s", sockaddr_ip6_to_string(local_ip6_address_).c_str()); - if (::bind(socket_, (struct sockaddr*)&local_ip6_address_, sizeof(local_ip6_address_)) < 0) { + if (!::is_multicast(local_ip6_address_)) { + if (::bind(socket_, (struct sockaddr*)&local_ip6_address_, sizeof(local_ip6_address_)) < 0) { + #ifdef _WIN32 + win_get_last_error(); + #else + fprintf(stderr, "%s\n", strerror(errno)); + #endif + UVG_LOG_ERROR("Binding to port %u failed!", ntohs(local_ip6_address_.sin6_port)); + return RTP_BIND_ERROR; + } + } else { + // Multicast address + // Reuse address to enabled receiving the same stream multiple times + const int enable = 1; + if (::setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(int)) < 0) { #ifdef _WIN32 - win_get_last_error(); + win_get_last_error(); #else - fprintf(stderr, "%s\n", strerror(errno)); + fprintf(stderr, "%s\n", strerror(errno)); #endif - UVG_LOG_ERROR("Binding to port %u failed!", ntohs(local_address_.sin_port)); - return RTP_BIND_ERROR; + + UVG_LOG_ERROR("Reuse address failed!"); + } + + // Bind with empty address + auto bind_addr_in = local_ip6_address_; + bind_addr_in.sin6_addr = in6addr_any; + + if (::bind(socket_, (struct sockaddr*)&bind_addr_in, sizeof(bind_addr_in)) < 0) { +#ifdef _WIN32 + win_get_last_error(); +#else + fprintf(stderr, "%s\n", strerror(errno)); +#endif + UVG_LOG_ERROR("Binding to port %u failed!", ntohs(bind_addr_in.sin6_port)); + return RTP_BIND_ERROR; + } + + // Join multicast membership + struct ipv6_mreq mreq{}; + memcpy(&mreq.ipv6mr_multiaddr, &local_ip6_address_.sin6_addr, sizeof(mreq.ipv6mr_multiaddr)); + + if (::setsockopt(socket_, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&mreq, sizeof(mreq)) < 0) { +#ifdef _WIN32 + win_get_last_error(); +#else + fprintf(stderr, "%s\n", strerror(errno)); +#endif + UVG_LOG_ERROR("Multicast join failed!"); + return RTP_BIND_ERROR; + } } return RTP_OK; diff --git a/test/test_2_rtp.cpp b/test/test_2_rtp.cpp index 98a0195..20b1bad 100644 --- a/test/test_2_rtp.cpp +++ b/test/test_2_rtp.cpp @@ -8,6 +8,7 @@ constexpr uint16_t SEND_PORT = 9300; constexpr char REMOTE_ADDRESS[] = "127.0.0.1"; +constexpr char MULTICAST_ADDRESS[] = "224.0.0.122"; constexpr uint16_t RECEIVE_PORT = 9302; void rtp_receive_hook(void* arg, uvgrtp::frame::rtp_frame* frame); @@ -384,4 +385,65 @@ TEST(RTPTests, send_large_amounts) cleanup_ms(sess, sender); cleanup_ms(sess, receiver); cleanup_sess(ctx, sess); -} \ No newline at end of file +} + +TEST(RTPTests, rtp_multicast) +{ + // Tests with a multicast address + std::cout << "Starting RTP multicast test" << std::endl; + uvgrtp::context ctx; + uvgrtp::session* sess = ctx.create_session(MULTICAST_ADDRESS, MULTICAST_ADDRESS); + + uvgrtp::media_stream* sender = nullptr; + uvgrtp::media_stream* receiver = nullptr; + + int flags = RCE_FRAGMENT_GENERIC; + if (sess) + { + sender = sess->create_stream(RECEIVE_PORT, SEND_PORT, RTP_FORMAT_GENERIC, flags); + receiver = sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags); + } + + int test_packets = 10; + std::vector sizes = { 1000, 2000 }; + for (size_t& size : sizes) + { + std::unique_ptr test_frame = create_test_packet(RTP_FORMAT_GENERIC, 0, false, size, RTP_NO_FLAGS); + test_packet_size(std::move(test_frame), test_packets, size, sess, sender, receiver, RTP_NO_FLAGS); + } + + cleanup_ms(sess, sender); + cleanup_ms(sess, receiver); + cleanup_sess(ctx, sess); +} + +TEST(RTPTests, rtp_multicast_multiple) +{ + // Tests with a multicast address + std::cout << "Starting RTP multicast test" << std::endl; + uvgrtp::context ctx; + uvgrtp::session* sess = ctx.create_session(MULTICAST_ADDRESS, MULTICAST_ADDRESS); + + EXPECT_NE(nullptr, sess); + if (!sess) return; + + int flags = RCE_FRAGMENT_GENERIC; + + auto sender = sess->create_stream(RECEIVE_PORT, SEND_PORT, RTP_FORMAT_GENERIC, flags); + std::vector receivers = { + sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags), + sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags) + }; + + int test_packets = 10; + std::vector sizes = { 1000, 2000 }; + for (size_t& size : sizes) + { + std::unique_ptr test_frame = create_test_packet(RTP_FORMAT_GENERIC, 0, false, size, RTP_NO_FLAGS); + test_packet_size(std::move(test_frame), test_packets, size, sess, sender, receivers, RTP_NO_FLAGS); + } + + cleanup_ms(sess, sender); + for (auto receiver: receivers) cleanup_ms(sess, receiver); + cleanup_sess(ctx, sess); +} diff --git a/test/test_3_rtcp.cpp b/test/test_3_rtcp.cpp index 1cafc31..4d62e3a 100644 --- a/test/test_3_rtcp.cpp +++ b/test/test_3_rtcp.cpp @@ -271,6 +271,7 @@ void app_hook(uvgrtp::frame::rtcp_app_packet* frame) size_t payload_len = size_t(frame->header.length - 2)*4; std::string payload = std::string((char*)frame->payload, payload_len); std::string name = std::string((char*)frame->name, 4); + uint8_t subtype = uint8_t(frame->header.pkt_subtype); std::cout << std::endl << "APP frame! ----------" << std::endl; std::cout << "ssrc: " << frame->ssrc << std::endl; @@ -278,6 +279,10 @@ void app_hook(uvgrtp::frame::rtcp_app_packet* frame) std::cout << "Calculated payload length " << payload_len << std::endl; std::cout << "Payload length field " << frame->payload_len << std::endl; + EXPECT_EQ(name, "Test"); + EXPECT_EQ(payload, "ABCD"); + EXPECT_EQ(payload_len, 4); + EXPECT_EQ(subtype, 1); if (payload_len > 0) { diff --git a/test/test_7_ipv6.cpp b/test/test_7_ipv6.cpp index a81a9f9..7ccf259 100644 --- a/test/test_7_ipv6.cpp +++ b/test/test_7_ipv6.cpp @@ -9,6 +9,8 @@ constexpr uint16_t SEND_PORT = 9300; constexpr char REMOTE_ADDRESS[] = "::1"; constexpr uint16_t RECEIVE_PORT = 9400; +constexpr char MULTICAST_ADDRESS[] = "FF02:0:0:0:0:0:0:0"; + // RTCP TEST PARAMETERS constexpr uint16_t PAYLOAD_LEN = 256; constexpr uint16_t FRAME_RATE = 30; @@ -528,4 +530,65 @@ void zrtp_receive_func6(uvgrtp::session* receiver_session, int sender_port, int } cleanup_ms(receiver_session, recv); -} \ No newline at end of file +} + +TEST(RTPTests_ip6, rtp_multicast_ip6) +{ + // Tests installing a hook to uvgRTP + std::cout << "Starting IPv6 RTP hook test" << std::endl; + uvgrtp::context ctx; + uvgrtp::session* sess = ctx.create_session(MULTICAST_ADDRESS, MULTICAST_ADDRESS); + + uvgrtp::media_stream* sender = nullptr; + uvgrtp::media_stream* receiver = nullptr; + + int flags = RCE_FRAGMENT_GENERIC; + if (sess) + { + sender = sess->create_stream(RECEIVE_PORT, SEND_PORT, RTP_FORMAT_GENERIC, flags); + receiver = sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags); + } + + int test_packets = 10; + std::vector sizes = { 1000, 2000 }; + for (size_t& size : sizes) + { + std::unique_ptr test_frame = create_test_packet(RTP_FORMAT_GENERIC, 0, false, size, RTP_NO_FLAGS); + test_packet_size(std::move(test_frame), test_packets, size, sess, sender, receiver, RTP_NO_FLAGS); + } + + cleanup_ms(sess, sender); + cleanup_ms(sess, receiver); + cleanup_sess(ctx, sess); +} + +TEST(RTPTests_ip6, rtp_multicast_multiple_ip6) +{ + // Tests with a multicast address + std::cout << "Starting RTP multicast test" << std::endl; + uvgrtp::context ctx; + uvgrtp::session* sess = ctx.create_session(MULTICAST_ADDRESS, MULTICAST_ADDRESS); + + EXPECT_NE(nullptr, sess); + if (!sess) return; + + int flags = RCE_FRAGMENT_GENERIC; + + auto sender = sess->create_stream(RECEIVE_PORT, SEND_PORT, RTP_FORMAT_GENERIC, flags); + std::vector receivers = { + sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags), + sess->create_stream(SEND_PORT, RECEIVE_PORT, RTP_FORMAT_GENERIC, flags) + }; + + int test_packets = 10; + std::vector sizes = { 1000, 2000 }; + for (size_t& size : sizes) + { + std::unique_ptr test_frame = create_test_packet(RTP_FORMAT_GENERIC, 0, false, size, RTP_NO_FLAGS); + test_packet_size(std::move(test_frame), test_packets, size, sess, sender, receivers, RTP_NO_FLAGS); + } + + cleanup_ms(sess, sender); + for (auto receiver : receivers) cleanup_ms(sess, receiver); + cleanup_sess(ctx, sess); +} diff --git a/test/test_common.hh b/test/test_common.hh index 6f3d814..2a31990 100644 --- a/test/test_common.hh +++ b/test/test_common.hh @@ -14,6 +14,11 @@ inline std::unique_ptr create_test_packet(rtp_format_t format, uint8_ inline void test_packet_size(std::unique_ptr test_packet, int packets, size_t size, uvgrtp::session* sess, uvgrtp::media_stream* sender, uvgrtp::media_stream* receiver, int rtp_flags, int framerate = 25); +inline void test_packet_size(std::unique_ptr test_packet, int packets, size_t size, + uvgrtp::session* sess, + uvgrtp::media_stream* sender, std::vector const& receiver, + int rtp_flags, int framerate = 25); + inline void send_packets(std::unique_ptr test_packet, size_t size, uvgrtp::session* sess, uvgrtp::media_stream* sender, int packets, int packet_interval_ms, bool print_progress, int rtp_flags, bool send_app = false); @@ -208,6 +213,41 @@ inline void test_packet_size(std::unique_ptr test_packet, int packets } } +inline void test_packet_size(std::unique_ptr test_packet, int packets, size_t size, + uvgrtp::session* sess, uvgrtp::media_stream* sender, + std::vector const& receivers, + int rtp_flags, int framerate) +{ + EXPECT_NE(nullptr, sess); + EXPECT_NE(nullptr, sender); + + if (sess && sender) + { + std::vector testers(receivers.size(), { packets }); + + int interval_ms = 1000 / framerate; + + for (auto i = 0; i < receivers.size(); ++i) { + auto receiver = receivers[i]; + + EXPECT_NE(nullptr, receiver); + + if (!receiver) return; + + add_hook(&testers[i], receiver, rtp_receive_hook); + } + + // to increase the likelyhood that receiver thread is ready to receive + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + + send_packets(std::move(test_packet), size, sess, sender, packets, interval_ms, false, rtp_flags); + + std::this_thread::sleep_for(std::chrono::milliseconds(50 + size / 500)); + + for (auto& tester : testers) tester.gotAll(); + } +} + inline void add_hook(Test_receiver* tester, uvgrtp::media_stream* receiver, void (*hook)(void*, uvgrtp::frame::rtp_frame*)) {