Refactor rtcp::TransportFeedback to use Timestamp and TimeDelta internally

Bug: webrtc:13757
Change-Id: I9815e54288a064c6c8ff40f130b52786b4e398b7
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/264559
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37149}
This commit is contained in:
Danil Chapovalov
2022-06-07 19:27:06 +02:00
committed by WebRTC LUCI CQ
parent bc6101459f
commit 86c452ac5a
3 changed files with 292 additions and 235 deletions

View File

@ -42,10 +42,8 @@ constexpr size_t kMaxSizeBytes = (1 << 16) * 4;
// * 8 bytes FeedbackPacket header.
// * 2 bytes for one chunk.
constexpr size_t kMinPayloadSizeBytes = 8 + 8 + 2;
constexpr int kBaseScaleFactor =
TransportFeedback::kDeltaScaleFactor * (1 << 8);
constexpr int64_t kTimeWrapPeriodUs = (1ll << 24) * kBaseScaleFactor;
constexpr TimeDelta kTimeWrapPeriod = TimeDelta::Micros(kTimeWrapPeriodUs);
constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8);
constexpr TimeDelta kTimeWrapPeriod = kBaseTimeTick * (1 << 24);
// Message format
//
@ -274,7 +272,7 @@ TransportFeedback::TransportFeedback(bool include_timestamps, bool include_lost)
base_time_ticks_(0),
feedback_seq_(0),
include_timestamps_(include_timestamps),
last_timestamp_us_(0),
last_timestamp_(Timestamp::Zero()),
size_bytes_(kTransportFeedbackHeaderSizeBytes) {}
TransportFeedback::TransportFeedback(const TransportFeedback&) = default;
@ -286,7 +284,7 @@ TransportFeedback::TransportFeedback(TransportFeedback&& other)
base_time_ticks_(other.base_time_ticks_),
feedback_seq_(other.feedback_seq_),
include_timestamps_(other.include_timestamps_),
last_timestamp_us_(other.last_timestamp_us_),
last_timestamp_(other.last_timestamp_),
received_packets_(std::move(other.received_packets_)),
all_packets_(std::move(other.all_packets_)),
encoded_chunks_(std::move(other.encoded_chunks_)),
@ -298,12 +296,12 @@ TransportFeedback::TransportFeedback(TransportFeedback&& other)
TransportFeedback::~TransportFeedback() {}
void TransportFeedback::SetBase(uint16_t base_sequence,
int64_t ref_timestamp_us) {
Timestamp ref_timestamp) {
RTC_DCHECK_EQ(num_seq_no_, 0);
RTC_DCHECK_GE(ref_timestamp_us, 0);
base_seq_no_ = base_sequence;
base_time_ticks_ = (ref_timestamp_us % kTimeWrapPeriodUs) / kBaseScaleFactor;
last_timestamp_us_ = GetBaseTimeUs();
base_time_ticks_ =
(ref_timestamp.us() % kTimeWrapPeriod.us()) / kBaseTimeTick.us();
last_timestamp_ = BaseTime();
}
void TransportFeedback::SetFeedbackSequenceNumber(uint8_t feedback_sequence) {
@ -311,19 +309,25 @@ void TransportFeedback::SetFeedbackSequenceNumber(uint8_t feedback_sequence) {
}
bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number,
int64_t timestamp_us) {
Timestamp timestamp) {
// Set delta to zero if timestamps are not included, this will simplify the
// encoding process.
int16_t delta = 0;
if (include_timestamps_) {
// Convert to ticks and round.
if (last_timestamp_ > timestamp) {
timestamp += (last_timestamp_ - timestamp).RoundUpTo(kTimeWrapPeriod);
}
RTC_DCHECK_GE(timestamp, last_timestamp_);
int64_t delta_full =
(timestamp_us - last_timestamp_us_) % kTimeWrapPeriodUs;
if (delta_full > kTimeWrapPeriodUs / 2)
delta_full -= kTimeWrapPeriodUs;
delta_full +=
delta_full < 0 ? -(kDeltaScaleFactor / 2) : kDeltaScaleFactor / 2;
delta_full /= kDeltaScaleFactor;
(timestamp - last_timestamp_).us() % kTimeWrapPeriod.us();
if (delta_full > kTimeWrapPeriod.us() / 2) {
delta_full -= kTimeWrapPeriod.us();
delta_full -= kDeltaTick.us() / 2;
} else {
delta_full += kDeltaTick.us() / 2;
}
delta_full /= kDeltaTick.us();
delta = static_cast<int16_t>(delta_full);
// If larger than 16bit signed, we can't represent it - need new fb packet.
@ -353,7 +357,7 @@ bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number,
received_packets_.emplace_back(sequence_number, delta);
if (include_lost_)
all_packets_.emplace_back(sequence_number, delta);
last_timestamp_us_ += delta * kDeltaScaleFactor;
last_timestamp_ += delta * kDeltaTick;
if (include_timestamps_) {
size_bytes_ += delta_size;
}
@ -376,17 +380,20 @@ uint16_t TransportFeedback::GetBaseSequence() const {
}
Timestamp TransportFeedback::BaseTime() const {
return Timestamp::Zero() +
TimeDelta::Micros(int64_t{base_time_ticks_} * kBaseScaleFactor);
// Add an extra kTimeWrapPeriod to allow add received packets arrived earlier
// than the first added packet (and thus allow to record negative deltas)
// even when base_time_ticks_ == 0.
return Timestamp::Zero() + kTimeWrapPeriod +
int64_t{base_time_ticks_} * kBaseTimeTick;
}
int64_t TransportFeedback::GetBaseTimeUs() const {
// Historically BaseTime was stored as signed integer and could be negative.
// However with new api it is not possible, but for compatibility with legacy
// tests return base time as negative when it used to be negative.
int64_t base_time_us = BaseTime().us() % kTimeWrapPeriodUs;
if (base_time_us >= kTimeWrapPeriodUs / 2) {
return base_time_us - kTimeWrapPeriodUs;
int64_t base_time_us = BaseTime().us() % kTimeWrapPeriod.us();
if (base_time_us >= kTimeWrapPeriod.us() / 2) {
return base_time_us - kTimeWrapPeriod.us();
} else {
return base_time_us;
}
@ -483,7 +490,7 @@ bool TransportFeedback::Parse(const CommonHeader& packet) {
received_packets_.emplace_back(seq_no, delta);
if (include_lost_)
all_packets_.emplace_back(seq_no, delta);
last_timestamp_us_ += delta * kDeltaScaleFactor;
last_timestamp_ += delta * kDeltaTick;
index += delta_size;
break;
}
@ -492,7 +499,7 @@ bool TransportFeedback::Parse(const CommonHeader& packet) {
received_packets_.emplace_back(seq_no, delta);
if (include_lost_)
all_packets_.emplace_back(seq_no, delta);
last_timestamp_us_ += delta * kDeltaScaleFactor;
last_timestamp_ += delta * kDeltaTick;
index += delta_size;
break;
}
@ -562,7 +569,7 @@ bool TransportFeedback::IsConsistent() const {
<< num_seq_no_;
return false;
}
int64_t timestamp_us = GetBaseTimeUs();
Timestamp timestamp = BaseTime();
auto packet_it = received_packets_.begin();
uint16_t seq_no = base_seq_no_;
for (DeltaSize delta_size : delta_sizes) {
@ -584,7 +591,7 @@ bool TransportFeedback::IsConsistent() const {
<< " doesn't fit into one byte";
return false;
}
timestamp_us += packet_it->delta_us();
timestamp += packet_it->delta();
++packet_it;
}
if (include_timestamps_) {
@ -597,9 +604,10 @@ bool TransportFeedback::IsConsistent() const {
<< packet_it->sequence_number();
return false;
}
if (timestamp_us != last_timestamp_us_) {
RTC_LOG(LS_ERROR) << "Last timestamp mismatch. Calculated: " << timestamp_us
<< ". Saved: " << last_timestamp_us_;
if (timestamp != last_timestamp_) {
RTC_LOG(LS_ERROR) << "Last timestamp mismatch. Calculated: "
<< ToLogString(timestamp)
<< ". Saved: " << ToLogString(last_timestamp_);
return false;
}
if (size_bytes_ != packet_size) {
@ -684,7 +692,7 @@ bool TransportFeedback::Create(uint8_t* packet,
void TransportFeedback::Clear() {
num_seq_no_ = 0;
last_timestamp_us_ = GetBaseTimeUs();
last_timestamp_ = BaseTime();
received_packets_.clear();
all_packets_.clear();
encoded_chunks_.clear();

View File

@ -41,8 +41,8 @@ class TransportFeedback : public Rtpfb {
uint16_t sequence_number() const { return sequence_number_; }
int16_t delta_ticks() const { return delta_ticks_; }
// ABSL_DEPRECATED("Use delta() that returns TimeDelta")
int32_t delta_us() const { return delta_ticks_ * kDeltaScaleFactor; }
TimeDelta delta() const { return TimeDelta::Micros(delta_us()); }
int32_t delta_us() const { return delta().us(); }
TimeDelta delta() const { return delta_ticks_ * kDeltaTick; }
bool received() const { return received_; }
private:
@ -71,20 +71,20 @@ class TransportFeedback : public Rtpfb {
~TransportFeedback() override;
// ABSL_DEPRECATED("Use version that takes Timestamp")
void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg.
int64_t ref_timestamp_us); // Reference timestamp for this msg.
void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg.
Timestamp ref_timestamp) { // Reference timestamp for this msg.
SetBase(base_sequence, ref_timestamp.us());
void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg.
int64_t ref_timestamp_us) { // Reference timestamp for this msg.
SetBase(base_sequence, Timestamp::Micros(ref_timestamp_us));
}
void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg.
Timestamp ref_timestamp); // Reference timestamp for this msg.
void SetFeedbackSequenceNumber(uint8_t feedback_sequence);
// NOTE: This method requires increasing sequence numbers (excepting wraps).
// ABSL_DEPRECATED("Use version that takes Timestamp")
bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp_us);
bool AddReceivedPacket(uint16_t sequence_number, Timestamp timestamp) {
return AddReceivedPacket(sequence_number, timestamp.us());
bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp_us) {
return AddReceivedPacket(sequence_number, Timestamp::Micros(timestamp_us));
}
bool AddReceivedPacket(uint16_t sequence_number, Timestamp timestamp);
const std::vector<ReceivedPacket>& GetReceivedPackets() const;
const std::vector<ReceivedPacket>& GetAllPackets() const;
@ -189,7 +189,7 @@ class TransportFeedback : public Rtpfb {
uint8_t feedback_seq_;
bool include_timestamps_;
int64_t last_timestamp_us_;
Timestamp last_timestamp_;
std::vector<ReceivedPacket> received_packets_;
std::vector<ReceivedPacket> all_packets_;
// All but last encoded packet chunks.

View File

@ -14,6 +14,9 @@
#include <memory>
#include <utility>
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
#include "test/gmock.h"
@ -23,74 +26,103 @@ namespace webrtc {
namespace {
using rtcp::TransportFeedback;
using ::testing::AllOf;
using ::testing::Each;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Property;
using ::testing::SizeIs;
static const int kHeaderSize = 20;
static const int kStatusChunkSize = 2;
static const int kSmallDeltaSize = 1;
static const int kLargeDeltaSize = 2;
constexpr int kHeaderSize = 20;
constexpr int kStatusChunkSize = 2;
constexpr int kSmallDeltaSize = 1;
constexpr int kLargeDeltaSize = 2;
static const int64_t kDeltaLimit = 0xFF * TransportFeedback::kDeltaScaleFactor;
constexpr TimeDelta kDeltaLimit = 0xFF * TransportFeedback::kDeltaTick;
constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8);
constexpr TimeDelta kBaseTimeWrapPeriod = kBaseTimeTick * (1 << 24);
MATCHER_P2(Near, value, max_abs_error, "") {
return value - max_abs_error <= arg && arg <= value + max_abs_error;
}
MATCHER(IsValidFeedback, "") {
rtcp::CommonHeader rtcp_header;
TransportFeedback feedback;
return rtcp_header.Parse(std::data(arg), std::size(arg)) &&
rtcp_header.type() == TransportFeedback::kPacketType &&
rtcp_header.fmt() == TransportFeedback::kFeedbackMessageType &&
feedback.Parse(rtcp_header);
}
TransportFeedback Parse(rtc::ArrayView<const uint8_t> buffer) {
rtcp::CommonHeader header;
RTC_DCHECK(header.Parse(buffer.data(), buffer.size()));
RTC_DCHECK_EQ(header.type(), TransportFeedback::kPacketType);
RTC_DCHECK_EQ(header.fmt(), TransportFeedback::kFeedbackMessageType);
TransportFeedback feedback;
RTC_DCHECK(feedback.Parse(header));
return feedback;
}
class FeedbackTester {
public:
FeedbackTester() : FeedbackTester(true) {}
explicit FeedbackTester(bool include_timestamps)
: expected_size_(kAnySize),
default_delta_(TransportFeedback::kDeltaScaleFactor * 4),
default_delta_(TransportFeedback::kDeltaTick * 4),
include_timestamps_(include_timestamps) {}
void WithExpectedSize(size_t expected_size) {
expected_size_ = expected_size;
}
void WithDefaultDelta(int64_t delta) { default_delta_ = delta; }
void WithDefaultDelta(TimeDelta delta) { default_delta_ = delta; }
void WithInput(const uint16_t received_seq[],
const int64_t received_ts[],
uint16_t length) {
std::unique_ptr<int64_t[]> temp_timestamps;
if (received_ts == nullptr) {
temp_timestamps.reset(new int64_t[length]);
GenerateReceiveTimestamps(received_seq, length, temp_timestamps.get());
received_ts = temp_timestamps.get();
void WithInput(rtc::ArrayView<const uint16_t> received_seq,
rtc::ArrayView<const Timestamp> received_ts = {}) {
std::vector<Timestamp> temp_timestamps;
if (received_ts.empty()) {
temp_timestamps = GenerateReceiveTimestamps(received_seq);
received_ts = temp_timestamps;
}
RTC_DCHECK_EQ(received_seq.size(), received_ts.size());
expected_seq_.clear();
expected_deltas_.clear();
feedback_.reset(new TransportFeedback(include_timestamps_));
feedback_.emplace(include_timestamps_);
feedback_->SetBase(received_seq[0], received_ts[0]);
ASSERT_TRUE(feedback_->IsConsistent());
// First delta is special: it doesn't represent the delta between two times,
// but a compensation for the reduced precision of the base time.
EXPECT_TRUE(feedback_->AddReceivedPacket(received_seq[0], received_ts[0]));
// GetBaseDelta suppose to return balanced diff between base time of the new
// feedback message (stored internally) and base time of the old feedback
// message (passed as parameter), but first delta is the difference between
// 1st timestamp (passed as parameter) and base time (stored internally),
// thus to get the first delta need to negate whatever GetBaseDelta returns.
expected_deltas_.push_back(-feedback_->GetBaseDelta(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_->AddReceivedPacket(received_seq[i], time));
if (last_time != -1) {
int64_t delta = time - last_time;
expected_deltas_.push_back(delta);
}
last_time = time;
for (size_t i = 1; i < received_ts.size(); ++i) {
EXPECT_TRUE(
feedback_->AddReceivedPacket(received_seq[i], received_ts[i]));
expected_deltas_.push_back(received_ts[i] - received_ts[i - 1]);
}
ASSERT_TRUE(feedback_->IsConsistent());
expected_seq_.insert(expected_seq_.begin(), &received_seq[0],
&received_seq[length]);
expected_seq_.assign(received_seq.begin(), received_seq.end());
}
void VerifyPacket() {
ASSERT_TRUE(feedback_->IsConsistent());
serialized_ = feedback_->Build();
VerifyInternal();
feedback_ =
TransportFeedback::ParseFrom(serialized_.data(), serialized_.size());
ASSERT_NE(nullptr, feedback_);
feedback_.emplace(Parse(serialized_));
ASSERT_TRUE(feedback_->IsConsistent());
EXPECT_EQ(include_timestamps_, feedback_->IncludeTimestamps());
VerifyInternal();
}
static const size_t kAnySize = static_cast<size_t>(0) - 1;
static constexpr size_t kAnySize = static_cast<size_t>(0) - 1;
private:
void VerifyInternal() {
@ -102,37 +134,39 @@ class FeedbackTester {
}
std::vector<uint16_t> actual_seq_nos;
std::vector<int64_t> actual_deltas_us;
std::vector<TimeDelta> actual_deltas;
for (const auto& packet : feedback_->GetReceivedPackets()) {
actual_seq_nos.push_back(packet.sequence_number());
actual_deltas_us.push_back(packet.delta_us());
actual_deltas.push_back(packet.delta());
}
EXPECT_THAT(actual_seq_nos, ElementsAreArray(expected_seq_));
if (include_timestamps_) {
EXPECT_THAT(actual_deltas_us, ElementsAreArray(expected_deltas_));
EXPECT_THAT(actual_deltas, ElementsAreArray(expected_deltas_));
}
}
void GenerateReceiveTimestamps(const uint16_t seq[],
const size_t length,
int64_t* timestamps) {
uint16_t last_seq = seq[0];
int64_t offset = 0;
std::vector<Timestamp> GenerateReceiveTimestamps(
rtc::ArrayView<const uint16_t> seq_nums) {
RTC_DCHECK(!seq_nums.empty());
uint16_t last_seq = seq_nums[0];
Timestamp time = Timestamp::Zero();
std::vector<Timestamp> result;
for (size_t i = 0; i < length; ++i) {
if (seq[i] < last_seq)
offset += 0x10000 * default_delta_;
last_seq = seq[i];
for (uint16_t seq : seq_nums) {
if (seq < last_seq)
time += 0x10000 * default_delta_;
last_seq = seq;
timestamps[i] = offset + (last_seq * default_delta_);
result.push_back(time + last_seq * default_delta_);
}
return result;
}
std::vector<uint16_t> expected_seq_;
std::vector<int64_t> expected_deltas_;
std::vector<TimeDelta> expected_deltas_;
size_t expected_size_;
int64_t default_delta_;
std::unique_ptr<TransportFeedback> feedback_;
TimeDelta default_delta_;
absl::optional<TransportFeedback> feedback_;
rtc::Buffer serialized_;
bool include_timestamps_;
};
@ -153,18 +187,17 @@ TEST(RtcpPacketTest, TransportFeedbackOneBitVector) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, nullptr, kLength);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneBitVectorNoRecvDelta) {
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;
FeedbackTester test(/*include_timestamps=*/false);
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, nullptr, kLength);
test.WithInput(kReceived);
test.VerifyPacket();
}
@ -176,7 +209,7 @@ TEST(RtcpPacketTest, TransportFeedbackFullOneBitVector) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, nullptr, kLength);
test.WithInput(kReceived);
test.VerifyPacket();
}
@ -189,7 +222,7 @@ TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapReceived) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, nullptr, kLength);
test.WithInput(kReceived);
test.VerifyPacket();
}
@ -202,7 +235,7 @@ TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapMissing) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, nullptr, kLength);
test.WithInput(kReceived);
test.VerifyPacket();
}
@ -214,8 +247,8 @@ TEST(RtcpPacketTest, TransportFeedbackTwoBitVector) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaScaleFactor);
test.WithInput(kReceived, nullptr, kLength);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick);
test.WithInput(kReceived);
test.VerifyPacket();
}
@ -227,32 +260,32 @@ TEST(RtcpPacketTest, TransportFeedbackTwoBitVectorFull) {
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaScaleFactor);
test.WithInput(kReceived, nullptr, kLength);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackWithLargeBaseTimeIsConsistent) {
TransportFeedback tb;
constexpr int64_t kTimestampUs =
int64_t{0x7fff'ffff} * TransportFeedback::kDeltaScaleFactor;
tb.SetBase(/*base_sequence=*/0, /*ref_timestamp_us=*/kTimestampUs);
tb.AddReceivedPacket(/*base_sequence=*/0, /*ref_timestamp_us=*/kTimestampUs);
constexpr Timestamp kTimestamp =
Timestamp::Zero() + int64_t{0x7fff'ffff} * TransportFeedback::kDeltaTick;
tb.SetBase(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp);
tb.AddReceivedPacket(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp);
EXPECT_TRUE(tb.IsConsistent());
}
TEST(RtcpPacketTest, TransportFeedbackLargeAndNegativeDeltas) {
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 Timestamp kReceiveTimes[] = {
Timestamp::Millis(2), Timestamp::Millis(1), Timestamp::Millis(4),
Timestamp::Millis(3),
Timestamp::Millis(3) + TransportFeedback::kDeltaTick * (1 << 8)};
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (3 * kLargeDeltaSize) + kSmallDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes, kLength);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
@ -264,14 +297,15 @@ TEST(RtcpPacketTest, TransportFeedbackMaxRle) {
const size_t kPacketCount = (1 << 13) - 1 + 14;
const uint16_t kReceived[] = {0, kPacketCount};
const int64_t kReceiveTimes[] = {1000, 2000};
const Timestamp kReceiveTimes[] = {Timestamp::Millis(1),
Timestamp::Millis(2)};
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.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
@ -282,44 +316,45 @@ TEST(RtcpPacketTest, TransportFeedbackMinRle) {
// * 1-bit vector chunk (1xreceived + 13xdropped)
const uint16_t kReceived[] = {0, (14 * 2) + 1};
const int64_t kReceiveTimes[] = {1000, 2000};
const Timestamp kReceiveTimes[] = {Timestamp::Millis(1),
Timestamp::Millis(2)};
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.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVector) {
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 Timestamp kReceiveTimes[] = {
Timestamp::Zero(),
Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick};
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + kSmallDeltaSize + kLargeDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes, kLength);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSimpleSplit) {
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 Timestamp kReceiveTimes[] = {
Timestamp::Zero(),
Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick};
const size_t kExpectedSizeBytes =
kHeaderSize + (kStatusChunkSize * 2) + kSmallDeltaSize + kLargeDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes, kLength);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
@ -328,7 +363,7 @@ TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSplit) {
// 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 TimeDelta kLargeDelta = TransportFeedback::kDeltaTick * (1 << 8);
const size_t kNumPackets = (3 * 7) + 1;
const size_t kExpectedSizeBytes = kHeaderSize + (kStatusChunkSize * 3) +
(kSmallDeltaSize * (kNumPackets - 1)) +
@ -338,107 +373,138 @@ TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSplit) {
for (size_t i = 0; i < kNumPackets; ++i)
kReceived[i] = i;
int64_t kReceiveTimes[kNumPackets];
kReceiveTimes[0] = 1000;
std::vector<Timestamp> receive_times;
receive_times.reserve(kNumPackets);
receive_times.push_back(Timestamp::Millis(1));
for (size_t i = 1; i < kNumPackets; ++i) {
int delta = (i == 8) ? kLargeDelta : 1000;
kReceiveTimes[i] = kReceiveTimes[i - 1] + delta;
TimeDelta delta = (i == 8) ? kLargeDelta : TimeDelta::Millis(1);
receive_times.push_back(receive_times.back() + delta);
}
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes, kNumPackets);
test.WithInput(kReceived, receive_times);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackAliasing) {
TransportFeedback feedback;
feedback.SetBase(0, 0);
feedback.SetBase(0, Timestamp::Zero());
const int kSamples = 100;
const int64_t kTooSmallDelta = TransportFeedback::kDeltaScaleFactor / 3;
const TimeDelta kTooSmallDelta = TransportFeedback::kDeltaTick / 3;
for (int i = 0; i < kSamples; ++i)
feedback.AddReceivedPacket(i, i * kTooSmallDelta);
feedback.AddReceivedPacket(i, Timestamp::Zero() + i * kTooSmallDelta);
feedback.Build();
int64_t accumulated_delta = 0;
TimeDelta accumulated_delta = TimeDelta::Zero();
int num_samples = 0;
for (const auto& packet : feedback.GetReceivedPackets()) {
accumulated_delta += packet.delta_us();
int64_t expected_time = num_samples * kTooSmallDelta;
accumulated_delta += packet.delta();
TimeDelta expected_time = num_samples * kTooSmallDelta;
++num_samples;
EXPECT_NEAR(expected_time, accumulated_delta,
TransportFeedback::kDeltaScaleFactor / 2);
EXPECT_THAT(accumulated_delta,
Near(expected_time, TransportFeedback::kDeltaTick / 2));
}
}
TEST(RtcpPacketTest, TransportFeedbackLimits) {
// Sequence number wrap above 0x8000.
std::unique_ptr<TransportFeedback> packet(new TransportFeedback());
packet->SetBase(0, 0);
EXPECT_TRUE(packet->AddReceivedPacket(0x0, 0));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, 1000));
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1)));
packet.reset(new TransportFeedback());
packet->SetBase(0, 0);
EXPECT_TRUE(packet->AddReceivedPacket(0x0, 0));
EXPECT_FALSE(packet->AddReceivedPacket(0x8000 + 1, 1000));
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_FALSE(packet->AddReceivedPacket(0x8000 + 1, Timestamp::Millis(1)));
// Packet status count max 0xFFFF.
packet.reset(new TransportFeedback());
packet->SetBase(0, 0);
EXPECT_TRUE(packet->AddReceivedPacket(0x0, 0));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, 1000));
EXPECT_TRUE(packet->AddReceivedPacket(0xFFFE, 2000));
EXPECT_FALSE(packet->AddReceivedPacket(0xFFFF, 3000));
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1)));
EXPECT_TRUE(packet->AddReceivedPacket(0xFFFE, Timestamp::Millis(2)));
EXPECT_FALSE(packet->AddReceivedPacket(0xFFFF, Timestamp::Millis(3)));
// Too large delta.
packet.reset(new TransportFeedback());
packet->SetBase(0, 0);
int64_t kMaxPositiveTimeDelta = std::numeric_limits<int16_t>::max() *
TransportFeedback::kDeltaScaleFactor;
EXPECT_FALSE(packet->AddReceivedPacket(
1, kMaxPositiveTimeDelta + TransportFeedback::kDeltaScaleFactor));
EXPECT_TRUE(packet->AddReceivedPacket(1, kMaxPositiveTimeDelta));
packet->SetBase(0, Timestamp::Zero());
TimeDelta kMaxPositiveTimeDelta =
std::numeric_limits<int16_t>::max() * TransportFeedback::kDeltaTick;
EXPECT_FALSE(packet->AddReceivedPacket(1, Timestamp::Zero() +
kMaxPositiveTimeDelta +
TransportFeedback::kDeltaTick));
EXPECT_TRUE(
packet->AddReceivedPacket(1, Timestamp::Zero() + kMaxPositiveTimeDelta));
// Too large negative delta.
packet.reset(new TransportFeedback());
packet->SetBase(0, 0);
int64_t kMaxNegativeTimeDelta = std::numeric_limits<int16_t>::min() *
TransportFeedback::kDeltaScaleFactor;
TimeDelta kMaxNegativeTimeDelta =
std::numeric_limits<int16_t>::min() * TransportFeedback::kDeltaTick;
// Use larger base time to avoid kBaseTime + kNegativeDelta to be negative.
Timestamp kBaseTime = Timestamp::Seconds(1'000'000);
packet->SetBase(0, kBaseTime);
EXPECT_FALSE(packet->AddReceivedPacket(
1, kMaxNegativeTimeDelta - TransportFeedback::kDeltaScaleFactor));
EXPECT_TRUE(packet->AddReceivedPacket(1, kMaxNegativeTimeDelta));
// Base time at maximum value.
int64_t kMaxBaseTime =
static_cast<int64_t>(TransportFeedback::kDeltaScaleFactor) * (1L << 8) *
((1L << 23) - 1);
packet.reset(new TransportFeedback());
packet->SetBase(0, kMaxBaseTime);
EXPECT_TRUE(packet->AddReceivedPacket(0, kMaxBaseTime));
// Serialize and de-serialize (verify 24bit parsing).
rtc::Buffer raw_packet = packet->Build();
packet = TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size());
EXPECT_EQ(kMaxBaseTime, packet->GetBaseTimeUs());
// Base time above maximum value.
int64_t kTooLargeBaseTime =
kMaxBaseTime + (TransportFeedback::kDeltaScaleFactor * (1L << 8));
packet.reset(new TransportFeedback());
packet->SetBase(0, kTooLargeBaseTime);
packet->AddReceivedPacket(0, kTooLargeBaseTime);
raw_packet = packet->Build();
packet = TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size());
EXPECT_NE(kTooLargeBaseTime, packet->GetBaseTimeUs());
1, kBaseTime + kMaxNegativeTimeDelta - TransportFeedback::kDeltaTick));
EXPECT_TRUE(packet->AddReceivedPacket(1, kBaseTime + kMaxNegativeTimeDelta));
// TODO(sprang): Once we support max length lower than RTCP length limit,
// add back test for max size in bytes.
}
TEST(RtcpPacketTest, BaseTimeIsConsistentAcrossMultiplePackets) {
constexpr Timestamp kMaxBaseTime =
Timestamp::Zero() + kBaseTimeWrapPeriod - kBaseTimeTick;
TransportFeedback packet1;
packet1.SetBase(0, kMaxBaseTime);
packet1.AddReceivedPacket(0, kMaxBaseTime);
// Build and parse packet to simulate sending it over the wire.
TransportFeedback parsed_packet1 = Parse(packet1.Build());
TransportFeedback packet2;
packet2.SetBase(1, kMaxBaseTime + kBaseTimeTick);
packet2.AddReceivedPacket(1, kMaxBaseTime + kBaseTimeTick);
TransportFeedback parsed_packet2 = Parse(packet2.Build());
EXPECT_EQ(parsed_packet2.GetBaseDelta(parsed_packet1.BaseTime()),
kBaseTimeTick);
}
TEST(RtcpPacketTest, SupportsMaximumNumberOfNegativeDeltas) {
TransportFeedback feedback;
// Use large base time to avoid hitting zero limit while filling the feedback,
// but use multiple of kBaseTimeWrapPeriod to hit edge case where base time
// is recorded as zero in the raw rtcp packet.
Timestamp time = Timestamp::Zero() + 1'000 * kBaseTimeWrapPeriod;
feedback.SetBase(0, time);
static constexpr TimeDelta kMinDelta =
TransportFeedback::kDeltaTick * std::numeric_limits<int16_t>::min();
uint16_t num_received_rtp_packets = 0;
time += kMinDelta;
while (feedback.AddReceivedPacket(++num_received_rtp_packets, time)) {
ASSERT_GE(time, Timestamp::Zero() - kMinDelta);
time += kMinDelta;
}
// Subtract one last packet that failed to add.
--num_received_rtp_packets;
EXPECT_TRUE(feedback.IsConsistent());
TransportFeedback parsed = Parse(feedback.Build());
EXPECT_EQ(parsed.GetReceivedPackets().size(), num_received_rtp_packets);
EXPECT_THAT(parsed.GetReceivedPackets(),
AllOf(SizeIs(num_received_rtp_packets),
Each(Property(&TransportFeedback::ReceivedPacket::delta,
Eq(kMinDelta)))));
EXPECT_GE(parsed.BaseTime(),
Timestamp::Zero() - kMinDelta * num_received_rtp_packets);
}
TEST(RtcpPacketTest, TransportFeedbackPadding) {
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + kSmallDeltaSize;
@ -447,8 +513,8 @@ TEST(RtcpPacketTest, TransportFeedbackPadding) {
4 * kExpectedSizeWords - kExpectedSizeBytes;
TransportFeedback feedback;
feedback.SetBase(0, 0);
EXPECT_TRUE(feedback.AddReceivedPacket(0, 0));
feedback.SetBase(0, Timestamp::Zero());
EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero()));
rtc::Buffer packet = feedback.Build();
EXPECT_EQ(kExpectedSizeWords * 4, packet.size());
@ -475,10 +541,7 @@ TEST(RtcpPacketTest, TransportFeedbackPadding) {
&mod_buffer[2], ByteReader<uint16_t>::ReadBigEndian(&mod_buffer[2]) +
((kPaddingBytes + 3) / 4));
std::unique_ptr<TransportFeedback> parsed_packet(
TransportFeedback::ParseFrom(mod_buffer, kExpectedSizeWithPadding));
ASSERT_TRUE(parsed_packet != nullptr);
EXPECT_EQ(kExpectedSizeWords * 4, packet.size()); // Padding not included.
EXPECT_THAT(mod_buffer, IsValidFeedback());
}
TEST(RtcpPacketTest, TransportFeedbackPaddingBackwardsCompatibility) {
@ -489,8 +552,8 @@ TEST(RtcpPacketTest, TransportFeedbackPaddingBackwardsCompatibility) {
4 * kExpectedSizeWords - kExpectedSizeBytes;
TransportFeedback feedback;
feedback.SetBase(0, 0);
EXPECT_TRUE(feedback.AddReceivedPacket(0, 0));
feedback.SetBase(0, Timestamp::Zero());
EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero()));
rtc::Buffer packet = feedback.Build();
EXPECT_EQ(kExpectedSizeWords * 4, packet.size());
@ -510,47 +573,40 @@ TEST(RtcpPacketTest, TransportFeedbackPaddingBackwardsCompatibility) {
const uint8_t padding_flag = 1 << 5;
mod_buffer[0] &= ~padding_flag; // Unset padding flag.
std::unique_ptr<TransportFeedback> parsed_packet(
TransportFeedback::ParseFrom(mod_buffer, kExpectedSizeWords * 4));
ASSERT_TRUE(parsed_packet != nullptr);
EXPECT_EQ(kExpectedSizeWords * 4, packet.size());
EXPECT_THAT(mod_buffer, IsValidFeedback());
}
TEST(RtcpPacketTest, TransportFeedbackCorrectlySplitsVectorChunks) {
const int kOneBitVectorCapacity = 14;
const int64_t kLargeTimeDelta =
TransportFeedback::kDeltaScaleFactor * (1 << 8);
const TimeDelta kLargeTimeDelta = TransportFeedback::kDeltaTick * (1 << 8);
// Test that a number of small deltas followed by a large delta results in a
// correct split into multiple chunks, as needed.
for (int deltas = 0; deltas <= kOneBitVectorCapacity + 1; ++deltas) {
TransportFeedback feedback;
feedback.SetBase(0, 0);
feedback.SetBase(0, Timestamp::Zero());
for (int i = 0; i < deltas; ++i)
feedback.AddReceivedPacket(i, i * 1000);
feedback.AddReceivedPacket(deltas, deltas * 1000 + kLargeTimeDelta);
feedback.AddReceivedPacket(i, Timestamp::Millis(i));
feedback.AddReceivedPacket(deltas,
Timestamp::Millis(deltas) + kLargeTimeDelta);
rtc::Buffer serialized_packet = feedback.Build();
std::unique_ptr<TransportFeedback> deserialized_packet =
TransportFeedback::ParseFrom(serialized_packet.data(),
serialized_packet.size());
EXPECT_TRUE(deserialized_packet != nullptr);
EXPECT_THAT(feedback.Build(), IsValidFeedback());
}
}
TEST(RtcpPacketTest, TransportFeedbackMoveConstructor) {
const int kSamples = 100;
const int64_t kDelta = TransportFeedback::kDeltaScaleFactor;
const uint16_t kBaseSeqNo = 7531;
const int64_t kBaseTimestampUs = 123456789;
const Timestamp kBaseTimestamp = Timestamp::Micros(123'456'789);
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback;
feedback.SetBase(kBaseSeqNo, kBaseTimestampUs);
feedback.SetBase(kBaseSeqNo, kBaseTimestamp);
feedback.SetFeedbackSequenceNumber(kFeedbackSeqNo);
for (int i = 0; i < kSamples; ++i) {
feedback.AddReceivedPacket(kBaseSeqNo + i, kBaseTimestampUs + i * kDelta);
feedback.AddReceivedPacket(
kBaseSeqNo + i, kBaseTimestamp + i * TransportFeedback::kDeltaTick);
}
EXPECT_TRUE(feedback.IsConsistent());
@ -567,49 +623,42 @@ TEST(RtcpPacketTest, TransportFeedbackMoveConstructor) {
TEST(TransportFeedbackTest, ReportsMissingPackets) {
const uint16_t kBaseSeqNo = 1000;
const int64_t kBaseTimestampUs = 10000;
const Timestamp kBaseTimestamp = Timestamp::Millis(10);
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback_builder(/*include_timestamps*/ true);
feedback_builder.SetBase(kBaseSeqNo, kBaseTimestampUs);
feedback_builder.SetBase(kBaseSeqNo, kBaseTimestamp);
feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, kBaseTimestampUs);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, kBaseTimestamp);
// Packet losses indicated by jump in sequence number.
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, kBaseTimestampUs + 2000);
rtc::Buffer coded = feedback_builder.Build();
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3,
kBaseTimestamp + TimeDelta::Millis(2));
rtcp::CommonHeader header;
header.Parse(coded.data(), coded.size());
TransportFeedback feedback(/*include_timestamps*/ true,
/*include_lost*/ true);
feedback.Parse(header);
auto packets = feedback.GetAllPackets();
EXPECT_TRUE(packets[0].received());
EXPECT_FALSE(packets[1].received());
EXPECT_FALSE(packets[2].received());
EXPECT_TRUE(packets[3].received());
EXPECT_THAT(
Parse(feedback_builder.Build()).GetAllPackets(),
ElementsAre(
Property(&TransportFeedback::ReceivedPacket::received, true),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, true)));
}
TEST(TransportFeedbackTest, ReportsMissingPacketsWithoutTimestamps) {
const uint16_t kBaseSeqNo = 1000;
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback_builder(/*include_timestamps*/ false);
feedback_builder.SetBase(kBaseSeqNo, 10000);
feedback_builder.SetBase(kBaseSeqNo, Timestamp::Millis(10));
feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, /*timestamp_us*/ 0);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, Timestamp::Zero());
// Packet losses indicated by jump in sequence number.
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, /*timestamp_us*/ 0);
rtc::Buffer coded = feedback_builder.Build();
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, Timestamp::Zero());
rtcp::CommonHeader header;
header.Parse(coded.data(), coded.size());
TransportFeedback feedback(/*include_timestamps*/ true,
/*include_lost*/ true);
feedback.Parse(header);
auto packets = feedback.GetAllPackets();
EXPECT_TRUE(packets[0].received());
EXPECT_FALSE(packets[1].received());
EXPECT_FALSE(packets[2].received());
EXPECT_TRUE(packets[3].received());
EXPECT_THAT(
Parse(feedback_builder.Build()).GetAllPackets(),
ElementsAre(
Property(&TransportFeedback::ReceivedPacket::received, true),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, true)));
}
} // namespace
} // namespace webrtc