diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h index b436e3ff9..648b1b0f5 100644 --- a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -416,14 +416,14 @@ class RtpRtcp : public Module { /* * Turn on/off sending RTX (RFC 4588) on a specific SSRC. */ - virtual WebRtc_Word32 SetRTXSendStatus(const bool enable, + virtual WebRtc_Word32 SetRTXSendStatus(const RtxMode mode, const bool setSSRC, const WebRtc_UWord32 SSRC) = 0; /* * Get status of sending RTX (RFC 4588) on a specific SSRC. */ - virtual WebRtc_Word32 RTXSendStatus(bool* enable, + virtual WebRtc_Word32 RTXSendStatus(RtxMode* mode, WebRtc_UWord32* SSRC) const = 0; /* diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h index 2b13d3951..f0ef84f14 100644 --- a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h +++ b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h @@ -107,6 +107,12 @@ enum RetransmissionMode { kRetransmitAllPackets = 0xFF }; +enum RtxMode { + kRtxOff = 0, + kRtxRetransmitted = 1, // Apply RTX only to retransmitted packets. + kRtxAll = 2 // Apply RTX to all packets (source + retransmissions). +}; + struct RTCPSenderInfo { WebRtc_UWord32 NTPseconds; diff --git a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h index 74fbda788..53670f9f0 100644 --- a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h +++ b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -128,9 +128,10 @@ class MockRtpRtcp : public RtpRtcp { MOCK_METHOD1(SetCSRCStatus, WebRtc_Word32(const bool include)); MOCK_METHOD3(SetRTXSendStatus, - WebRtc_Word32(const bool enable, const bool setSSRC, const WebRtc_UWord32 SSRC)); + WebRtc_Word32(const RtxMode mode, const bool setSSRC, + const WebRtc_UWord32 SSRC)); MOCK_CONST_METHOD2(RTXSendStatus, - WebRtc_Word32(bool* enable, WebRtc_UWord32* SSRC)); + WebRtc_Word32(RtxMode* mode, WebRtc_UWord32* SSRC)); MOCK_METHOD1(SetSendingStatus, WebRtc_Word32(const bool sending)); MOCK_CONST_METHOD0(Sending, diff --git a/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc b/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc new file mode 100644 index 000000000..13da43b5f --- /dev/null +++ b/webrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" + +namespace webrtc { +const int kVideoNackListSize = 10; +const int kTestId = 123; +const WebRtc_UWord32 kTestSsrc = 3456; +const WebRtc_UWord16 kTestSequenceNumber = 2345; +const WebRtc_UWord32 kTestNumberOfPackets = 450; +const int kTestNumberOfRtxPackets = 49; + +class VerifyingRtxReceiver : public RtpData { + public: + VerifyingRtxReceiver() {} + + virtual WebRtc_Word32 OnReceivedPayloadData( + const WebRtc_UWord8* data, + const WebRtc_UWord16 size, + const webrtc::WebRtcRTPHeader* rtp_header) { + if (!sequence_numbers_.empty()) { + EXPECT_EQ(kTestSsrc, rtp_header->header.ssrc); + } + sequence_numbers_.push_back(rtp_header->header.sequenceNumber); + return 0; + } + std::vector sequence_numbers_; +}; + +class RtxLoopBackTransport : public webrtc::Transport { + public: + explicit RtxLoopBackTransport(uint32_t rtx_ssrc) + : count_(0), + packet_loss_(0), + rtx_ssrc_(rtx_ssrc), + count_rtx_ssrc_(0), + module_(NULL) { + } + void SetSendModule(RtpRtcp* rtpRtcpModule) { + module_ = rtpRtcpModule; + } + void DropEveryNthPacket(int n) { + packet_loss_ = n; + } + virtual int SendPacket(int channel, const void *data, int len) { + count_++; + const unsigned char* ptr = static_cast(data); + uint32_t ssrc = (ptr[8] << 24) + (ptr[9] << 16) + (ptr[10] << 8) + ptr[11]; + if (ssrc == rtx_ssrc_) count_rtx_ssrc_++; + if (packet_loss_ > 0) { + if ((count_ % packet_loss_) == 0) { + return len; + } + } + if (module_->IncomingPacket((const WebRtc_UWord8*)data, len) == 0) { + return len; + } + return -1; + } + virtual int SendRTCPPacket(int channel, const void *data, int len) { + if (module_->IncomingPacket((const WebRtc_UWord8*)data, len) == 0) { + return len; + } + return -1; + } + int count_; + int packet_loss_; + uint32_t rtx_ssrc_; + int count_rtx_ssrc_; + RtpRtcp* module_; +}; + +class RtpRtcpNackTest : public ::testing::Test { + protected: + RtpRtcpNackTest() + : rtp_rtcp_module_(NULL), + transport_(kTestSsrc + 1), + receiver_(), + payload_data_length(sizeof(payload_data)), + fake_clock(123456) {} + ~RtpRtcpNackTest() {} + + virtual void SetUp() { + RtpRtcp::Configuration configuration; + configuration.id = kTestId; + configuration.audio = false; + configuration.clock = &fake_clock; + configuration.incoming_data = &receiver_; + configuration.outgoing_transport = &transport_; + rtp_rtcp_module_ = RtpRtcp::CreateRtpRtcp(configuration); + + EXPECT_EQ(0, rtp_rtcp_module_->SetSSRC(kTestSsrc)); + EXPECT_EQ(0, rtp_rtcp_module_->SetRTCPStatus(kRtcpCompound)); + EXPECT_EQ(0, rtp_rtcp_module_->SetNACKStatus(kNackRtcp, 450)); + EXPECT_EQ(0, rtp_rtcp_module_->SetStorePacketsStatus(true, 600)); + EXPECT_EQ(0, rtp_rtcp_module_->SetSendingStatus(true)); + EXPECT_EQ(0, rtp_rtcp_module_->SetSequenceNumber(kTestSequenceNumber)); + EXPECT_EQ(0, rtp_rtcp_module_->SetStartTimestamp(111111)); + + transport_.SetSendModule(rtp_rtcp_module_); + + VideoCodec video_codec; + memset(&video_codec, 0, sizeof(video_codec)); + video_codec.plType = 123; + memcpy(video_codec.plName, "I420", 5); + + EXPECT_EQ(0, rtp_rtcp_module_->RegisterSendPayload(video_codec)); + EXPECT_EQ(0, rtp_rtcp_module_->RegisterReceivePayload(video_codec)); + + for (int n = 0; n < payload_data_length; n++) { + payload_data[n] = n % 10; + } + } + + virtual void TearDown() { + delete rtp_rtcp_module_; + } + + RtpRtcp* rtp_rtcp_module_; + RtxLoopBackTransport transport_; + VerifyingRtxReceiver receiver_; + WebRtc_UWord8 payload_data[65000]; + int payload_data_length; + SimulatedClock fake_clock; +}; + +TEST_F(RtpRtcpNackTest, RTCP) { + WebRtc_UWord32 timestamp = 3000; + WebRtc_UWord16 nack_list[kVideoNackListSize]; + transport_.DropEveryNthPacket(10); + + for (int frame = 0; frame < 10; ++frame) { + EXPECT_EQ(0, rtp_rtcp_module_->SendOutgoingData(webrtc::kVideoFrameDelta, + 123, + timestamp, + timestamp / 90, + payload_data, + payload_data_length)); + + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + + std::vector missing_sequence_numbers; + std::vector::iterator it = + receiver_.sequence_numbers_.begin(); + + while (it != receiver_.sequence_numbers_.end()) { + WebRtc_UWord16 sequence_number_1 = *it; + ++it; + if (it != receiver_.sequence_numbers_.end()) { + WebRtc_UWord16 sequence_number_2 = *it; + // Add all missing sequence numbers to list. + for (WebRtc_UWord16 i = sequence_number_1 + 1; i < sequence_number_2; + ++i) { + missing_sequence_numbers.push_back(i); + } + } + } + int n = 0; + for (it = missing_sequence_numbers.begin(); + it != missing_sequence_numbers.end(); ++it) { + nack_list[n++] = (*it); + } + rtp_rtcp_module_->SendNACK(nack_list, n); + fake_clock.AdvanceTimeMilliseconds(33); + rtp_rtcp_module_->Process(); + + // Prepare next frame. + timestamp += 3000; + } + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin())); + EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, + *(receiver_.sequence_numbers_.rbegin())); + EXPECT_EQ(kTestNumberOfPackets, receiver_.sequence_numbers_.size()); + EXPECT_EQ(0, transport_.count_rtx_ssrc_); +} + +TEST_F(RtpRtcpNackTest, RTXNack) { + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXReceiveStatus(true, kTestSsrc + 1)); + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXSendStatus(kRtxRetransmitted, + true, kTestSsrc + 1)); + + transport_.DropEveryNthPacket(10); + + WebRtc_UWord32 timestamp = 3000; + WebRtc_UWord16 nack_list[kVideoNackListSize]; + + for (int frame = 0; frame < 10; ++frame) { + EXPECT_EQ(0, rtp_rtcp_module_->SendOutgoingData(webrtc::kVideoFrameDelta, + 123, + timestamp, + timestamp / 90, + payload_data, + payload_data_length)); + + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + + std::vector missing_sequence_numbers; + + + std::vector::iterator it = + receiver_.sequence_numbers_.begin(); + while (it != receiver_.sequence_numbers_.end()) { + int sequence_number_1 = *it; + ++it; + if (it != receiver_.sequence_numbers_.end()) { + int sequence_number_2 = *it; + // Add all missing sequence numbers to list. + for (int i = sequence_number_1 + 1; i < sequence_number_2; ++i) { + missing_sequence_numbers.push_back(i); + } + } + } + int n = 0; + for (it = missing_sequence_numbers.begin(); + it != missing_sequence_numbers.end(); ++it) { + nack_list[n++] = (*it); + } + rtp_rtcp_module_->SendNACK(nack_list, n); + fake_clock.AdvanceTimeMilliseconds(33); + rtp_rtcp_module_->Process(); + + // Prepare next frame. + timestamp += 3000; + } + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin())); + EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, + *(receiver_.sequence_numbers_.rbegin())); + EXPECT_EQ(kTestNumberOfPackets, receiver_.sequence_numbers_.size()); + EXPECT_EQ(kTestNumberOfRtxPackets, transport_.count_rtx_ssrc_); +} + +TEST_F(RtpRtcpNackTest, RTXAllNoLoss) { + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXReceiveStatus(true, kTestSsrc + 1)); + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXSendStatus(kRtxAll, + true, kTestSsrc + 1)); + transport_.DropEveryNthPacket(0); + + WebRtc_UWord32 timestamp = 3000; + + for (int frame = 0; frame < 10; ++frame) { + EXPECT_EQ(0, rtp_rtcp_module_->SendOutgoingData(webrtc::kVideoFrameDelta, + 123, + timestamp, + timestamp / 90, + payload_data, + payload_data_length)); + + fake_clock.AdvanceTimeMilliseconds(33); + rtp_rtcp_module_->Process(); + + // Prepare next frame. + timestamp += 3000; + } + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin())); + EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, + *(receiver_.sequence_numbers_.rbegin())); + // We have transmitted all packets twice, and loss was set to 0. + EXPECT_EQ(kTestNumberOfPackets * 2u, receiver_.sequence_numbers_.size()); + // Half of the packets should be via RTX. + EXPECT_EQ(static_cast(kTestNumberOfPackets), + transport_.count_rtx_ssrc_); +} + +TEST_F(RtpRtcpNackTest, RTXAllWithLoss) { + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXReceiveStatus(true, kTestSsrc + 1)); + EXPECT_EQ(0, rtp_rtcp_module_->SetRTXSendStatus(kRtxAll, + true, + kTestSsrc + 1)); + + int loss = 10; + transport_.DropEveryNthPacket(loss); + + WebRtc_UWord32 timestamp = 3000; + WebRtc_UWord16 nack_list[kVideoNackListSize]; + + for (int frame = 0; frame < 10; ++frame) { + EXPECT_EQ(0, rtp_rtcp_module_->SendOutgoingData(webrtc::kVideoFrameDelta, + 123, + timestamp, + timestamp / 90, + payload_data, + payload_data_length)); + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + std::vector missing_sequence_numbers; + + std::vector::iterator it = + receiver_.sequence_numbers_.begin(); + while (it != receiver_.sequence_numbers_.end()) { + int sequence_number_1 = *it; + ++it; + if (it != receiver_.sequence_numbers_.end()) { + int sequence_number_2 = *it; + // Add all missing sequence numbers to list. + for (int i = sequence_number_1 + 1; i < sequence_number_2; ++i) { + missing_sequence_numbers.push_back(i); + } + } + } + int n = 0; + for (it = missing_sequence_numbers.begin(); + it != missing_sequence_numbers.end(); ++it) { + nack_list[n++] = (*it); + } + if (n > 0) + rtp_rtcp_module_->SendNACK(nack_list, n); + fake_clock.AdvanceTimeMilliseconds(33); + rtp_rtcp_module_->Process(); + + // Prepare next frame. + timestamp += 3000; + } + std::sort(receiver_.sequence_numbers_.begin(), + receiver_.sequence_numbers_.end()); + EXPECT_EQ(kTestSequenceNumber, *(receiver_.sequence_numbers_.begin())); + EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, + *(receiver_.sequence_numbers_.rbegin())); + // Got everything but 10% loss. + EXPECT_EQ(2u * (kTestNumberOfPackets - kTestNumberOfPackets / 10), + receiver_.sequence_numbers_.size()); + EXPECT_EQ(static_cast(kTestNumberOfPackets), + transport_.count_rtx_ssrc_); +} + +} // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index c463f80bf..8626512a4 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -519,16 +519,16 @@ WebRtc_Word32 ModuleRtpRtcpImpl::RemoteCSRCs( } WebRtc_Word32 ModuleRtpRtcpImpl::SetRTXSendStatus( - const bool enable, + const RtxMode mode, const bool set_ssrc, const WebRtc_UWord32 ssrc) { - rtp_sender_.SetRTXStatus(enable, set_ssrc, ssrc); + rtp_sender_.SetRTXStatus(mode, set_ssrc, ssrc); return 0; } -WebRtc_Word32 ModuleRtpRtcpImpl::RTXSendStatus(bool* enable, +WebRtc_Word32 ModuleRtpRtcpImpl::RTXSendStatus(RtxMode* mode, WebRtc_UWord32* ssrc) const { - rtp_sender_.RTXStatus(enable, ssrc); + rtp_sender_.RTXStatus(mode, ssrc); return 0; } diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h index c8283238a..b15b9aa70 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -157,11 +157,11 @@ class ModuleRtpRtcpImpl : public RtpRtcp { virtual WebRtc_UWord32 ByteCountSent() const; - virtual WebRtc_Word32 SetRTXSendStatus(const bool enable, + virtual WebRtc_Word32 SetRTXSendStatus(const RtxMode mode, const bool set_ssrc, const WebRtc_UWord32 ssrc); - virtual WebRtc_Word32 RTXSendStatus(bool* enable, + virtual WebRtc_Word32 RTXSendStatus(RtxMode* mode, WebRtc_UWord32* ssrc) const; // Sends kRtcpByeCode when going from true to false. diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi index b73a0521c..0f600ae91 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi +++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi @@ -25,13 +25,13 @@ '../test/testAPI/test_api.cc', '../test/testAPI/test_api.h', '../test/testAPI/test_api_audio.cc', - '../test/testAPI/test_api_nack.cc', '../test/testAPI/test_api_rtcp.cc', '../test/testAPI/test_api_video.cc', 'mock/mock_rtp_payload_strategy.h', 'mock/mock_rtp_receiver_video.h', 'fec_test_helper.cc', 'fec_test_helper.h', + 'nack_rtx_unittest.cc', 'producer_fec_unittest.cc', 'receiver_fec_unittest.cc', 'rtcp_format_remb_unittest.cc', diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc index 991f12ad6..8ba583b0a 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc @@ -38,15 +38,19 @@ RTPSender::RTPSender(const WebRtc_Word32 id, const bool audio, Clock *clock, // Statistics packets_sent_(0), payload_bytes_sent_(0), start_time_stamp_forced_(false), start_time_stamp_(0), ssrc_db_(*SSRCDatabase::GetSSRCDatabase()), - remote_ssrc_(0), sequence_number_forced_(false), sequence_number_(0), - sequence_number_rtx_(0), ssrc_forced_(false), ssrc_(0), time_stamp_(0), - csrcs_(0), csrc_(), include_csrcs_(true), rtx_(false), ssrc_rtx_(0) { + remote_ssrc_(0), sequence_number_forced_(false), ssrc_forced_(false), + time_stamp_(0), csrcs_(0), csrc_(), include_csrcs_(true), + rtx_(kRtxOff) { memset(nack_byte_count_times_, 0, sizeof(nack_byte_count_times_)); memset(nack_byte_count_, 0, sizeof(nack_byte_count_)); memset(csrc_, 0, sizeof(csrc_)); // We need to seed the random generator. srand(static_cast(clock_->TimeInMilliseconds())); ssrc_ = ssrc_db_.CreateSSRC(); // Can't be 0. + ssrc_rtx_ = ssrc_db_.CreateSSRC(); // Can't be 0. + // Random start, 16 bits. Can't be 0. + sequence_number_rtx_ = static_cast(rand() + 1) & 0x7FFF; + sequence_number_ = static_cast(rand() + 1) & 0x7FFF; if (audio) { audio_ = new RTPSenderAudio(id, clock_, this); @@ -233,11 +237,11 @@ WebRtc_UWord16 RTPSender::MaxPayloadLength() const { WebRtc_UWord16 RTPSender::PacketOverHead() const { return packet_over_head_; } -void RTPSender::SetRTXStatus(const bool enable, const bool set_ssrc, +void RTPSender::SetRTXStatus(const RtxMode mode, const bool set_ssrc, const WebRtc_UWord32 ssrc) { CriticalSectionScoped cs(send_critsect_); - rtx_ = enable; - if (enable) { + rtx_ = mode; + if (rtx_ != kRtxOff) { if (set_ssrc) { ssrc_rtx_ = ssrc; } else { @@ -246,9 +250,9 @@ void RTPSender::SetRTXStatus(const bool enable, const bool set_ssrc, } } -void RTPSender::RTXStatus(bool *enable, WebRtc_UWord32 *SSRC) const { +void RTPSender::RTXStatus(RtxMode* mode, WebRtc_UWord32 *SSRC) const { CriticalSectionScoped cs(send_critsect_); - *enable = rtx_; + *mode = rtx_; *SSRC = ssrc_rtx_; } @@ -439,39 +443,11 @@ WebRtc_Word32 RTPSender::ReSendPacket(WebRtc_UWord16 packet_id, return 0; } WebRtc_UWord8 data_buffer_rtx[IP_PACKET_SIZE]; - if (rtx_) { + if (rtx_ != kRtxOff) { + BuildRtxPacket(data_buffer, &length, data_buffer_rtx); buffer_to_send_ptr = data_buffer_rtx; - - CriticalSectionScoped cs(send_critsect_); - // Add RTX header. - ModuleRTPUtility::RTPHeaderParser rtp_parser( - reinterpret_cast(data_buffer), length); - - WebRtcRTPHeader rtp_header; - rtp_parser.Parse(rtp_header); - - // Add original RTP header. - memcpy(data_buffer_rtx, data_buffer, rtp_header.header.headerLength); - - // Replace sequence number. - WebRtc_UWord8 *ptr = data_buffer_rtx + 2; - ModuleRTPUtility::AssignUWord16ToBuffer(ptr, sequence_number_rtx_++); - - // Replace SSRC. - ptr += 6; - ModuleRTPUtility::AssignUWord32ToBuffer(ptr, ssrc_rtx_); - - // Add OSN (original sequence number). - ptr = data_buffer_rtx + rtp_header.header.headerLength; - ModuleRTPUtility::AssignUWord16ToBuffer(ptr, - rtp_header.header.sequenceNumber); - ptr += 2; - - // Add original payload data. - memcpy(ptr, data_buffer + rtp_header.header.headerLength, - length - rtp_header.header.headerLength); - length += 2; } + WebRtc_Word32 bytes_sent = ReSendToNetwork(buffer_to_send_ptr, length); if (bytes_sent <= 0) { WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, id_, @@ -682,6 +658,21 @@ WebRtc_Word32 RTPSender::SendToNetwork( storage) != 0) { return -1; } + + WebRtc_Word32 bytes_sent = -1; + // Create and send RTX Packet. + if (rtx_ == kRtxAll && storage == kAllowRetransmission) { + WebRtc_UWord16 length_rtx = payload_length + rtp_header_length; + WebRtc_UWord8 data_buffer_rtx[IP_PACKET_SIZE]; + BuildRtxPacket(buffer, &length_rtx, data_buffer_rtx); + if (transport_) { + bytes_sent += transport_->SendPacket(id_, data_buffer_rtx, length_rtx); + if (bytes_sent <= 0) { + return -1; + } + } + } + if (paced_sender_) { if (!paced_sender_->SendPacket( PacedSender::kNormalPriority, rtp_header.header.ssrc, @@ -692,8 +683,8 @@ WebRtc_Word32 RTPSender::SendToNetwork( return payload_length + rtp_header_length; } } - // Send packet. - WebRtc_Word32 bytes_sent = -1; + // Send data packet. + bytes_sent = -1; if (transport_) { bytes_sent = transport_->SendPacket(id_, buffer, payload_length + rtp_header_length); @@ -1191,4 +1182,38 @@ WebRtc_Word32 RTPSender::SetFecParameters( return video_->SetFecParameters(delta_params, key_params); } +void RTPSender::BuildRtxPacket(WebRtc_UWord8* buffer, WebRtc_UWord16* length, + WebRtc_UWord8* buffer_rtx) { + CriticalSectionScoped cs(send_critsect_); + WebRtc_UWord8* data_buffer_rtx = buffer_rtx; + // Add RTX header. + ModuleRTPUtility::RTPHeaderParser rtp_parser( + reinterpret_cast(buffer), *length); + + WebRtcRTPHeader rtp_header; + rtp_parser.Parse(rtp_header); + + // Add original RTP header. + memcpy(data_buffer_rtx, buffer, rtp_header.header.headerLength); + + // Replace sequence number. + WebRtc_UWord8 *ptr = data_buffer_rtx + 2; + ModuleRTPUtility::AssignUWord16ToBuffer(ptr, sequence_number_rtx_++); + + // Replace SSRC. + ptr += 6; + ModuleRTPUtility::AssignUWord32ToBuffer(ptr, ssrc_rtx_); + + // Add OSN (original sequence number). + ptr = data_buffer_rtx + rtp_header.header.headerLength; + ModuleRTPUtility::AssignUWord16ToBuffer(ptr, + rtp_header.header.sequenceNumber); + ptr += 2; + + // Add original payload data. + memcpy(ptr, buffer + rtp_header.header.headerLength, + *length - rtp_header.header.headerLength); + *length += 2; +} + } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.h b/webrtc/modules/rtp_rtcp/source/rtp_sender.h index dc8cf3ad1..b101a3df1 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.h @@ -176,10 +176,10 @@ class RTPSender : public Bitrate, public RTPSenderInterface { bool ProcessNACKBitRate(const WebRtc_UWord32 now); // RTX. - void SetRTXStatus(const bool enable, const bool set_ssrc, + void SetRTXStatus(const RtxMode mode, const bool set_ssrc, const WebRtc_UWord32 SSRC); - void RTXStatus(bool *enable, WebRtc_UWord32 *SSRC) const; + void RTXStatus(RtxMode* mode, WebRtc_UWord32 *SSRC) const; // Functions wrapping RTPSenderInterface. virtual WebRtc_Word32 BuildRTPheader( @@ -263,6 +263,9 @@ class RTPSender : public Bitrate, public RTPSenderInterface { WebRtc_UWord32 capture_timestamp, int64_t capture_time_ms); + void BuildRtxPacket(WebRtc_UWord8* buffer, WebRtc_UWord16* length, + WebRtc_UWord8* buffer_rtx); + WebRtc_Word32 id_; const bool audio_configured_; RTPSenderAudio *audio_; @@ -309,7 +312,7 @@ class RTPSender : public Bitrate, public RTPSenderInterface { WebRtc_UWord8 csrcs_; WebRtc_UWord32 csrc_[kRtpCsrcSize]; bool include_csrcs_; - bool rtx_; + RtxMode rtx_; WebRtc_UWord32 ssrc_rtx_; }; diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc index aa822195c..fb1b337ec 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.cc @@ -8,15 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "webrtc/modules/rtp_rtcp/test/testAPI/test_api.h" + #include #include -#include - -#include "test_api.h" - -#include "common_types.h" -#include "rtp_rtcp.h" -#include "rtp_rtcp_defines.h" using namespace webrtc; @@ -112,3 +107,28 @@ TEST_F(RtpRtcpAPITest, RTCP) { EXPECT_EQ(0, module->SetNACKStatus(kNackRtcp, 450)); EXPECT_EQ(kNackRtcp, module->NACK()); } + +TEST_F(RtpRtcpAPITest, RTXSender) { + unsigned int ssrc = 0; + RtxMode rtx_mode = kRtxOff; + EXPECT_EQ(0, module->SetRTXSendStatus(kRtxRetransmitted, true, 1)); + EXPECT_EQ(0, module->RTXSendStatus(&rtx_mode, &ssrc)); + EXPECT_EQ(kRtxRetransmitted, rtx_mode); + EXPECT_EQ(1u, ssrc); + rtx_mode = kRtxOff; + EXPECT_EQ(0, module->SetRTXSendStatus(kRtxOff, true, 0)); + EXPECT_EQ(0, module->RTXSendStatus(&rtx_mode, &ssrc)); + EXPECT_EQ(kRtxOff, rtx_mode); +} + +TEST_F(RtpRtcpAPITest, RTXReceiver) { + bool enable = false; + unsigned int ssrc = 0; + EXPECT_EQ(0, module->SetRTXReceiveStatus(true, 1)); + EXPECT_EQ(0, module->RTXReceiveStatus(&enable, &ssrc)); + EXPECT_TRUE(enable); + EXPECT_EQ(1u, ssrc); + EXPECT_EQ(0, module->SetRTXReceiveStatus(false, 0)); + EXPECT_EQ(0, module->RTXReceiveStatus(&enable, &ssrc)); + EXPECT_FALSE(enable); +} diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.h b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.h index b3a30a2a1..d3698d4c9 100644 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api.h +++ b/webrtc/modules/rtp_rtcp/test/testAPI/test_api.h @@ -8,9 +8,10 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "common_types.h" -#include "rtp_rtcp.h" -#include "rtp_rtcp_defines.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/common_types.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" namespace webrtc { @@ -87,4 +88,3 @@ class RtpReceiver : public RtpData { }; } // namespace webrtc - diff --git a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_nack.cc b/webrtc/modules/rtp_rtcp/test/testAPI/test_api_nack.cc deleted file mode 100644 index 695f0f5f1..000000000 --- a/webrtc/modules/rtp_rtcp/test/testAPI/test_api_nack.cc +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include -#include -#include -#include -#include - -#include "test_api.h" - -#include "common_types.h" -#include "rtp_rtcp.h" -#include "rtp_rtcp_defines.h" - -using namespace webrtc; - -const int kVideoNackListSize = 10; -const int kTestId = 123; -const WebRtc_UWord32 kTestSsrc = 3456; -const WebRtc_UWord16 kTestSequenceNumber = 2345; -const WebRtc_UWord32 kTestNumberOfPackets = 450; -const int kTestNumberOfRtxPackets = 49; - -class VerifyingNackReceiver : public RtpData -{ - public: - VerifyingNackReceiver() {} - - virtual WebRtc_Word32 OnReceivedPayloadData( - const WebRtc_UWord8* data, - const WebRtc_UWord16 size, - const webrtc::WebRtcRTPHeader* rtp_header) { - - EXPECT_EQ(kTestSsrc, rtp_header->header.ssrc); - bool already_received = std::find( - sequence_numbers_.begin(), sequence_numbers_.end(), - rtp_header->header.sequenceNumber) != sequence_numbers_.end(); - EXPECT_FALSE(already_received); - sequence_numbers_.push_back(rtp_header->header.sequenceNumber); - return 0; - } - std::list sequence_numbers_; -}; - -class NackLoopBackTransport : public webrtc::Transport { - public: - NackLoopBackTransport(uint32_t rtx_ssrc) - : count_(0), - packet_loss_(0), - consecutive_drop_start_(0), - consecutive_drop_end_(0), - rtx_ssrc_(rtx_ssrc), - count_rtx_ssrc_(0), - module_(NULL) { - } - void SetSendModule(RtpRtcp* rtpRtcpModule) { - module_ = rtpRtcpModule; - } - void DropEveryNthPacket(int n) { - packet_loss_ = n; - consecutive_drop_start_ = 0; - consecutive_drop_end_ = 0; - } - void DropConsecutivePackets(int start, int total) { - consecutive_drop_start_ = start; - consecutive_drop_end_ = start + total; - packet_loss_ = 0; - } - virtual int SendPacket(int channel, const void *data, int len) { - count_++; - const unsigned char* ptr = static_cast(data); - uint32_t ssrc = (ptr[8] << 24) + (ptr[9] << 16) + (ptr[10] << 8) + ptr[11]; - if (ssrc == rtx_ssrc_) count_rtx_ssrc_++; - uint16_t sequence_number = (ptr[2] << 8) + ptr[3]; - expected_sequence_numbers_.insert(expected_sequence_numbers_.end(), - sequence_number); - - if (packet_loss_ > 0) { - if ((count_ % packet_loss_) == 0) { - return len; - } - } else if (count_ >= consecutive_drop_start_ && - count_ < consecutive_drop_end_) { - return len; - } - if (module_->IncomingPacket((const WebRtc_UWord8*)data, len) == 0) { - return len; - } - return -1; - } - virtual int SendRTCPPacket(int channel, const void *data, int len) { - if (module_->IncomingPacket((const WebRtc_UWord8*)data, len) == 0) { - return len; - } - return -1; - } - int count_; - int packet_loss_; - int consecutive_drop_start_; - int consecutive_drop_end_; - uint32_t rtx_ssrc_; - int count_rtx_ssrc_; - RtpRtcp* module_; - std::set expected_sequence_numbers_; -}; - -class RtpRtcpNackTest : public ::testing::Test { - protected: - RtpRtcpNackTest() - : video_module_(NULL), - transport_(NULL), - nack_receiver_(NULL), - payload_data_length(sizeof(payload_data)), - fake_clock(123456) {} - ~RtpRtcpNackTest() {} - - virtual void SetUp() { - transport_ = new NackLoopBackTransport(kTestSsrc + 1); - nack_receiver_ = new VerifyingNackReceiver(); - - RtpRtcp::Configuration configuration; - configuration.id = kTestId; - configuration.audio = false; - configuration.clock = &fake_clock; - configuration.incoming_data = nack_receiver_; - configuration.outgoing_transport = transport_; - video_module_ = RtpRtcp::CreateRtpRtcp(configuration); - - EXPECT_EQ(0, video_module_->SetRTCPStatus(kRtcpCompound)); - EXPECT_EQ(0, video_module_->SetSSRC(kTestSsrc)); - EXPECT_EQ(0, video_module_->SetNACKStatus(kNackRtcp, 450)); - EXPECT_EQ(0, video_module_->SetStorePacketsStatus(true, 600)); - EXPECT_EQ(0, video_module_->SetSendingStatus(true)); - EXPECT_EQ(0, video_module_->SetSequenceNumber(kTestSequenceNumber)); - EXPECT_EQ(0, video_module_->SetStartTimestamp(111111)); - - transport_->SetSendModule(video_module_); - - VideoCodec video_codec; - memset(&video_codec, 0, sizeof(video_codec)); - video_codec.plType = 123; - memcpy(video_codec.plName, "I420", 5); - - EXPECT_EQ(0, video_module_->RegisterSendPayload(video_codec)); - EXPECT_EQ(0, video_module_->RegisterReceivePayload(video_codec)); - - for (int n = 0; n < payload_data_length; n++) { - payload_data[n] = n % 10; - } - } - - virtual void TearDown() { - delete video_module_; - delete transport_; - delete nack_receiver_; - } - - int BuildNackList(uint16_t* nack_list) const { - nack_receiver_->sequence_numbers_.sort(); - - std::list missing_sequence_numbers; - std::list::iterator it = - nack_receiver_->sequence_numbers_.begin(); - - while (it != nack_receiver_->sequence_numbers_.end()) { - WebRtc_UWord16 sequence_number_1 = *it; - ++it; - if (it != nack_receiver_->sequence_numbers_.end()) { - WebRtc_UWord16 sequence_number_2 = *it; - // Add all missing sequence numbers to list - for (WebRtc_UWord16 i = sequence_number_1 + 1; i < sequence_number_2; - ++i) { - missing_sequence_numbers.push_back(i); - } - } - } - int n = 0; - for (it = missing_sequence_numbers.begin(); - it != missing_sequence_numbers.end(); ++it) { - nack_list[n++] = (*it); - } - return n; - } - - bool ExpectedPacketsReceived() { - std::list received_sorted; - std::copy(nack_receiver_->sequence_numbers_.begin(), - nack_receiver_->sequence_numbers_.end(), - std::back_inserter(received_sorted)); - received_sorted.sort(); - return std::equal(received_sorted.begin(), received_sorted.end(), - transport_->expected_sequence_numbers_.begin()); - } - - RtpRtcp* video_module_; - NackLoopBackTransport* transport_; - VerifyingNackReceiver* nack_receiver_; - WebRtc_UWord8 payload_data[65000]; - int payload_data_length; - SimulatedClock fake_clock; -}; - -TEST_F(RtpRtcpNackTest, RTCP) { - WebRtc_UWord32 timestamp = 3000; - uint16_t nack_list[kVideoNackListSize]; - transport_->DropEveryNthPacket(10); - - for (int frame = 0; frame < 10; ++frame) { - EXPECT_EQ(0, video_module_->SendOutgoingData(webrtc::kVideoFrameDelta, 123, - timestamp, - timestamp / 90, - payload_data, - payload_data_length)); - - int length = BuildNackList(nack_list); - video_module_->SendNACK(nack_list, length); - fake_clock.AdvanceTimeMilliseconds(33); - video_module_->Process(); - - // Prepare next frame. - timestamp += 3000; - } - nack_receiver_->sequence_numbers_.sort(); - EXPECT_EQ(kTestSequenceNumber, *(nack_receiver_->sequence_numbers_.begin())); - EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, - *(nack_receiver_->sequence_numbers_.rbegin())); - EXPECT_EQ(kTestNumberOfPackets, nack_receiver_->sequence_numbers_.size()); - EXPECT_EQ(0, transport_->count_rtx_ssrc_); -} - -TEST_F(RtpRtcpNackTest, LongNackList) { - const int kNumPacketsToDrop = 900; - const int kNumFrames = 30; - const int kNumRequiredRtcp = 4; - WebRtc_UWord32 timestamp = 3000; - uint16_t nack_list[kNumPacketsToDrop]; - // Disable StorePackets to be able to set a larger packet history. - EXPECT_EQ(0, video_module_->SetStorePacketsStatus(false, 0)); - // Enable StorePackets with a packet history of 2000 packets. - EXPECT_EQ(0, video_module_->SetStorePacketsStatus(true, 2000)); - // Drop 900 packets from the second one so that we get a NACK list which is - // big enough to require 4 RTCP packets to be fully transmitted to the sender. - transport_->DropConsecutivePackets(2, kNumPacketsToDrop); - // Send 30 frames which at the default size is roughly what we need to get - // enough packets. - for (int frame = 0; frame < kNumFrames; ++frame) { - EXPECT_EQ(0, video_module_->SendOutgoingData(webrtc::kVideoFrameDelta, 123, - timestamp, - timestamp / 90, - payload_data, - payload_data_length)); - // Prepare next frame. - timestamp += 3000; - fake_clock.AdvanceTimeMilliseconds(33); - video_module_->Process(); - } - EXPECT_FALSE(transport_->expected_sequence_numbers_.empty()); - EXPECT_FALSE(nack_receiver_->sequence_numbers_.empty()); - size_t last_receive_count = nack_receiver_->sequence_numbers_.size(); - int length = BuildNackList(nack_list); - for (int i = 0; i < kNumRequiredRtcp - 1; ++i) { - video_module_->SendNACK(nack_list, length); - EXPECT_GT(nack_receiver_->sequence_numbers_.size(), last_receive_count); - last_receive_count = nack_receiver_->sequence_numbers_.size(); - EXPECT_FALSE(ExpectedPacketsReceived()); - } - video_module_->SendNACK(nack_list, length); - EXPECT_GT(nack_receiver_->sequence_numbers_.size(), last_receive_count); - EXPECT_TRUE(ExpectedPacketsReceived()); -} - -TEST_F(RtpRtcpNackTest, RTX) { - EXPECT_EQ(0, video_module_->SetRTXReceiveStatus(true, kTestSsrc + 1)); - EXPECT_EQ(0, video_module_->SetRTXSendStatus(true, true, kTestSsrc + 1)); - - transport_->DropEveryNthPacket(10); - - WebRtc_UWord32 timestamp = 3000; - WebRtc_UWord16 nack_list[kVideoNackListSize]; - - for (int frame = 0; frame < 10; ++frame) { - EXPECT_EQ(0, video_module_->SendOutgoingData(webrtc::kVideoFrameDelta, - 123, - timestamp, - timestamp / 90, - payload_data, - payload_data_length)); - - nack_receiver_->sequence_numbers_.sort(); - - std::list missing_sequence_numbers; - - - std::list::iterator it = - nack_receiver_->sequence_numbers_.begin(); - while (it != nack_receiver_->sequence_numbers_.end()) { - int sequence_number_1 = *it; - ++it; - if (it != nack_receiver_->sequence_numbers_.end()) { - int sequence_number_2 = *it; - // Add all missing sequence numbers to list. - for (int i = sequence_number_1 + 1; i < sequence_number_2; ++i) { - missing_sequence_numbers.push_back(i); - } - } - } - int n = 0; - for (it = missing_sequence_numbers.begin(); - it != missing_sequence_numbers.end(); ++it) { - nack_list[n++] = (*it); - } - video_module_->SendNACK(nack_list, n); - fake_clock.AdvanceTimeMilliseconds(33); - video_module_->Process(); - - // Prepare next frame. - timestamp += 3000; - } - nack_receiver_->sequence_numbers_.sort(); - EXPECT_EQ(kTestSequenceNumber, *(nack_receiver_->sequence_numbers_.begin())); - EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, - *(nack_receiver_->sequence_numbers_.rbegin())); - EXPECT_EQ(kTestNumberOfPackets, nack_receiver_->sequence_numbers_.size()); - EXPECT_EQ(kTestNumberOfRtxPackets, transport_->count_rtx_ssrc_); -} diff --git a/webrtc/video_engine/vie_channel.cc b/webrtc/video_engine/vie_channel.cc index a93530ad9..b05601456 100644 --- a/webrtc/video_engine/vie_channel.cc +++ b/webrtc/video_engine/vie_channel.cc @@ -915,7 +915,7 @@ WebRtc_Word32 ViEChannel::SetSSRC(const WebRtc_UWord32 SSRC, } RtpRtcp* rtp_rtcp = *it; if (usage == kViEStreamTypeRtx) { - return rtp_rtcp->SetRTXSendStatus(true, true, SSRC); + return rtp_rtcp->SetRTXSendStatus(kRtxRetransmitted, true, SSRC); } return rtp_rtcp->SetSSRC(SSRC); }