Index: webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc |
diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6a26d1cfad526ce18f9b195c997d2fb44c3038f1 |
--- /dev/null |
+++ b/webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc |
@@ -0,0 +1,434 @@ |
+/* |
+ * Copyright (c) 2015 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 "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" |
+ |
+#include <limits> |
+ |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+#include "webrtc/modules/rtp_rtcp/source/byte_io.h" |
+ |
+using webrtc::rtcp::TransportFeedback; |
+ |
+namespace webrtc { |
+namespace { |
+ |
+static const int kHeaderSize = 20; |
+static const int kStatusChunkSize = 2; |
+static const int kSmallDeltaSize = 1; |
+static const int kLargeDeltaSize = 2; |
+ |
+static const int64_t kDeltaLimit = 0xFF * TransportFeedback::kDeltaScaleFactor; |
+ |
+class FeedbackTester { |
+ public: |
+ FeedbackTester() |
+ : expected_size_(kAnySize), |
+ default_delta_(TransportFeedback::kDeltaScaleFactor * 4) {} |
+ |
+ void WithExpectedSize(size_t expected_size) { |
+ expected_size_ = expected_size; |
+ } |
+ |
+ void WithDefaultDelta(int64_t delta) { default_delta_ = delta; } |
+ |
+ void WithInput(const uint16_t received_seq[], |
+ const int64_t received_ts[], |
+ uint16_t length) { |
+ rtc::scoped_ptr<int64_t[]> temp_deltas; |
+ if (received_ts == nullptr) { |
+ temp_deltas.reset(new int64_t[length]); |
+ GenerateDeltas(received_seq, length, temp_deltas.get()); |
+ received_ts = temp_deltas.get(); |
+ } |
+ |
+ expected_seq_.clear(); |
+ expected_deltas_.clear(); |
+ feedback_.reset(new TransportFeedback()); |
+ |
+ feedback_->WithBase(received_seq[0], received_ts[0]); |
+ int64_t last_time = feedback_->GetBaseTimeUs(); |
+ for (int i = 0; i < length; ++i) { |
+ int64_t time = received_ts[i]; |
+ EXPECT_TRUE(feedback_->WithReceivedPacket(received_seq[i], time)); |
+ |
+ if (last_time != -1) { |
+ int64_t delta = time - last_time; |
+ expected_deltas_.push_back(delta); |
+ } |
+ last_time = time; |
+ } |
+ expected_seq_.insert(expected_seq_.begin(), &received_seq[0], |
+ &received_seq[length]); |
+ } |
+ |
+ void VerifyPacket() { |
+ serialized_ = feedback_->Build(); |
+ VerifyInternal(); |
+ feedback_ = TransportFeedback::ParseFrom(serialized_->Buffer(), |
+ serialized_->Length()); |
+ ASSERT_NE(nullptr, feedback_.get()); |
+ VerifyInternal(); |
+ } |
+ |
+ static const size_t kAnySize = static_cast<size_t>(0) - 1; |
+ |
+ private: |
+ void VerifyInternal() { |
+ if (expected_size_ != kAnySize) { |
+ // Round up to whole 32-bit words. |
+ size_t expected_size_words = (expected_size_ + 3) / 4; |
+ size_t expected_size_bytes = expected_size_words * 4; |
+ EXPECT_EQ(expected_size_bytes, serialized_->Length()); |
+ } |
+ |
+ std::vector<TransportFeedback::StatusSymbol> symbols = |
+ feedback_->GetStatusVector(); |
+ uint16_t seq = feedback_->GetBaseSequence(); |
+ auto seq_it = expected_seq_.begin(); |
+ for (TransportFeedback::StatusSymbol symbol : symbols) { |
+ bool received = |
+ (symbol == TransportFeedback::StatusSymbol::kReceivedSmallDelta || |
+ symbol == TransportFeedback::StatusSymbol::kReceivedLargeDelta); |
+ if (seq_it != expected_seq_.end()) { |
+ if (seq == *seq_it) { |
+ ASSERT_NE(expected_seq_.end(), seq_it); |
+ ASSERT_TRUE(received) << "Expected received packet @ " << seq; |
+ ++seq_it; |
+ } else { |
+ ASSERT_FALSE(received) << "Did not expect received packet @ " << seq; |
+ } |
+ } |
+ ++seq; |
+ } |
+ ASSERT_EQ(expected_seq_.end(), seq_it); |
+ |
+ std::vector<int64_t> deltas = feedback_->GetReceiveDeltasUs(); |
+ ASSERT_EQ(expected_deltas_.size(), deltas.size()); |
+ for (size_t i = 0; i < expected_deltas_.size(); ++i) |
+ EXPECT_EQ(expected_deltas_[i], deltas[i]) << "Delta mismatch @ " << i; |
+ } |
+ |
+ void GenerateDeltas(const uint16_t seq[], |
+ const size_t length, |
+ int64_t* deltas) { |
+ uint16_t last_seq = seq[0]; |
+ int64_t offset = 0; |
+ |
+ for (size_t i = 0; i < length; ++i) { |
+ if (seq[i] < last_seq) |
+ offset += 0x10000 * default_delta_; |
+ last_seq = seq[i]; |
+ |
+ deltas[i] = offset + (last_seq * default_delta_); |
+ } |
+ } |
+ |
+ std::vector<uint16_t> expected_seq_; |
+ std::vector<int64_t> expected_deltas_; |
+ size_t expected_size_; |
+ int64_t default_delta_; |
+ rtc::scoped_ptr<TransportFeedback> feedback_; |
+ rtc::scoped_ptr<rtcp::RawPacket> serialized_; |
+}; |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneBitVector) { |
+ const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_FullOneBitVector) { |
+ const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13, 14}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneBitVector_WrapReceived) { |
+ const uint16_t kMax = 0xFFFF; |
+ const uint16_t kReceived[] = {kMax - 2, kMax - 1, kMax, 0, 1, 2}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneBitVector_WrapMissing) { |
+ const uint16_t kMax = 0xFFFF; |
+ const uint16_t kReceived[] = {kMax - 2, kMax - 1, 1, 2}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_TwoBitVector) { |
+ const uint16_t kReceived[] = {1, 2, 6, 7}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (kLength * kLargeDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaScaleFactor); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_TwoBitVectorFull) { |
+ const uint16_t kReceived[] = {1, 2, 6, 7, 8}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + (2 * kStatusChunkSize) + (kLength * kLargeDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaScaleFactor); |
+ test.WithInput(kReceived, nullptr, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_LargeAndNegativeDeltas) { |
+ const uint16_t kReceived[] = {1, 2, 6, 7, 8}; |
+ const int64_t kReceiveTimes[] = { |
+ 2000, |
+ 1000, |
+ 4000, |
+ 3000, |
+ 3000 + TransportFeedback::kDeltaScaleFactor * (1 << 8)}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + (3 * kLargeDeltaSize) + kSmallDeltaSize; |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_MaxRle) { |
+ // Expected chunks created: |
+ // * 1-bit vector chunk (1xreceived + 13xdropped) |
+ // * RLE chunk of max length for dropped symbol |
+ // * 1-bit vector chunk (1xreceived + 13xdropped) |
+ |
+ const size_t kPacketCount = (1 << 13) - 1 + 14; |
+ const uint16_t kReceived[] = {0, kPacketCount}; |
+ const int64_t kReceiveTimes[] = {1000, 2000}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_MinRle) { |
+ // Expected chunks created: |
+ // * 1-bit vector chunk (1xreceived + 13xdropped) |
+ // * RLE chunk of length 15 for dropped symbol |
+ // * 1-bit vector chunk (1xreceived + 13xdropped) |
+ |
+ const uint16_t kReceived[] = {0, (14 * 2) + 1}; |
+ const int64_t kReceiveTimes[] = {1000, 2000}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize); |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneToTwoBitVector) { |
+ const size_t kTwoBitVectorCapacity = 7; |
+ const uint16_t kReceived[] = {0, kTwoBitVectorCapacity - 1}; |
+ const int64_t kReceiveTimes[] = { |
+ 0, kDeltaLimit + TransportFeedback::kDeltaScaleFactor}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + kSmallDeltaSize + kLargeDeltaSize; |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneToTwoBitVectorSimpleSplit) { |
+ const size_t kTwoBitVectorCapacity = 7; |
+ const uint16_t kReceived[] = {0, kTwoBitVectorCapacity}; |
+ const int64_t kReceiveTimes[] = { |
+ 0, kDeltaLimit + TransportFeedback::kDeltaScaleFactor}; |
+ const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + (kStatusChunkSize * 2) + kSmallDeltaSize + kLargeDeltaSize; |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kLength); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_OneToTwoBitVectorSplit) { |
+ // With received small delta = S, received large delta = L, use input |
+ // SSSSSSSSLSSSSSSSSSSSS. This will cause a 1:2 split at the L. |
+ // After split there will be two symbols in symbol_vec: SL. |
+ |
+ const int64_t kLargeDelta = TransportFeedback::kDeltaScaleFactor * (1 << 8); |
+ const size_t kNumPackets = (3 * 7) + 1; |
+ const size_t kExpectedSizeBytes = kHeaderSize + (kStatusChunkSize * 3) + |
+ (kSmallDeltaSize * (kNumPackets - 1)) + |
+ (kLargeDeltaSize * 1); |
+ |
+ uint16_t kReceived[kNumPackets]; |
+ for (size_t i = 0; i < kNumPackets; ++i) |
+ kReceived[i] = i; |
+ |
+ int64_t kReceiveTimes[kNumPackets]; |
+ kReceiveTimes[0] = 1000; |
+ for (size_t i = 1; i < kNumPackets; ++i) { |
+ int delta = (i == 8) ? kLargeDelta : 1000; |
+ kReceiveTimes[i] = kReceiveTimes[i - 1] + delta; |
+ } |
+ |
+ FeedbackTester test; |
+ test.WithExpectedSize(kExpectedSizeBytes); |
+ test.WithInput(kReceived, kReceiveTimes, kNumPackets); |
+ test.VerifyPacket(); |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_Aliasing) { |
+ TransportFeedback feedback; |
+ feedback.WithBase(0, 0); |
+ |
+ const int kSamples = 100; |
+ const int64_t kTooSmallDelta = TransportFeedback::kDeltaScaleFactor / 3; |
+ |
+ for (int i = 0; i < kSamples; ++i) |
+ feedback.WithReceivedPacket(i, i * kTooSmallDelta); |
+ |
+ feedback.Build(); |
+ std::vector<int64_t> deltas = feedback.GetReceiveDeltasUs(); |
+ |
+ int64_t accumulated_delta = 0; |
+ int num_samples = 0; |
+ for (int64_t delta : deltas) { |
+ accumulated_delta += delta; |
+ int64_t expected_time = num_samples * kTooSmallDelta; |
+ ++num_samples; |
+ |
+ EXPECT_NEAR(expected_time, accumulated_delta, |
+ TransportFeedback::kDeltaScaleFactor / 2); |
+ } |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_Limits) { |
+ // Sequence number wrap above 0x8000. |
+ rtc::scoped_ptr<TransportFeedback> packet(new TransportFeedback()); |
+ packet->WithBase(0, 0); |
+ EXPECT_TRUE(packet->WithReceivedPacket(0x8000, 1000)); |
+ |
+ packet.reset(new TransportFeedback()); |
+ packet->WithBase(0, 0); |
+ EXPECT_FALSE(packet->WithReceivedPacket(0x8000 + 1, 1000)); |
+ |
+ // Packet status count max 0xFFFF. |
+ packet.reset(new TransportFeedback()); |
+ packet->WithBase(0, 0); |
+ EXPECT_TRUE(packet->WithReceivedPacket(0x8000, 1000)); |
+ EXPECT_TRUE(packet->WithReceivedPacket(0xFFFF, 2000)); |
+ EXPECT_FALSE(packet->WithReceivedPacket(0, 3000)); |
+ |
+ // Too large delta. |
+ packet.reset(new TransportFeedback()); |
+ packet->WithBase(0, 0); |
+ int64_t kMaxPositiveTimeDelta = std::numeric_limits<int16_t>::max() * |
+ TransportFeedback::kDeltaScaleFactor; |
+ EXPECT_FALSE(packet->WithReceivedPacket( |
+ 1, kMaxPositiveTimeDelta + TransportFeedback::kDeltaScaleFactor)); |
+ EXPECT_TRUE(packet->WithReceivedPacket(1, kMaxPositiveTimeDelta)); |
+ |
+ // Too large negative delta. |
+ packet.reset(new TransportFeedback()); |
+ packet->WithBase(0, 0); |
+ int64_t kMaxNegativeTimeDelta = std::numeric_limits<int16_t>::min() * |
+ TransportFeedback::kDeltaScaleFactor; |
+ EXPECT_FALSE(packet->WithReceivedPacket( |
+ 1, kMaxNegativeTimeDelta - TransportFeedback::kDeltaScaleFactor)); |
+ EXPECT_TRUE(packet->WithReceivedPacket(1, kMaxNegativeTimeDelta)); |
+ |
+ // TODO(sprang): Once we support max length lower than RTCP length limit, |
+ // add back test for max size in bytes. |
+} |
+ |
+TEST(RtcpPacketTest, TransportFeedback_Padding) { |
+ const size_t kExpectedSizeBytes = |
+ kHeaderSize + kStatusChunkSize + kSmallDeltaSize; |
+ const size_t kExpectedSizeWords = (kExpectedSizeBytes + 3) / 4; |
+ |
+ TransportFeedback feedback; |
+ feedback.WithBase(0, 0); |
+ EXPECT_TRUE(feedback.WithReceivedPacket(0, 0)); |
+ |
+ rtc::scoped_ptr<rtcp::RawPacket> packet(feedback.Build()); |
+ EXPECT_EQ(kExpectedSizeWords * 4, packet->Length()); |
+ ASSERT_GT(kExpectedSizeWords * 4, kExpectedSizeBytes); |
+ for (size_t i = kExpectedSizeBytes; i < kExpectedSizeWords * 4; ++i) |
+ EXPECT_EQ(0u, packet->Buffer()[i]); |
+ |
+ // Modify packet by adding 4 bytes of padding at the end. Not currently used |
+ // when we're sending, but need to be able to handle it when receiving. |
+ |
+ const int kPaddingBytes = 4; |
+ const size_t kExpectedSizeWithPadding = |
+ (kExpectedSizeWords * 4) + kPaddingBytes; |
+ uint8_t mod_buffer[kExpectedSizeWithPadding]; |
+ memcpy(mod_buffer, packet->Buffer(), kExpectedSizeWords * 4); |
+ memset(&mod_buffer[kExpectedSizeWords * 4], 0, kPaddingBytes - 1); |
+ mod_buffer[kExpectedSizeWithPadding - 1] = kPaddingBytes; |
+ const uint8_t padding_flag = 1 << 5; |
+ mod_buffer[0] |= padding_flag; |
+ ByteWriter<uint16_t>::WriteBigEndian( |
+ &mod_buffer[2], ByteReader<uint16_t>::ReadBigEndian(&mod_buffer[2]) + |
+ ((kPaddingBytes + 3) / 4)); |
+ |
+ rtc::scoped_ptr<TransportFeedback> parsed_packet( |
+ TransportFeedback::ParseFrom(mod_buffer, kExpectedSizeWithPadding)); |
+ ASSERT_TRUE(parsed_packet.get() != nullptr); |
+ EXPECT_EQ(kExpectedSizeWords * 4, packet->Length()); // Padding not included. |
+} |
+ |
+} // namespace |
+} // namespace webrtc |