diff --git a/src/modules/rtp_rtcp/interface/rtp_rtcp.h b/src/modules/rtp_rtcp/interface/rtp_rtcp.h index d3257be001..afb0b8412b 100644 --- a/src/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/src/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -437,6 +437,13 @@ public: virtual WebRtc_Word32 DeregisterSendRtpHeaderExtension( const RTPExtensionType type) = 0; + /* + * Enable/disable traffic smoothing of sending stream. + */ + virtual void SetTransmissionSmoothingStatus(const bool enable) = 0; + + virtual bool TransmissionSmoothingStatus() const = 0; + /* * get start timestamp */ diff --git a/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h b/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h index 67884999e1..d154cfd15f 100644 --- a/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h +++ b/src/modules/rtp_rtcp/interface/rtp_rtcp_defines.h @@ -38,6 +38,12 @@ enum RTPAliveType kRtpAlive = 2 }; +enum StorageType { + kDontStore, + kDontRetransmit, + kAllowRetransmission +}; + enum RTPExtensionType { kRtpExtensionNone, diff --git a/src/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/src/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h index dea883867b..7f6ad86697 100644 --- a/src/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h +++ b/src/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -96,6 +96,10 @@ class MockRtpRtcp : public RtpRtcp { WebRtc_Word32(const RTPExtensionType type, const WebRtc_UWord8 id)); MOCK_METHOD1(DeregisterSendRtpHeaderExtension, WebRtc_Word32(const RTPExtensionType type)); + MOCK_METHOD1(SetTransmissionSmoothingStatus, + void(const bool enable)); + MOCK_CONST_METHOD0(TransmissionSmoothingStatus, + bool()); MOCK_CONST_METHOD0(StartTimestamp, WebRtc_UWord32()); MOCK_METHOD1(SetStartTimestamp, diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension.cc b/src/modules/rtp_rtcp/source/rtp_header_extension.cc index d45330c919..07d82560b2 100644 --- a/src/modules/rtp_rtcp/source/rtp_header_extension.cc +++ b/src/modules/rtp_rtcp/source/rtp_header_extension.cc @@ -32,8 +32,8 @@ void RtpHeaderExtensionMap::Erase() { } } -WebRtc_Word32 RtpHeaderExtensionMap::Register(const RTPExtensionType type, - const WebRtc_UWord8 id) { +int32_t RtpHeaderExtensionMap::Register(const RTPExtensionType type, + const uint8_t id) { if (id < 1 || id > 14) { return -1; } @@ -46,8 +46,8 @@ WebRtc_Word32 RtpHeaderExtensionMap::Register(const RTPExtensionType type, return 0; } -WebRtc_Word32 RtpHeaderExtensionMap::Deregister(const RTPExtensionType type) { - WebRtc_UWord8 id; +int32_t RtpHeaderExtensionMap::Deregister(const RTPExtensionType type) { + uint8_t id; if (GetId(type, &id) != 0) { return -1; } @@ -61,8 +61,8 @@ WebRtc_Word32 RtpHeaderExtensionMap::Deregister(const RTPExtensionType type) { return 0; } -WebRtc_Word32 RtpHeaderExtensionMap::GetType(const WebRtc_UWord8 id, - RTPExtensionType* type) const { +int32_t RtpHeaderExtensionMap::GetType(const uint8_t id, + RTPExtensionType* type) const { assert(type); MapItem* item = extensionMap_.Find(id); if (item == NULL) { @@ -73,8 +73,8 @@ WebRtc_Word32 RtpHeaderExtensionMap::GetType(const WebRtc_UWord8 id, return 0; } -WebRtc_Word32 RtpHeaderExtensionMap::GetId(const RTPExtensionType type, - WebRtc_UWord8* id) const { +int32_t RtpHeaderExtensionMap::GetId(const RTPExtensionType type, + uint8_t* id) const { assert(id); MapItem* item = extensionMap_.First(); while (item != NULL) { @@ -88,10 +88,9 @@ WebRtc_Word32 RtpHeaderExtensionMap::GetId(const RTPExtensionType type, return -1; } -WebRtc_UWord16 RtpHeaderExtensionMap::GetTotalLengthInBytes() const -{ +uint16_t RtpHeaderExtensionMap::GetTotalLengthInBytes() const { // Get length for each extension block. - WebRtc_UWord16 length = 0; + uint16_t length = 0; MapItem* item = extensionMap_.First(); while (item != NULL) { HeaderExtension* extension = (HeaderExtension*)item->GetItem(); @@ -105,7 +104,29 @@ WebRtc_UWord16 RtpHeaderExtensionMap::GetTotalLengthInBytes() const return length; } -WebRtc_Word32 RtpHeaderExtensionMap::Size() const { +int32_t RtpHeaderExtensionMap::GetLengthUntilBlockStartInBytes( + const RTPExtensionType type) const { + uint8_t id; + if (GetId(type, &id) != 0) { + // Not registered. + return -1; + } + // Get length until start of extension block type. + uint16_t length = RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES; + MapItem* item = extensionMap_.First(); + while (item != NULL) { + HeaderExtension* extension = (HeaderExtension*)item->GetItem(); + if (extension->type == type) { + break; + } else { + length += extension->length; + } + item = extensionMap_.Next(item); + } + return length; +} + +int32_t RtpHeaderExtensionMap::Size() const { return extensionMap_.Size(); } @@ -118,9 +139,8 @@ RTPExtensionType RtpHeaderExtensionMap::First() const { return extension->type; } -RTPExtensionType RtpHeaderExtensionMap::Next(RTPExtensionType type) const -{ - WebRtc_UWord8 id; +RTPExtensionType RtpHeaderExtensionMap::Next(RTPExtensionType type) const { + uint8_t id; if (GetId(type, &id) != 0) { return kRtpExtensionNone; } diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension.h b/src/modules/rtp_rtcp/source/rtp_header_extension.h index 26c7de78dc..17afdc5083 100644 --- a/src/modules/rtp_rtcp/source/rtp_header_extension.h +++ b/src/modules/rtp_rtcp/source/rtp_header_extension.h @@ -11,6 +11,8 @@ #ifndef WEBRTC_MODULES_RTP_RTCP_RTP_HEADER_EXTENSION_H_ #define WEBRTC_MODULES_RTP_RTCP_RTP_HEADER_EXTENSION_H_ +#include + #include "map_wrapper.h" #include "rtp_rtcp_defines.h" #include "typedefs.h" @@ -34,7 +36,7 @@ struct HeaderExtension { } const RTPExtensionType type; - WebRtc_UWord8 length; + uint8_t length; }; class RtpHeaderExtensionMap { @@ -44,19 +46,21 @@ class RtpHeaderExtensionMap { void Erase(); - WebRtc_Word32 Register(const RTPExtensionType type, const WebRtc_UWord8 id); + int32_t Register(const RTPExtensionType type, const uint8_t id); - WebRtc_Word32 Deregister(const RTPExtensionType type); + int32_t Deregister(const RTPExtensionType type); - WebRtc_Word32 GetType(const WebRtc_UWord8 id, RTPExtensionType* type) const; + int32_t GetType(const uint8_t id, RTPExtensionType* type) const; - WebRtc_Word32 GetId(const RTPExtensionType type, WebRtc_UWord8* id) const; + int32_t GetId(const RTPExtensionType type, uint8_t* id) const; - WebRtc_UWord16 GetTotalLengthInBytes() const; + uint16_t GetTotalLengthInBytes() const; + + int32_t GetLengthUntilBlockStartInBytes(const RTPExtensionType type) const; void GetCopy(RtpHeaderExtensionMap* map) const; - WebRtc_Word32 Size() const; + int32_t Size() const; RTPExtensionType First() const; diff --git a/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc b/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc index 3da3b96717..003e92cc55 100644 --- a/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc +++ b/src/modules/rtp_rtcp/source/rtp_header_extension_test.cc @@ -54,7 +54,7 @@ TEST_F(RtpHeaderExtensionTest, NonUniqueId) { EXPECT_EQ(-1, map_.Register(kRtpExtensionTransmissionTimeOffset, kId)); } -TEST_F(RtpHeaderExtensionTest, GetLength) { +TEST_F(RtpHeaderExtensionTest, GetTotalLength) { EXPECT_EQ(0, map_.GetTotalLengthInBytes()); EXPECT_EQ(0, map_.Register(kRtpExtensionTransmissionTimeOffset, kId)); EXPECT_EQ(RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES + @@ -62,6 +62,15 @@ TEST_F(RtpHeaderExtensionTest, GetLength) { map_.GetTotalLengthInBytes()); } +TEST_F(RtpHeaderExtensionTest, GetLengthUntilBlockStart) { + EXPECT_EQ(-1, map_.GetLengthUntilBlockStartInBytes( + kRtpExtensionTransmissionTimeOffset)); + EXPECT_EQ(0, map_.Register(kRtpExtensionTransmissionTimeOffset, kId)); + EXPECT_EQ(RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES, + map_.GetLengthUntilBlockStartInBytes( + kRtpExtensionTransmissionTimeOffset)); +} + TEST_F(RtpHeaderExtensionTest, GetType) { RTPExtensionType typeOut; EXPECT_EQ(-1, map_.GetType(kId, &typeOut)); @@ -72,7 +81,7 @@ TEST_F(RtpHeaderExtensionTest, GetType) { } TEST_F(RtpHeaderExtensionTest, GetId) { - WebRtc_UWord8 idOut; + uint8_t idOut; EXPECT_EQ(-1, map_.GetId(kRtpExtensionTransmissionTimeOffset, &idOut)); EXPECT_EQ(0, map_.Register(kRtpExtensionTransmissionTimeOffset, kId)); diff --git a/src/modules/rtp_rtcp/source/rtp_packet_history.cc b/src/modules/rtp_rtcp/source/rtp_packet_history.cc new file mode 100644 index 0000000000..a118162db0 --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_packet_history.cc @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2011 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. + */ + +/* + * Class for storing RTP packets. + */ + +#include "rtp_packet_history.h" + +#include +#include // memset + +#include "critical_section_wrapper.h" +#include "rtp_utility.h" +#include "trace.h" + +namespace webrtc { + +RTPPacketHistory::RTPPacketHistory(RtpRtcpClock* clock) + : clock_(*clock), + critsect_(CriticalSectionWrapper::CreateCriticalSection()), + store_(false), + prev_index_(0), + max_packet_length_(0) { +} + +RTPPacketHistory::~RTPPacketHistory() { + Free(); + delete critsect_; +} + +void RTPPacketHistory::SetStorePacketsStatus(bool enable, + uint16_t number_to_store) { + if (enable) { + Allocate(number_to_store); + } else { + Free(); + } +} + +void RTPPacketHistory::Allocate(uint16_t number_to_store) { + assert(number_to_store > 0); + webrtc::CriticalSectionScoped cs(*critsect_); + if (store_) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "SetStorePacketsStatus already set, number: %d", number_to_store); + return; + } + + store_ = true; + stored_packets_.resize(number_to_store); + stored_seq_nums_.resize(number_to_store); + stored_lengths_.resize(number_to_store); + stored_times_.resize(number_to_store); + stored_resend_times_.resize(number_to_store); + stored_types_.resize(number_to_store); +} + +void RTPPacketHistory::Free() { + webrtc::CriticalSectionScoped cs(*critsect_); + if (!store_) { + return; + } + + std::vector >::iterator it; + for (it = stored_packets_.begin(); it != stored_packets_.end(); ++it) { + it->clear(); + } + + stored_packets_.clear(); + stored_seq_nums_.clear(); + stored_lengths_.clear(); + stored_times_.clear(); + stored_resend_times_.clear(); + stored_types_.clear(); + + store_ = false; + prev_index_ = 0; + max_packet_length_ = 0; +} + +bool RTPPacketHistory::StorePackets() const { + webrtc::CriticalSectionScoped cs(*critsect_); + return store_; +} + +// private, lock should already be taken +void RTPPacketHistory::VerifyAndAllocatePacketLength(uint16_t packet_length) { + assert(packet_length > 0); + if (!store_) { + return; + } + + if (packet_length <= max_packet_length_) { + return; + } + + std::vector >::iterator it; + for (it = stored_packets_.begin(); it != stored_packets_.end(); ++it) { + it->resize(packet_length); + } + max_packet_length_ = packet_length; +} + +int32_t RTPPacketHistory::PutRTPPacket(const uint8_t* packet, + uint16_t packet_length, + uint16_t max_packet_length, + StorageType type) { + if (type == kDontStore) { + return 0; + } + + webrtc::CriticalSectionScoped cs(*critsect_); + if (!store_) { + return 0; + } + + assert(packet); + assert(packet_length > 3); + + VerifyAndAllocatePacketLength(max_packet_length); + + if (packet_length > max_packet_length_) { + WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, -1, + "Failed to store RTP packet, length: %d", packet_length); + return -1; + } + + const uint16_t seq_num = (packet[2] << 8) + packet[3]; + + // Store packet + std::vector >::iterator it = + stored_packets_.begin() + prev_index_; + copy(packet, packet + packet_length, it->begin()); + + stored_seq_nums_[prev_index_] = seq_num; + stored_lengths_[prev_index_] = packet_length; + stored_times_[prev_index_] = clock_.GetTimeInMS(); + stored_resend_times_[prev_index_] = 0; // packet not resent + stored_types_[prev_index_] = type; + + ++prev_index_; + if (prev_index_ >= stored_seq_nums_.size()) { + prev_index_ = 0; + } + return 0; +} + +bool RTPPacketHistory::HasRTPPacket(uint16_t sequence_number) const { + webrtc::CriticalSectionScoped cs(*critsect_); + if (!store_) { + return false; + } + + int32_t index = 0; + bool found = FindSeqNum(sequence_number, &index); + if (!found) { + return false; + } + + uint16_t length = stored_lengths_.at(index); + if (length == 0 || length > max_packet_length_) { + // Invalid length. + return false; + } + return true; +} + +bool RTPPacketHistory::GetRTPPacket(uint16_t sequence_number, + uint32_t min_elapsed_time_ms, + uint8_t* packet, + uint16_t* packet_length, + uint32_t* stored_time_ms, + StorageType* type) const { + webrtc::CriticalSectionScoped cs(*critsect_); + if (!store_) { + return false; + } + + int32_t index = 0; + bool found = FindSeqNum(sequence_number, &index); + if (!found) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, + "No match for getting seqNum %u", sequence_number); + return false; + } + + uint16_t length = stored_lengths_.at(index); + if (length == 0 || length > max_packet_length_) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, + "No match for getting seqNum %u, len %d", sequence_number, length); + return false; + } + + if (length > *packet_length) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Input buffer too short for packet %u", sequence_number); + return false; + } + + // Verify elapsed time since last retrieve. + uint32_t now = clock_.GetTimeInMS(); + if (min_elapsed_time_ms > 0 && + ((now - stored_resend_times_.at(index)) < min_elapsed_time_ms)) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, + "Skip getting packet %u, packet recently resent.", sequence_number); + *packet_length = 0; + return true; + } + + // Get packet. + std::vector >::const_iterator it_found_packet = + stored_packets_.begin() + index; + copy(it_found_packet->begin(), it_found_packet->begin() + length, packet); + *packet_length = stored_lengths_.at(index); + *stored_time_ms = stored_times_.at(index); + *type = stored_types_.at(index); + return true; +} + +void RTPPacketHistory::UpdateResendTime(uint16_t sequence_number) { + webrtc::CriticalSectionScoped cs(*critsect_); + if (!store_) { + return; + } + + int32_t index = 0; + bool found = FindSeqNum(sequence_number, &index); + if (!found) { + WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, -1, + "Failed to update resend time, seq num: %u.", sequence_number); + return; + } + stored_resend_times_[index] = clock_.GetTimeInMS(); +} + +// private, lock should already be taken +bool RTPPacketHistory::FindSeqNum(uint16_t sequence_number, + int32_t* index) const { + uint16_t temp_sequence_number = 0; + if (prev_index_ > 0) { + *index = prev_index_ - 1; + temp_sequence_number = stored_seq_nums_[*index]; + } else { + *index = stored_seq_nums_.size() - 1; + temp_sequence_number = stored_seq_nums_[*index]; // wrap + } + + int32_t idx = (prev_index_ - 1) - (temp_sequence_number - sequence_number); + if (idx >= 0 && idx < static_cast(stored_seq_nums_.size())) { + *index = idx; + temp_sequence_number = stored_seq_nums_[*index]; + } + + if (temp_sequence_number != sequence_number) { + // We did not found a match, search all. + for (uint16_t m = 0; m < stored_seq_nums_.size(); m++) { + if (stored_seq_nums_[m] == sequence_number) { + *index = m; + temp_sequence_number = stored_seq_nums_[*index]; + break; + } + } + } + if (temp_sequence_number == sequence_number) { + // We found a match. + return true; + } + return false; +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_packet_history.h b/src/modules/rtp_rtcp/source/rtp_packet_history.h new file mode 100644 index 0000000000..49db5b6b2b --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_packet_history.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2011 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. + */ + +/* + * Class for storing RTP packets. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_RTP_PACKET_HISTORY_H_ +#define WEBRTC_MODULES_RTP_RTCP_RTP_PACKET_HISTORY_H_ + +#include +#include + +#include "module_common_types.h" +#include "rtp_rtcp_defines.h" + +namespace webrtc { + +class RtpRtcpClock; +class CriticalSectionWrapper; + +class RTPPacketHistory { + public: + RTPPacketHistory(RtpRtcpClock* clock); + ~RTPPacketHistory(); + + void SetStorePacketsStatus(bool enable, uint16_t number_to_store); + + bool StorePackets() const; + + // Stores RTP packet. + int32_t PutRTPPacket(const uint8_t* packet, + uint16_t packet_length, + uint16_t max_packet_length, + StorageType type); + + // Gets stored RTP packet corresponding to the input sequence number. + // The packet is copied to the buffer pointed to by ptr_rtp_packet. + // The rtp_packet_length should show the available buffer size. + // Returns true if packet is found. + // rtp_packet_length: returns the copied packet length on success. + // min_elapsed_time_ms: the minimum time that must have elapsed since the last + // time the packet was resent (parameter is ignored if set to zero). + // If the packet is found but the minimum time has not elaped, no bytes are + // copied. + // stored_time_ms: returns the time when the packet was stored. + // type: returns the storage type set in PutRTPPacket. + bool GetRTPPacket(uint16_t sequence_number, + uint32_t min_elapsed_time_ms, + uint8_t* packet, + uint16_t* packet_length, + uint32_t* stored_time_ms, + StorageType* type) const; + + bool HasRTPPacket(uint16_t sequence_number) const; + + void UpdateResendTime(uint16_t sequence_number); + + private: + void Allocate(uint16_t number_to_store); + void Free(); + void VerifyAndAllocatePacketLength(uint16_t packet_length); + bool FindSeqNum(uint16_t sequence_number, int32_t* index) const; + + private: + RtpRtcpClock& clock_; + CriticalSectionWrapper* critsect_; + bool store_; + uint32_t prev_index_; + uint16_t max_packet_length_; + + std::vector > stored_packets_; + std::vector stored_seq_nums_; + std::vector stored_lengths_; + std::vector stored_times_; + std::vector stored_resend_times_; + std::vector stored_types_; +}; +} // namespace webrtc +#endif // WEBRTC_MODULES_RTP_RTCP_RTP_PACKET_HISTORY_H_ diff --git a/src/modules/rtp_rtcp/source/rtp_packet_history_test.cc b/src/modules/rtp_rtcp/source/rtp_packet_history_test.cc new file mode 100644 index 0000000000..0fbb408eed --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_packet_history_test.cc @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2011 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. + */ + +/* + * This file includes unit tests for the RTPPacketHistory. + */ + +#include +#include + +#include "rtp_packet_history.h" +#include "rtp_rtcp_defines.h" +#include "rtp_utility.h" +#include "typedefs.h" + +namespace webrtc { + +class FakeClock : public RtpRtcpClock { + public: + FakeClock() { + time_in_ms_ = 123456; + } + // Return a timestamp in milliseconds relative to some arbitrary + // source; the source is fixed for this clock. + virtual WebRtc_UWord32 GetTimeInMS() { + return time_in_ms_; + } + // Retrieve an NTP absolute timestamp. + virtual void CurrentNTP(WebRtc_UWord32& secs, WebRtc_UWord32& frac) { + secs = time_in_ms_ / 1000; + frac = (time_in_ms_ % 1000) * 4294967; + } + void IncrementTime(WebRtc_UWord32 time_increment_ms) { + time_in_ms_ += time_increment_ms; + } + private: + WebRtc_UWord32 time_in_ms_; +}; + +class RtpPacketHistoryTest : public ::testing::Test { + protected: + RtpPacketHistoryTest() + : hist_(new RTPPacketHistory(&fake_clock_)) { + } + ~RtpPacketHistoryTest() { + delete hist_; + } + + FakeClock fake_clock_; + RTPPacketHistory* hist_; + enum {kPayload = 127}; + enum {kSsrc = 12345678}; + enum {kSeqNum = 88}; + enum {kTimestamp = 127}; + enum {kMaxPacketLength = 1500}; + uint8_t packet_[kMaxPacketLength]; + uint8_t packet_out_[kMaxPacketLength]; + + void CreateRtpPacket(uint16_t seq_num, uint32_t ssrc, uint8_t payload, + uint32_t timestamp, uint8_t* array, uint16_t* cur_pos) { + array[(*cur_pos)++] = 0x80; + array[(*cur_pos)++] = payload; + array[(*cur_pos)++] = seq_num >> 8; + array[(*cur_pos)++] = seq_num; + array[(*cur_pos)++] = timestamp >> 24; + array[(*cur_pos)++] = timestamp >> 16; + array[(*cur_pos)++] = timestamp >> 8; + array[(*cur_pos)++] = timestamp; + array[(*cur_pos)++] = ssrc >> 24; + array[(*cur_pos)++] = ssrc >> 16; + array[(*cur_pos)++] = ssrc >> 8; + array[(*cur_pos)++] = ssrc; + } +}; + +TEST_F(RtpPacketHistoryTest, SetStoreStatus) { + EXPECT_FALSE(hist_->StorePackets()); + hist_->SetStorePacketsStatus(true, 10); + EXPECT_TRUE(hist_->StorePackets()); + hist_->SetStorePacketsStatus(false, 0); + EXPECT_FALSE(hist_->StorePackets()); +} + +TEST_F(RtpPacketHistoryTest, NoStoreStatus) { + EXPECT_FALSE(hist_->StorePackets()); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kAllowRetransmission)); + // Packet should not be stored. + len = kMaxPacketLength; + uint32_t time; + StorageType type; + EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len, &time, &type)); +} + +TEST_F(RtpPacketHistoryTest, DontStore) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, kDontStore)); + + // Packet should not be stored. + len = kMaxPacketLength; + uint32_t time; + StorageType type; + EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len, &time, &type)); +} + +TEST_F(RtpPacketHistoryTest, PutRtpPacket_TooLargePacketLength) { + hist_->SetStorePacketsStatus(true, 10); + EXPECT_EQ(-1, hist_->PutRTPPacket(packet_, + kMaxPacketLength + 1, + kMaxPacketLength, + kAllowRetransmission)); +} + +TEST_F(RtpPacketHistoryTest, GetRtpPacket_TooSmallBuffer) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kAllowRetransmission)); + uint16_t len_out = len - 1; + uint32_t time; + StorageType type; + EXPECT_FALSE(hist_->GetRTPPacket(kSeqNum, 0, packet_, &len_out, &time, + &type)); +} + +TEST_F(RtpPacketHistoryTest, GetRtpPacket_NotStored) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = kMaxPacketLength; + uint32_t time; + StorageType type; + EXPECT_FALSE(hist_->GetRTPPacket(0, 0, packet_, &len, &time, &type)); +} + +TEST_F(RtpPacketHistoryTest, PutRtpPacket) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + + EXPECT_FALSE(hist_->HasRTPPacket(kSeqNum)); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kAllowRetransmission)); + EXPECT_TRUE(hist_->HasRTPPacket(kSeqNum)); +} + +TEST_F(RtpPacketHistoryTest, GetRtpPacket) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kAllowRetransmission)); + + uint16_t len_out = kMaxPacketLength; + uint32_t time; + StorageType type; + EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time, + &type)); + EXPECT_EQ(len, len_out); + EXPECT_EQ(kAllowRetransmission, type); + for (int i = 0; i < len; i++) { + EXPECT_EQ(packet_[i], packet_out_[i]); + } +} + +TEST_F(RtpPacketHistoryTest, DontRetransmit) { + hist_->SetStorePacketsStatus(true, 10); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kDontRetransmit)); + + uint16_t len_out = kMaxPacketLength; + uint32_t time; + StorageType type; + EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 0, packet_out_, &len_out, &time, + &type)); + EXPECT_EQ(len, len_out); + EXPECT_EQ(kDontRetransmit, type); +} + +TEST_F(RtpPacketHistoryTest, MinResendTime) { + hist_->SetStorePacketsStatus(true, 10); + WebRtc_UWord32 store_time = fake_clock_.GetTimeInMS(); + uint16_t len = 0; + CreateRtpPacket(kSeqNum, kSsrc, kPayload, kTimestamp, packet_, &len); + EXPECT_EQ(0, hist_->PutRTPPacket(packet_, len, kMaxPacketLength, + kAllowRetransmission)); + + hist_->UpdateResendTime(kSeqNum); + fake_clock_.IncrementTime(100); + + // Time has elapsed. + len = kMaxPacketLength; + StorageType type; + uint32_t time; + EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 100, packet_, &len, &time, &type)); + EXPECT_GT(len, 0); + EXPECT_EQ(store_time, time); + + // Time has not elapsed. Packet should be found, but no bytes copied. + len = kMaxPacketLength; + EXPECT_TRUE(hist_->GetRTPPacket(kSeqNum, 101, packet_, &len, &time, &type)); + EXPECT_EQ(0, len); +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi index 8fe8599c26..381779c13b 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp.gypi @@ -75,6 +75,8 @@ 'h263_information.h', 'remote_rate_control.cc', 'remote_rate_control.h', + 'rtp_packet_history.cc', + 'rtp_packet_history.h', 'rtp_receiver_video.cc', 'rtp_receiver_video.h', 'rtp_sender_video.cc', @@ -84,6 +86,8 @@ 'video_codec_information.h', 'rtp_format_vp8.cc', 'rtp_format_vp8.h', + 'transmission_bucket.cc', + 'transmission_bucket.h', # Mocks '../mocks/mock_rtp_rtcp.h', ], # source diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_config.h b/src/modules/rtp_rtcp/source/rtp_rtcp_config.h index 3afb78d376..a0eed88ed8 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_config.h +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_config.h @@ -13,7 +13,9 @@ // Configuration file for RTP utilities (RTPSender, RTPReceiver ...) namespace webrtc { -enum { kRtpRtcpMaxIdleTimeProcess = 10 }; +enum { kRtpRtcpMaxIdleTimeProcess = 5, + kRtpRtcpBitrateProcessTimeMs = 10, + kRtpRtcpPacketTimeoutProcessTimeMs = 100 }; enum { NACK_PACKETS_MAX_SIZE = 256 }; // in packets enum { NACK_BYTECOUNT_SIZE = 60}; // size of our NACK history diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index 8ef9e9f600..93d088dcf2 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -87,6 +87,8 @@ ModuleRtpRtcpImpl::ModuleRtpRtcpImpl(const WebRtc_Word32 id, _audio(audio), _collisionDetected(false), _lastProcessTime(clock->GetTimeInMS()), + _lastBitrateProcessTime(clock->GetTimeInMS()), + _lastPacketTimeoutProcessTime(clock->GetTimeInMS()), _packetOverHead(28), // IPV4 UDP _criticalSectionModulePtrs(CriticalSectionWrapper::CreateCriticalSection()), @@ -398,47 +400,55 @@ WebRtc_Word32 ModuleRtpRtcpImpl::TimeUntilNextProcess() // Process any pending tasks such as timeouts // non time critical events -WebRtc_Word32 ModuleRtpRtcpImpl::Process() -{ - _lastProcessTime = _clock.GetTimeInMS(); +WebRtc_Word32 ModuleRtpRtcpImpl::Process() { + const WebRtc_UWord32 now = _clock.GetTimeInMS(); + _lastProcessTime = now; + _rtpSender.ProcessSendToNetwork(); + + if (now >= _lastPacketTimeoutProcessTime + + kRtpRtcpPacketTimeoutProcessTimeMs) { _rtpReceiver.PacketTimeout(); _rtcpReceiver.PacketTimeout(); + _lastPacketTimeoutProcessTime = now; + } + if (now >= _lastBitrateProcessTime + kRtpRtcpBitrateProcessTimeMs) { _rtpSender.ProcessBitrate(); _rtpReceiver.ProcessBitrate(); + _lastBitrateProcessTime = now; + } - ProcessDeadOrAliveTimer(); + ProcessDeadOrAliveTimer(); - const bool defaultInstance(_childModules.empty() ? false : true); - if(!defaultInstance &&_rtcpSender.TimeToSendRTCPReport()) - { - WebRtc_UWord16 RTT = 0; - _rtcpReceiver.RTT(_rtpReceiver.SSRC(), &RTT, NULL, NULL, NULL); - if (REMB() && _rtcpSender.ValidBitrateEstimate()) - { - unsigned int target_bitrate = - _rtcpSender.CalculateNewTargetBitrate(RTT); - _rtcpSender.UpdateRemoteBitrateEstimate(target_bitrate); - } else if (TMMBR()) { + const bool defaultInstance(_childModules.empty() ? false : true); + if(!defaultInstance &&_rtcpSender.TimeToSendRTCPReport()) + { + WebRtc_UWord16 RTT = 0; + _rtcpReceiver.RTT(_rtpReceiver.SSRC(), &RTT, NULL, NULL, NULL); + if (REMB() && _rtcpSender.ValidBitrateEstimate()) + { + unsigned int target_bitrate = _rtcpSender.CalculateNewTargetBitrate(RTT); - } - _rtcpSender.SendRTCP(kRtcpReport, 0, 0, RTT); + _rtcpSender.UpdateRemoteBitrateEstimate(target_bitrate); + } else if (TMMBR()) { + _rtcpSender.CalculateNewTargetBitrate(RTT); + } + _rtcpSender.SendRTCP(kRtcpReport, 0, 0, RTT); + } + + if (_rtpSender.RTPKeepalive()) { + // check time to send RTP keep alive + if (_rtpSender.TimeToSendRTPKeepalive()) { + _rtpSender.SendRTPKeepalivePacket(); } - if(_rtpSender.RTPKeepalive()) - { - // check time to send RTP keep alive - if( _rtpSender.TimeToSendRTPKeepalive()) - { - _rtpSender.SendRTPKeepalivePacket(); - } - } - if(UpdateRTCPReceiveInformationTimers()) - { - // a receiver has timed out - UpdateTMMBR(); - } - return 0; + } + + if (UpdateRTCPReceiveInformationTimers()) { + // a receiver has timed out + UpdateTMMBR(); + } + return 0; } /** @@ -1849,6 +1859,14 @@ WebRtc_Word32 ModuleRtpRtcpImpl::DeregisterReceiveRtpHeaderExtension( return _rtpReceiver.DeregisterRtpHeaderExtension(type); } +void ModuleRtpRtcpImpl::SetTransmissionSmoothingStatus(const bool enable) { + _rtpSender.SetTransmissionSmoothingStatus(enable); +} + +bool ModuleRtpRtcpImpl::TransmissionSmoothingStatus() const { + return _rtpSender.TransmissionSmoothingStatus(); +} + /* * (TMMBR) Temporary Max Media Bit Rate */ diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h index 717f12cd39..4351ab1e7d 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -181,6 +181,10 @@ public: virtual WebRtc_Word32 DeregisterSendRtpHeaderExtension( const RTPExtensionType type); + virtual void SetTransmissionSmoothingStatus(const bool enable); + + virtual bool TransmissionSmoothingStatus() const; + // get start timestamp virtual WebRtc_UWord32 StartTimestamp() const; @@ -572,6 +576,8 @@ private: const bool _audio; bool _collisionDetected; WebRtc_UWord32 _lastProcessTime; + WebRtc_UWord32 _lastBitrateProcessTime; + WebRtc_UWord32 _lastPacketTimeoutProcessTime; WebRtc_UWord16 _packetOverHead; CriticalSectionWrapper* _criticalSectionModulePtrs; diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi index 8619f6cf4c..a93ff8b3ed 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi @@ -25,10 +25,12 @@ 'rtp_format_vp8_test_helper.cc', 'rtp_format_vp8_test_helper.h', 'rtcp_format_remb_unittest.cc', + 'rtp_packet_history_test.cc', 'rtp_utility_test.cc', 'rtp_header_extension_test.cc', 'rtp_sender_test.cc', 'rtcp_sender_test.cc', + 'transmission_bucket_test.cc', ], }, ], diff --git a/src/modules/rtp_rtcp/source/rtp_sender.cc b/src/modules/rtp_rtcp/source/rtp_sender.cc index 89470d8b0b..84e76a9033 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender.cc +++ b/src/modules/rtp_rtcp/source/rtp_sender.cc @@ -15,6 +15,7 @@ #include "critical_section_wrapper.h" #include "trace.h" +#include "rtp_packet_history.h" #include "rtp_sender_audio.h" #include "rtp_sender_video.h" @@ -49,20 +50,16 @@ RTPSender::RTPSender(const WebRtc_Word32 id, _keepAliveLastSent(0), _keepAliveDeltaTimeSend(0), - _storeSentPackets(false), - _storeSentPacketsNumber(0), - _prevSentPacketsCritsect(CriticalSectionWrapper::CreateCriticalSection()), - _prevSentPacketsIndex(0), - _ptrPrevSentPackets(NULL), - _prevSentPacketsSeqNum(NULL), - _prevSentPacketsLength(NULL), - _prevSentPacketsResendTime(NULL), - // NACK _nackByteCountTimes(), _nackByteCount(), _nackBitrate(clock), + _packetHistory(new RTPPacketHistory(clock)), + _sendBucket(), + _timeLastSendToNetworkUpdate(clock->GetTimeInMS()), + _transmissionSmoothing(false), + // statistics _packetsSent(0), _payloadBytesSent(0), @@ -113,7 +110,7 @@ RTPSender::~RTPSender() _ssrcDB.ReturnSSRC(_ssrc); SSRCDatabase::ReturnSSRCDatabase(); - delete _prevSentPacketsCritsect; + delete _sendCritsect; delete _transportCritsect; @@ -136,18 +133,7 @@ RTPSender::~RTPSender() } } while (loop); - for(WebRtc_Word32 i=0; i< _storeSentPacketsNumber; i++) - { - if(_ptrPrevSentPackets[i]) - { - delete [] _ptrPrevSentPackets[i]; - _ptrPrevSentPackets[i] = 0; - } - } - delete [] _ptrPrevSentPackets; - delete [] _prevSentPacketsSeqNum; - delete [] _prevSentPacketsLength; - delete [] _prevSentPacketsResendTime; + delete _packetHistory; delete _audio; delete _video; @@ -207,6 +193,7 @@ RTPSender::Init(const WebRtc_UWord32 remoteSSRC) _nackBitrate.Init(); SetStorePacketsStatus(false, 0); + _sendBucket.Reset(); Bitrate::Init(); @@ -570,23 +557,6 @@ RTPSender::SetMaxPayloadLength(const WebRtc_UWord16 maxPayloadLength, const WebR WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s invalid argument", __FUNCTION__); return -1; } - if(maxPayloadLength > _maxPayloadLength) - { - CriticalSectionScoped lock(_prevSentPacketsCritsect); - if(_storeSentPackets) - { - // we need to free the memmory allocated for storing sent packets - // will be allocated in SendToNetwork - for(WebRtc_Word32 i=0; i< _storeSentPacketsNumber; i++) - { - if(_ptrPrevSentPackets[i]) - { - delete [] _ptrPrevSentPackets[i]; - _ptrPrevSentPackets[i] = NULL; - } - } - } - } CriticalSectionScoped cs(_sendCritsect); _maxPayloadLength = maxPayloadLength; @@ -618,6 +588,16 @@ RTPSender::PacketOverHead() const return _packetOverHead; } +void RTPSender::SetTransmissionSmoothingStatus(const bool enable) { + CriticalSectionScoped cs(_sendCritsect); + _transmissionSmoothing = enable; +} + +bool RTPSender::TransmissionSmoothingStatus() const { + CriticalSectionScoped cs(_sendCritsect); + return _transmissionSmoothing; +} + void RTPSender::SetRTXStatus(const bool enable, const bool setSSRC, const WebRtc_UWord32 SSRC) { @@ -839,216 +819,110 @@ WebRtc_Word32 RTPSender::SendPadData(WebRtc_Word8 payload_type, return 0; } -WebRtc_Word32 -RTPSender::SetStorePacketsStatus(const bool enable, const WebRtc_UWord16 numberToStore) -{ - CriticalSectionScoped lock(_prevSentPacketsCritsect); - - if(enable) - { - if(_storeSentPackets) - { - // already enabled - return -1; - } - if(numberToStore > 0) - { - _storeSentPackets = enable; - _storeSentPacketsNumber = numberToStore; - - _ptrPrevSentPackets = new WebRtc_Word8*[numberToStore], - _prevSentPacketsSeqNum = new WebRtc_UWord16[numberToStore]; - _prevSentPacketsLength = new WebRtc_UWord16[numberToStore]; - _prevSentPacketsResendTime = new WebRtc_UWord32[numberToStore]; - - memset(_ptrPrevSentPackets,0, sizeof(WebRtc_Word8*)*numberToStore); - memset(_prevSentPacketsSeqNum,0, sizeof(WebRtc_UWord16)*numberToStore); - memset(_prevSentPacketsLength,0, sizeof(WebRtc_UWord16)*numberToStore); - memset(_prevSentPacketsResendTime,0,sizeof(WebRtc_UWord32)*numberToStore); - } else - { - // storing 0 packets does not make sence - return -1; - } - } else - { - _storeSentPackets = enable; - if(_storeSentPacketsNumber > 0) - { - for(WebRtc_Word32 i=0; i< _storeSentPacketsNumber; i++) - { - if(_ptrPrevSentPackets[i]) - { - delete [] _ptrPrevSentPackets[i]; - _ptrPrevSentPackets[i] = 0; - } - } - delete [] _ptrPrevSentPackets; - delete [] _prevSentPacketsSeqNum; - delete [] _prevSentPacketsLength; - delete [] _prevSentPacketsResendTime; - - _ptrPrevSentPackets = NULL; - _prevSentPacketsSeqNum = NULL; - _prevSentPacketsLength = NULL; - _prevSentPacketsResendTime = NULL; - - _storeSentPacketsNumber = 0; - } - } - return 0; +WebRtc_Word32 RTPSender::SetStorePacketsStatus( + const bool enable, + const WebRtc_UWord16 numberToStore) { + _packetHistory->SetStorePacketsStatus(enable, numberToStore); + return 0; } bool RTPSender::StorePackets() const { - return _storeSentPackets; + return _packetHistory->StorePackets(); } -WebRtc_Word32 RTPSender::ReSendPacket(WebRtc_UWord16 packetID, - WebRtc_UWord32 minResendTime) { - WebRtc_Word32 length = 0; - WebRtc_Word32 index = 0; - WebRtc_UWord8 dataBuffer[IP_PACKET_SIZE]; - { - CriticalSectionScoped lock(_prevSentPacketsCritsect); +WebRtc_Word32 RTPSender::ReSendPacket(WebRtc_UWord16 packet_id, + WebRtc_UWord32 min_resend_time) { - WebRtc_UWord16 seqNum = 0; - if (!_storeSentPackets) { - WEBRTC_TRACE(kTraceWarning, - kTraceRtpRtcp, - _id, - "Ignoring request to ReSendPacket:%u we're not storing.", - seqNum); - return -1; - } - if (_prevSentPacketsIndex) { - seqNum = _prevSentPacketsSeqNum[_prevSentPacketsIndex-1]; - } else { - seqNum = _prevSentPacketsSeqNum[_storeSentPacketsNumber-1]; - } - index = (_prevSentPacketsIndex-1) - (seqNum - packetID); - if (index >= 0 && index < _storeSentPacketsNumber) { - seqNum = _prevSentPacketsSeqNum[index]; - } - if (seqNum != packetID) { - // we did not found a match, search all - for (WebRtc_Word32 m = 0; m < _storeSentPacketsNumber; m++) { - if(_prevSentPacketsSeqNum[m] == packetID) { - index = m; - seqNum = _prevSentPacketsSeqNum[index]; - break; - } - } - } - if (seqNum != packetID) { - WEBRTC_TRACE(kTraceWarning, - kTraceRtpRtcp, - _id, - "No match for resending seqNum %u and packetId %u", - seqNum, packetID); - return -1; - } - WebRtc_UWord32 timeNow = _clock.GetTimeInMS(); - if (minResendTime > 0 && - (timeNow-_prevSentPacketsResendTime[index] < minResendTime)) { - // No point in sending the packet again yet. Get out of here - WEBRTC_TRACE(kTraceStream, - kTraceRtpRtcp, - _id, - "Skipping to resend RTP packet %d, it was just resent", - seqNum); - return 0; - } - length = _prevSentPacketsLength[index]; - if (length > _maxPayloadLength || _ptrPrevSentPackets[index] == 0) { - WEBRTC_TRACE(kTraceWarning, - kTraceRtpRtcp, - _id, - "Failed to resend seqNum %u: length = %d index = %d", - seqNum, length, index); - return -1; - } - if (length == 0) { - // This is a valid case since packets which we decide not to retransmit - // are stored but with length zero. - return 0; - } - if (_RTX) { - CriticalSectionScoped cs(_sendCritsect); - // Copy to local buffer for callback and add RTX header. - ModuleRTPUtility::RTPHeaderParser rtpParser( - reinterpret_cast(_ptrPrevSentPackets[index]), - length); + WebRtc_UWord16 length = IP_PACKET_SIZE; + WebRtc_UWord8 data_buffer[IP_PACKET_SIZE]; + WebRtc_UWord8* buffer_to_send_ptr = data_buffer; - WebRtcRTPHeader rtp_header; - rtpParser.Parse(rtp_header); - - // Add original RTP header. - memcpy(dataBuffer, _ptrPrevSentPackets[index], - rtp_header.header.headerLength); - - // Replace sequence number. - WebRtc_UWord8* ptr = dataBuffer + 2; - ModuleRTPUtility::AssignUWord16ToBuffer(ptr, _sequenceNumberRTX++); - - // Replace SSRC. - ptr += 6; - ModuleRTPUtility::AssignUWord32ToBuffer(ptr, _ssrcRTX); - - // Add OSN (original sequence number). - ptr = dataBuffer + rtp_header.header.headerLength; - ModuleRTPUtility::AssignUWord16ToBuffer( - ptr, rtp_header.header.sequenceNumber); - ptr += 2; - - // Add original payload data. - memcpy(ptr, - _ptrPrevSentPackets[index] + rtp_header.header.headerLength, - length - rtp_header.header.headerLength); - length += 2; - } else { - // copy to local buffer for callback - memcpy(dataBuffer, _ptrPrevSentPackets[index], length); - } - } // End of scope lock(_prevSentPacketsCritsect). - WebRtc_Word32 i = ReSendToNetwork(dataBuffer, length); - - if (_storeSentPackets && i > 0) { - CriticalSectionScoped lock(_prevSentPacketsCritsect); - - // Make sure the packet is still in the array - if(_prevSentPacketsSeqNum[index] == packetID) { - // Store the time when the frame was last resent. - _prevSentPacketsResendTime[index]= _clock.GetTimeInMS(); - } - return i; //bytes sent over network + WebRtc_UWord32 stored_time_in_ms; + StorageType type; + bool found = _packetHistory->GetRTPPacket(packet_id, + min_resend_time, data_buffer, &length, &stored_time_in_ms, &type); + if (!found) { + // Packet not found. + return -1; } - WEBRTC_TRACE(kTraceWarning, - kTraceRtpRtcp, - _id, - "Transport failed to resend packetID %u", - packetID); - return -1; + + if (length == 0 || type == kDontRetransmit) { + // No bytes copied (packet recently resent, skip resending) or + // packet should not be retransmitted. + return 0; + } + + if (_RTX) { + WebRtc_UWord8 data_buffer_rtx[IP_PACKET_SIZE]; + buffer_to_send_ptr = data_buffer_rtx; + + CriticalSectionScoped cs(_sendCritsect); + // Add RTX header. + ModuleRTPUtility::RTPHeaderParser rtpParser( + reinterpret_cast(data_buffer), + length); + + WebRtcRTPHeader rtp_header; + rtpParser.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, _sequenceNumberRTX++); + + // Replace SSRC. + ptr += 6; + ModuleRTPUtility::AssignUWord32ToBuffer(ptr, _ssrcRTX); + + // 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, + "Transport failed to resend packet_id %u", packet_id); + return -1; + } + + // Store the time when the packet was last resent. + _packetHistory->UpdateResendTime(packet_id); + + return bytes_sent; } WebRtc_Word32 RTPSender::ReSendToNetwork(const WebRtc_UWord8* packet, const WebRtc_UWord32 size) { - WebRtc_Word32 i = -1; + WebRtc_Word32 bytes_sent = -1; { CriticalSectionScoped lock(_transportCritsect); - if(_transport) { - i = _transport->SendPacket(_id, packet, size); + if (_transport) { + bytes_sent = _transport->SendPacket(_id, packet, size); } } - if(i > 0) { - CriticalSectionScoped cs(_sendCritsect); - Bitrate::Update(i); - - _packetsSent++; - // We on purpose don't add to _payloadBytesSent since this is a re-transmit - // and not new payload data + if (bytes_sent <= 0) { + return -1; } - return i; + + // Update send statistics + CriticalSectionScoped cs(_sendCritsect); + Bitrate::Update(bytes_sent); + _packetsSent++; + // We on purpose don't add to _payloadBytesSent since this is a + // re-transmit and not new payload data. + return bytes_sent; } int RTPSender::SelectiveRetransmissions() const { @@ -1068,12 +942,12 @@ RTPSender::OnReceivedNACK(const WebRtc_UWord16 nackSequenceNumbersLength, const WebRtc_UWord32 now = _clock.GetTimeInMS(); WebRtc_UWord32 bytesReSent = 0; - // Enough bandwith to send NACK? + // Enough bandwidth to send NACK? if (!ProcessNACKBitRate(now)) { WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, - "NACK bitrate reached. Skipp sending NACK response. Target %d", + "NACK bitrate reached. Skip sending NACK response. Target %d", TargetSendBitrateKbit()); return; } @@ -1171,86 +1045,108 @@ void RTPSender::UpdateNACKBitRate(const WebRtc_UWord32 bytes, } } +void RTPSender::ProcessSendToNetwork() { + + // triggered by timer + WebRtc_UWord32 delta_time_ms; + { + CriticalSectionScoped cs(_sendCritsect); + + if (!_transmissionSmoothing) { + return; + } + + WebRtc_UWord32 now = _clock.GetTimeInMS(); + delta_time_ms = now - _timeLastSendToNetworkUpdate; + _timeLastSendToNetworkUpdate = now; + } + + _sendBucket.UpdateBytesPerInterval(delta_time_ms, _targetSendBitrate); + + while (!_sendBucket.Empty()) { + + WebRtc_Word32 seq_num = _sendBucket.GetNextPacket(); + if (seq_num < 0) { + break; + } + + WebRtc_UWord8 data_buffer[IP_PACKET_SIZE]; + WebRtc_UWord16 length = IP_PACKET_SIZE; + WebRtc_UWord32 stored_time_ms; + StorageType type; + assert(_packetHistory->GetRTPPacket(seq_num, 0, data_buffer, &length, + &stored_time_ms, &type)); + assert(length > 0); + + WebRtc_UWord32 diff_ms = _clock.GetTimeInMS() - stored_time_ms; + + ModuleRTPUtility::RTPHeaderParser rtpParser(data_buffer, length); + WebRtcRTPHeader rtp_header; + assert(rtpParser.Parse(rtp_header)); + + UpdateTransmissionTimeOffset(data_buffer, length, rtp_header, diff_ms); + + // Send packet + WebRtc_Word32 bytes_sent = -1; + { + CriticalSectionScoped cs(_transportCritsect); + if (_transport) { + bytes_sent = _transport->SendPacket(_id, data_buffer, length); + } + } + + // Update send statistics + if (bytes_sent > 0) { + CriticalSectionScoped cs(_sendCritsect); + Bitrate::Update(bytes_sent); + _packetsSent++; + if (bytes_sent > rtp_header.header.headerLength) { + _payloadBytesSent += bytes_sent - rtp_header.header.headerLength; + } + } + } +} + WebRtc_Word32 RTPSender::SendToNetwork(const WebRtc_UWord8* buffer, const WebRtc_UWord16 length, const WebRtc_UWord16 rtpLength, const StorageType storage) { - WebRtc_Word32 retVal = -1; - // sanity - if(length + rtpLength > _maxPayloadLength) - { - return -1; - } - - // Make sure the packet is big enough for us to parse the sequence number. - assert(length + rtpLength > 3); - // Parse the sequence number from the RTP header. - WebRtc_UWord16 sequenceNumber = (buffer[2] << 8) + buffer[3]; - switch (storage) { - case kAllowRetransmission: - StorePacket(buffer, length + rtpLength, sequenceNumber); - break; - case kDontRetransmit: - // Store an empty packet. Won't be retransmitted if NACKed. - StorePacket(NULL, 0, sequenceNumber); - break; - case kDontStore: - break; - default: - assert(false); - } - // Send packet - { - CriticalSectionScoped cs(_transportCritsect); - if(_transport) - { - retVal = _transport->SendPacket(_id, buffer, length + rtpLength); - } - } - // success? - if(retVal > 0) - { - CriticalSectionScoped cs(_sendCritsect); - - Bitrate::Update(retVal); - - _packetsSent++; - - if(retVal > rtpLength) - { - _payloadBytesSent += retVal-rtpLength; - } - return 0; - } + // Used for NACK or to spead out the transmission of packets. + if (_packetHistory->PutRTPPacket( + buffer, rtpLength + length, _maxPayloadLength, storage) != 0) { return -1; -} + } -void RTPSender::StorePacket(const uint8_t* buffer, uint16_t length, - uint16_t sequence_number) { - // Store packet to be used for NACK. - CriticalSectionScoped lock(_prevSentPacketsCritsect); - if(_storeSentPackets) { - if(_ptrPrevSentPackets[0] == NULL) { - for(WebRtc_Word32 i = 0; i < _storeSentPacketsNumber; i++) { - _ptrPrevSentPackets[i] = new char[_maxPayloadLength]; - memset(_ptrPrevSentPackets[i], 0, _maxPayloadLength); - } - } + if (_transmissionSmoothing) { + const WebRtc_UWord16 sequenceNumber = (buffer[2] << 8) + buffer[3]; + _sendBucket.Fill(sequenceNumber, rtpLength + length); + // Packet will be sent at a later time. + return 0; + } - if (buffer != NULL && length > 0) { - memcpy(_ptrPrevSentPackets[_prevSentPacketsIndex], buffer, length); - } - _prevSentPacketsSeqNum[_prevSentPacketsIndex] = sequence_number; - _prevSentPacketsLength[_prevSentPacketsIndex] = length; - // Packet has not been re-sent. - _prevSentPacketsResendTime[_prevSentPacketsIndex] = 0; - _prevSentPacketsIndex++; - if(_prevSentPacketsIndex >= _storeSentPacketsNumber) { - _prevSentPacketsIndex = 0; + // Send packet + WebRtc_Word32 bytes_sent = -1; + { + CriticalSectionScoped cs(_transportCritsect); + if (_transport) { + bytes_sent = _transport->SendPacket(_id, buffer, length + rtpLength); } } + + if (bytes_sent <= 0) { + return -1; + } + + // Update send statistics + CriticalSectionScoped cs(_sendCritsect); + Bitrate::Update(bytes_sent); + _packetsSent++; + if (bytes_sent > rtpLength) { + _payloadBytesSent += bytes_sent - rtpLength; + } + return 0; } void @@ -1453,7 +1349,7 @@ RTPSender::BuildTransmissionTimeOffsetExtension(WebRtc_UWord8* dataBuffer) const // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | len=2 | transmission offset | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -1475,6 +1371,60 @@ RTPSender::BuildTransmissionTimeOffsetExtension(WebRtc_UWord8* dataBuffer) const return TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES; } +void RTPSender::UpdateTransmissionTimeOffset( + WebRtc_UWord8* rtp_packet, + const WebRtc_UWord16 rtp_packet_length, + const WebRtcRTPHeader& rtp_header, + const WebRtc_UWord32 time_ms) const { + CriticalSectionScoped cs(_sendCritsect); + + // Get length until start of transmission block. + int transmission_block_pos = + _rtpHeaderExtensionMap.GetLengthUntilBlockStartInBytes( + kRtpExtensionTransmissionTimeOffset); + if (transmission_block_pos < 0) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, + "Failed to update transmission time offset, not registered."); + return; + } + + int block_pos = 12 + rtp_header.header.numCSRCs + transmission_block_pos; + if ((rtp_packet_length < block_pos + 4)) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, + "Failed to update transmission time offset, invalid length."); + return; + } + + // Verify that header contains extension. + if (!((rtp_packet[12 + rtp_header.header.numCSRCs] == 0xBE) && + (rtp_packet[12 + rtp_header.header.numCSRCs + 1] == 0xDE))) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, + "Failed to update transmission time offset, hdr extension not found."); + return; + } + + // Get id. + WebRtc_UWord8 id = 0; + if (_rtpHeaderExtensionMap.GetId(kRtpExtensionTransmissionTimeOffset, + &id) != 0) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, + "Failed to update transmission time offset, no id."); + return; + } + + // Verify first byte in block. + const WebRtc_UWord8 first_block_byte = (id << 4) + 2; + if (rtp_packet[block_pos] != first_block_byte) { + WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, + "Failed to update transmission time offset."); + return; + } + + // Update transmission offset field. + ModuleRTPUtility::AssignUWord24ToBuffer(rtp_packet + block_pos + 1, + time_ms * 90); // RTP timestamp +} + WebRtc_Word32 RTPSender::RegisterSendTransport(Transport* transport) { diff --git a/src/modules/rtp_rtcp/source/rtp_sender.h b/src/modules/rtp_rtcp/source/rtp_sender.h index bfb79b04a0..da2e67bfb3 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender.h +++ b/src/modules/rtp_rtcp/source/rtp_sender.h @@ -11,7 +11,7 @@ #ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ #define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ -#include "rtp_rtcp_config.h" // misc. defines (e.g. MAX_PACKET_LENGTH) +#include "rtp_rtcp_config.h" // misc. defines (e.g. MAX_PACKET_LENGTH) #include "rtp_rtcp_defines.h" #include "common_types.h" // Encryption #include "ssrc_database.h" @@ -20,6 +20,7 @@ #include "Bitrate.h" #include "rtp_header_extension.h" #include "video_codec_information.h" +#include "transmission_bucket.h" #include #include @@ -28,15 +29,10 @@ namespace webrtc { class CriticalSectionWrapper; +class RTPPacketHistory; class RTPSenderAudio; class RTPSenderVideo; -enum StorageType { - kDontStore, - kDontRetransmit, - kAllowRetransmission -}; - class RTPSenderInterface { public: @@ -78,6 +74,7 @@ public: void ChangeUniqueId(const WebRtc_Word32 id); void ProcessBitrate(); + void ProcessSendToNetwork(); WebRtc_UWord16 TargetSendBitrateKbit() const; WebRtc_UWord16 ActualSendBitrateKbit() const; @@ -169,6 +166,15 @@ public: WebRtc_UWord8 BuildTransmissionTimeOffsetExtension( WebRtc_UWord8* dataBuffer) const; + void UpdateTransmissionTimeOffset(WebRtc_UWord8* rtp_packet, + const WebRtc_UWord16 rtp_packet_length, + const WebRtcRTPHeader& rtp_header, + const WebRtc_UWord32 time_ms) const; + + void SetTransmissionSmoothingStatus(const bool enable); + + bool TransmissionSmoothingStatus() const; + /* * NACK */ @@ -183,8 +189,8 @@ public: bool StorePackets() const; - WebRtc_Word32 ReSendPacket(WebRtc_UWord16 packetID, - WebRtc_UWord32 minResendTime=0); + WebRtc_Word32 ReSendPacket(WebRtc_UWord16 packet_id, + WebRtc_UWord32 min_resend_time = 0); WebRtc_Word32 ReSendToNetwork(const WebRtc_UWord8* packet, const WebRtc_UWord32 size); @@ -301,20 +307,18 @@ public: const bool deltaUseUepProtection); protected: - WebRtc_Word32 CheckPayloadType(const WebRtc_Word8 payloadType, RtpVideoCodecTypes& videoType); + WebRtc_Word32 CheckPayloadType(const WebRtc_Word8 payloadType, + RtpVideoCodecTypes& videoType); private: void UpdateNACKBitRate(const WebRtc_UWord32 bytes, const WebRtc_UWord32 now); - void StorePacket(const uint8_t* buffer, uint16_t length, - uint16_t sequence_number); - - WebRtc_Word32 _id; - const bool _audioConfigured; - RTPSenderAudio* _audio; - RTPSenderVideo* _video; - + WebRtc_Word32 _id; + const bool _audioConfigured; + RTPSenderAudio* _audio; + RTPSenderVideo* _video; + CriticalSectionWrapper* _sendCritsect; CriticalSectionWrapper* _transportCritsect; @@ -337,21 +341,16 @@ private: WebRtc_UWord32 _keepAliveLastSent; WebRtc_UWord16 _keepAliveDeltaTimeSend; - bool _storeSentPackets; - WebRtc_UWord16 _storeSentPacketsNumber; - CriticalSectionWrapper* _prevSentPacketsCritsect; - - WebRtc_Word32 _prevSentPacketsIndex; - WebRtc_Word8** _ptrPrevSentPackets; - WebRtc_UWord16* _prevSentPacketsSeqNum; - WebRtc_UWord16* _prevSentPacketsLength; - WebRtc_UWord32* _prevSentPacketsResendTime; - // NACK WebRtc_UWord32 _nackByteCountTimes[NACK_BYTECOUNT_SIZE]; WebRtc_Word32 _nackByteCount[NACK_BYTECOUNT_SIZE]; Bitrate _nackBitrate; + RTPPacketHistory* _packetHistory; + TransmissionBucket _sendBucket; + WebRtc_UWord32 _timeLastSendToNetworkUpdate; + bool _transmissionSmoothing; + // statistics WebRtc_UWord32 _packetsSent; WebRtc_UWord32 _payloadBytesSent; diff --git a/src/modules/rtp_rtcp/source/rtp_sender_test.cc b/src/modules/rtp_rtcp/source/rtp_sender_test.cc index cde82fd3e4..90cfb72ac9 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender_test.cc +++ b/src/modules/rtp_rtcp/source/rtp_sender_test.cc @@ -32,19 +32,66 @@ const int kTimeOffset = 22222; const int kMaxPacketLength = 1500; } // namespace +class FakeClockTest : public RtpRtcpClock { + public: + FakeClockTest() { + time_in_ms_ = 123456; + } + // Return a timestamp in milliseconds relative to some arbitrary + // source; the source is fixed for this clock. + virtual WebRtc_UWord32 GetTimeInMS() { + return time_in_ms_; + } + // Retrieve an NTP absolute timestamp. + virtual void CurrentNTP(WebRtc_UWord32& secs, WebRtc_UWord32& frac) { + secs = time_in_ms_ / 1000; + frac = (time_in_ms_ % 1000) * 4294967; + } + void IncrementTime(WebRtc_UWord32 time_increment_ms) { + time_in_ms_ += time_increment_ms; + } + private: + WebRtc_UWord32 time_in_ms_; +}; + +class LoopbackTransportTest : public webrtc::Transport { + public: + LoopbackTransportTest() + : packets_sent_(0), + last_sent_packet_len_(0) { + } + virtual int SendPacket(int channel, const void *data, int len) { + packets_sent_++; + memcpy(last_sent_packet_, data, len); + last_sent_packet_len_ = len; + return len; + } + virtual int SendRTCPPacket(int channel, const void *data, int len) { + return -1; + } + int packets_sent_; + int last_sent_packet_len_; + uint8_t last_sent_packet_[kMaxPacketLength]; +}; + class RtpSenderTest : public ::testing::Test { protected: RtpSenderTest() - : rtp_sender_(new RTPSender(0, false, ModuleRTPUtility::GetSystemClock())), + : fake_clock_(), + rtp_sender_(new RTPSender(0, false, &fake_clock_)), + transport_(), kMarkerBit(true), - kType(kRtpExtensionTransmissionTimeOffset) { + kType(kRtpExtensionTransmissionTimeOffset), + packet_() { EXPECT_EQ(0, rtp_sender_->SetSequenceNumber(kSeqNum)); } ~RtpSenderTest() { delete rtp_sender_; } + FakeClockTest fake_clock_; RTPSender* rtp_sender_; + LoopbackTransportTest transport_; const bool kMarkerBit; RTPExtensionType kType; uint8_t packet_[kMaxPacketLength]; @@ -124,4 +171,57 @@ TEST_F(RtpSenderTest, BuildRTPPacketWithExtension) { EXPECT_EQ(length, rtp_header2.header.headerLength); EXPECT_EQ(0, rtp_header2.extension.transmissionTimeOffset); } + +TEST_F(RtpSenderTest, NoTrafficSmoothing) { + EXPECT_EQ(0, rtp_sender_->RegisterSendTransport(&transport_)); + + WebRtc_Word32 rtp_length = rtp_sender_->BuildRTPheader(packet_, + kPayload, + kMarkerBit, + kTimestamp); + + // Packet should be sent immediately. + EXPECT_EQ(0, rtp_sender_->SendToNetwork(packet_, 0, rtp_length, + kAllowRetransmission)); + EXPECT_EQ(1, transport_.packets_sent_); + EXPECT_EQ(rtp_length, transport_.last_sent_packet_len_); +} + +TEST_F(RtpSenderTest, TrafficSmoothing) { + rtp_sender_->SetTransmissionSmoothingStatus(true); + EXPECT_EQ(0, rtp_sender_->SetStorePacketsStatus(true, 10)); + EXPECT_EQ(0, rtp_sender_->RegisterRtpHeaderExtension(kType, kId)); + EXPECT_EQ(0, rtp_sender_->RegisterSendTransport(&transport_)); + + WebRtc_Word32 rtp_length = rtp_sender_->BuildRTPheader(packet_, + kPayload, + kMarkerBit, + kTimestamp); + + // Packet should be stored in a send bucket. + EXPECT_EQ(0, rtp_sender_->SendToNetwork(packet_, 0, rtp_length, + kAllowRetransmission)); + EXPECT_EQ(0, transport_.packets_sent_); + + const int kStoredTimeInMs = 100; + fake_clock_.IncrementTime(kStoredTimeInMs); + + // Process send bucket. Packet should now be sent. + rtp_sender_->ProcessSendToNetwork(); + EXPECT_EQ(1, transport_.packets_sent_); + EXPECT_EQ(rtp_length, transport_.last_sent_packet_len_); + + // Parse sent packet. + webrtc::ModuleRTPUtility::RTPHeaderParser rtpParser( + transport_.last_sent_packet_, rtp_length); + webrtc::WebRtcRTPHeader rtp_header; + + RtpHeaderExtensionMap map; + map.Register(kType, kId); + const bool valid_rtp_header = rtpParser.Parse(rtp_header, &map); + ASSERT_TRUE(valid_rtp_header); + + // Verify transmission time offset. + EXPECT_EQ(kStoredTimeInMs * 90, rtp_header.extension.transmissionTimeOffset); +} } // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/transmission_bucket.cc b/src/modules/rtp_rtcp/source/transmission_bucket.cc new file mode 100644 index 0000000000..ed605d9fbe --- /dev/null +++ b/src/modules/rtp_rtcp/source/transmission_bucket.cc @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2011 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 "transmission_bucket.h" + +#include +#include "critical_section_wrapper.h" + +namespace webrtc { + +TransmissionBucket::TransmissionBucket() + : critsect_(CriticalSectionWrapper::CreateCriticalSection()), + accumulator_(0), + bytes_rem_total_(0), + bytes_rem_interval_(0), + packets_(), + first_(true) { +} + +TransmissionBucket::~TransmissionBucket() { + packets_.clear(); + delete critsect_; +} + +void TransmissionBucket::Reset() { + webrtc::CriticalSectionScoped cs(*critsect_); + accumulator_ = 0; + bytes_rem_total_ = 0; + bytes_rem_interval_ = 0; + packets_.clear(); + first_ = true; +} + +void TransmissionBucket::Fill(const uint16_t seq_num, + const uint32_t num_bytes) { + webrtc::CriticalSectionScoped cs(*critsect_); + accumulator_ += num_bytes; + + Packet p(seq_num, num_bytes); + packets_.push_back(p); +} + +bool TransmissionBucket::Empty() { + webrtc::CriticalSectionScoped cs(*critsect_); + return packets_.empty(); +} + +void TransmissionBucket::UpdateBytesPerInterval( + const uint32_t delta_time_ms, + const uint16_t target_bitrate_kbps) { + webrtc::CriticalSectionScoped cs(*critsect_); + + const float kMargin = 1.05; + uint32_t bytes_per_interval = + kMargin * (target_bitrate_kbps * delta_time_ms / 8); + + if (bytes_rem_interval_ < 0) { + bytes_rem_interval_ += bytes_per_interval; + } else { + bytes_rem_interval_ = bytes_per_interval; + } + + if (accumulator_) { + bytes_rem_total_ += bytes_per_interval; + return; + } + bytes_rem_total_ = bytes_per_interval; +} + +int32_t TransmissionBucket::GetNextPacket() { + webrtc::CriticalSectionScoped cs(*critsect_); + + if (accumulator_ == 0) { + // Empty. + return -1; + } + + std::vector::const_iterator it_begin = packets_.begin(); + const uint16_t num_bytes = (*it_begin).length_; + const uint16_t seq_num = (*it_begin).sequence_number_; + + if (first_) { + // Ok to transmit first packet. + first_ = false; + packets_.erase(packets_.begin()); + return seq_num; + } + + const float kFrameComplete = 0.80; + if (num_bytes * kFrameComplete > bytes_rem_total_) { + // Packet does not fit. + return -1; + } + + if (bytes_rem_interval_ <= 0) { + // All bytes consumed for this interval. + return -1; + } + + // Ok to transmit packet. + bytes_rem_total_ -= num_bytes; + bytes_rem_interval_ -= num_bytes; + + assert(accumulator_ - num_bytes >= 0); + accumulator_ -= num_bytes; + + packets_.erase(packets_.begin()); + return seq_num; +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/transmission_bucket.h b/src/modules/rtp_rtcp/source/transmission_bucket.h new file mode 100644 index 0000000000..1c27fc3646 --- /dev/null +++ b/src/modules/rtp_rtcp/source/transmission_bucket.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 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. + */ + +#ifndef WEBRTC_MODULES_RTP_RTCP_TRANSMISSION_BUCKET_H_ +#define WEBRTC_MODULES_RTP_RTCP_TRANSMISSION_BUCKET_H_ + +#include +#include + +#include "typedefs.h" + +namespace webrtc +{ +class CriticalSectionWrapper; + +class TransmissionBucket { + public: + TransmissionBucket(); + ~TransmissionBucket(); + + // Resets members to initial state. + void Reset(); + + // Adds packet to be sent. + void Fill(const uint16_t seq_num, const uint32_t num_bytes); + + // Returns true if there is no packet to be sent. + bool Empty(); + + // Updates the number of bytes that can be sent for the next time interval. + void UpdateBytesPerInterval(const uint32_t delta_time_in_ms, + const uint16_t target_bitrate_kbps); + + // Checks if next packet in line can be transmitted. Returns the sequence + // number of the packet on success, -1 otherwise. The packet is removed from + // the vector on success. + int32_t GetNextPacket(); + + private: + struct Packet { + Packet(uint16_t sequence_number, uint16_t length_in_bytes) + : sequence_number_(sequence_number), + length_(length_in_bytes) { + } + uint16_t sequence_number_; + uint16_t length_; + }; + + CriticalSectionWrapper* critsect_; + uint32_t accumulator_; + int32_t bytes_rem_total_; + int32_t bytes_rem_interval_; + std::vector packets_; + bool first_; +}; +} // namespace webrtc +#endif // WEBRTC_MODULES_RTP_RTCP_TRANSMISSION_BUCKET_H_ diff --git a/src/modules/rtp_rtcp/source/transmission_bucket_test.cc b/src/modules/rtp_rtcp/source/transmission_bucket_test.cc new file mode 100644 index 0000000000..a8c9247615 --- /dev/null +++ b/src/modules/rtp_rtcp/source/transmission_bucket_test.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 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. + */ + +/* + * This file includes unit tests for the TransmissionBucket. + */ + +#include + +#include "transmission_bucket.h" + +namespace webrtc { + +class TransmissionBucketTest : public ::testing::Test { + protected: + TransmissionBucket send_bucket_; +}; + +TEST_F(TransmissionBucketTest, Fill) { + EXPECT_TRUE(send_bucket_.Empty()); + send_bucket_.Fill(1, 100); + EXPECT_FALSE(send_bucket_.Empty()); +} + +TEST_F(TransmissionBucketTest, Reset) { + send_bucket_.Fill(1, 100); + EXPECT_FALSE(send_bucket_.Empty()); + send_bucket_.Reset(); + EXPECT_TRUE(send_bucket_.Empty()); +} + +TEST_F(TransmissionBucketTest, GetNextPacket) { + EXPECT_EQ(-1, send_bucket_.GetNextPacket()); // empty + send_bucket_.Fill(1234, 100); + EXPECT_EQ(1234, send_bucket_.GetNextPacket()); // first packet ok + send_bucket_.Fill(1235, 100); + EXPECT_EQ(-1, send_bucket_.GetNextPacket()); // packet does not fit +} + +TEST_F(TransmissionBucketTest, UpdateBytesPerInterval) { + const int delta_time_ms = 1; + const int target_bitrate_kbps = 800; + send_bucket_.UpdateBytesPerInterval(delta_time_ms, target_bitrate_kbps); + + send_bucket_.Fill(1234, 50); + send_bucket_.Fill(1235, 50); + send_bucket_.Fill(1236, 50); + + EXPECT_EQ(1234, send_bucket_.GetNextPacket()); // first packet ok + EXPECT_EQ(1235, send_bucket_.GetNextPacket()); // ok + EXPECT_EQ(1236, send_bucket_.GetNextPacket()); // ok + EXPECT_TRUE(send_bucket_.Empty()); + + send_bucket_.Fill(1237, 50); + EXPECT_EQ(-1, send_bucket_.GetNextPacket()); // packet does not fit +} +} // namespace webrtc diff --git a/src/modules/rtp_rtcp/test/testAPI/test_api.cc b/src/modules/rtp_rtcp/test/testAPI/test_api.cc index 00b015734e..727d076d02 100644 --- a/src/modules/rtp_rtcp/test/testAPI/test_api.cc +++ b/src/modules/rtp_rtcp/test/testAPI/test_api.cc @@ -89,6 +89,12 @@ TEST_F(RtpRtcpAPITest, CSRC) { EXPECT_EQ(test_CSRC[1], testOfCSRC[1]); } +TEST_F(RtpRtcpAPITest, TrafficSmoothing) { + EXPECT_FALSE(module->TransmissionSmoothingStatus()); + module->SetTransmissionSmoothingStatus(true); + EXPECT_TRUE(module->TransmissionSmoothingStatus()); +} + TEST_F(RtpRtcpAPITest, RTCP) { EXPECT_EQ(kRtcpOff, module->RTCP()); EXPECT_EQ(0, module->SetRTCPStatus(kRtcpCompound));