webrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc
Taylor Brandstetter 84916937b7 Update packetsLost and jitter stats any time a packet is received.
Before this CL, the packetsLost and jitter stats (as returned by
GetStats, at the API level) were only being updated when an RTCP SR or
RR is generated. According to the stats spec, "local" stats like this
should be updated any time a packet is received.

This CL also fixes some minor issues with the calculation of packetsLost
(and fractionLost):
* Packets weren't being count as lost if lost over a sequence number
  rollover.
* Temporary periods of "negative" loss (caused by duplicate or out of
  order packets) weren't being accumulated into the cumulative loss
  counter. Example:
  Period 1: Received packets 1, 2, 4
    Loss over that period: 1 (expected 4 packets, got 3)
    Reported cumulative loss: 1
  Period 2: Received packets 3, 5
    Loss over that period: -1 (expected 1 packet, got 2)
    Reported cumulative loss: 1 (should be 0!)

Landing with NOTRY because Android compile bots are broken for an
unrelated reason.
NOTRY=True

Bug: webrtc:8804
Change-Id: I840ba34de8957b1276f6bdaf93718f805629f5c8
Reviewed-on: https://webrtc-review.googlesource.com/50020
Commit-Queue: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Oskar Sundbom <ossu@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23731}
2018-06-25 23:56:39 +00:00

804 lines
32 KiB
C++

/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <vector>
#include "modules/rtp_rtcp/include/receive_statistics.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::_;
using ::testing::SaveArg;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
const size_t kPacketSize1 = 100;
const size_t kPacketSize2 = 300;
const uint32_t kSsrc1 = 101;
const uint32_t kSsrc2 = 202;
const uint32_t kSsrc3 = 203;
const uint32_t kSsrc4 = 304;
RTPHeader CreateRtpHeader(uint32_t ssrc) {
RTPHeader header;
memset(&header, 0, sizeof(header));
header.ssrc = ssrc;
header.sequenceNumber = 100;
return header;
}
class ReceiveStatisticsTest : public ::testing::Test {
public:
ReceiveStatisticsTest()
: clock_(0), receive_statistics_(ReceiveStatistics::Create(&clock_)) {
header1_ = CreateRtpHeader(kSsrc1);
header2_ = CreateRtpHeader(kSsrc2);
}
protected:
SimulatedClock clock_;
std::unique_ptr<ReceiveStatistics> receive_statistics_;
RTPHeader header1_;
RTPHeader header2_;
};
TEST_F(ReceiveStatisticsTest, TwoIncomingSsrcs) {
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
++header1_.sequenceNumber;
receive_statistics_->IncomingPacket(header2_, kPacketSize2, false);
++header2_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(100);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
++header1_.sequenceNumber;
receive_statistics_->IncomingPacket(header2_, kPacketSize2, false);
++header2_.sequenceNumber;
StreamStatistician* statistician =
receive_statistics_->GetStatistician(kSsrc1);
ASSERT_TRUE(statistician != NULL);
EXPECT_GT(statistician->BitrateReceived(), 0u);
size_t bytes_received = 0;
uint32_t packets_received = 0;
statistician->GetDataCounters(&bytes_received, &packets_received);
EXPECT_EQ(200u, bytes_received);
EXPECT_EQ(2u, packets_received);
statistician = receive_statistics_->GetStatistician(kSsrc2);
ASSERT_TRUE(statistician != NULL);
EXPECT_GT(statistician->BitrateReceived(), 0u);
statistician->GetDataCounters(&bytes_received, &packets_received);
EXPECT_EQ(600u, bytes_received);
EXPECT_EQ(2u, packets_received);
EXPECT_EQ(2u, receive_statistics_->RtcpReportBlocks(3).size());
// Add more incoming packets and verify that they are registered in both
// access methods.
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
++header1_.sequenceNumber;
receive_statistics_->IncomingPacket(header2_, kPacketSize2, false);
++header2_.sequenceNumber;
receive_statistics_->GetStatistician(kSsrc1)->GetDataCounters(
&bytes_received, &packets_received);
EXPECT_EQ(300u, bytes_received);
EXPECT_EQ(3u, packets_received);
receive_statistics_->GetStatistician(kSsrc2)->GetDataCounters(
&bytes_received, &packets_received);
EXPECT_EQ(900u, bytes_received);
EXPECT_EQ(3u, packets_received);
}
TEST_F(ReceiveStatisticsTest,
RtcpReportBlocksReturnsMaxBlocksWhenThereAreMoreStatisticians) {
RTPHeader header1 = CreateRtpHeader(kSsrc1);
RTPHeader header2 = CreateRtpHeader(kSsrc2);
RTPHeader header3 = CreateRtpHeader(kSsrc3);
receive_statistics_->IncomingPacket(header1, kPacketSize1, false);
receive_statistics_->IncomingPacket(header2, kPacketSize1, false);
receive_statistics_->IncomingPacket(header3, kPacketSize1, false);
EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2));
EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2));
EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2));
}
TEST_F(ReceiveStatisticsTest,
RtcpReportBlocksReturnsAllObservedSsrcsWithMultipleCalls) {
RTPHeader header1 = CreateRtpHeader(kSsrc1);
RTPHeader header2 = CreateRtpHeader(kSsrc2);
RTPHeader header3 = CreateRtpHeader(kSsrc3);
RTPHeader header4 = CreateRtpHeader(kSsrc4);
receive_statistics_->IncomingPacket(header1, kPacketSize1, false);
receive_statistics_->IncomingPacket(header2, kPacketSize1, false);
receive_statistics_->IncomingPacket(header3, kPacketSize1, false);
receive_statistics_->IncomingPacket(header4, kPacketSize1, false);
std::vector<uint32_t> observed_ssrcs;
std::vector<rtcp::ReportBlock> report_blocks =
receive_statistics_->RtcpReportBlocks(2);
ASSERT_THAT(report_blocks, SizeIs(2));
observed_ssrcs.push_back(report_blocks[0].source_ssrc());
observed_ssrcs.push_back(report_blocks[1].source_ssrc());
report_blocks = receive_statistics_->RtcpReportBlocks(2);
ASSERT_THAT(report_blocks, SizeIs(2));
observed_ssrcs.push_back(report_blocks[0].source_ssrc());
observed_ssrcs.push_back(report_blocks[1].source_ssrc());
EXPECT_THAT(observed_ssrcs,
UnorderedElementsAre(kSsrc1, kSsrc2, kSsrc3, kSsrc4));
}
TEST_F(ReceiveStatisticsTest, ActiveStatisticians) {
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(1000);
receive_statistics_->IncomingPacket(header2_, kPacketSize2, false);
++header2_.sequenceNumber;
// Nothing should time out since only 1000 ms has passed since the first
// packet came in.
EXPECT_EQ(2u, receive_statistics_->RtcpReportBlocks(3).size());
clock_.AdvanceTimeMilliseconds(7000);
// kSsrc1 should have timed out.
EXPECT_EQ(1u, receive_statistics_->RtcpReportBlocks(3).size());
clock_.AdvanceTimeMilliseconds(1000);
// kSsrc2 should have timed out.
EXPECT_EQ(0u, receive_statistics_->RtcpReportBlocks(3).size());
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
++header1_.sequenceNumber;
// kSsrc1 should be active again and the data counters should have survived.
EXPECT_EQ(1u, receive_statistics_->RtcpReportBlocks(3).size());
StreamStatistician* statistician =
receive_statistics_->GetStatistician(kSsrc1);
ASSERT_TRUE(statistician != NULL);
size_t bytes_received = 0;
uint32_t packets_received = 0;
statistician->GetDataCounters(&bytes_received, &packets_received);
EXPECT_EQ(200u, bytes_received);
EXPECT_EQ(2u, packets_received);
}
TEST_F(ReceiveStatisticsTest, GetReceiveStreamDataCounters) {
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
StreamStatistician* statistician =
receive_statistics_->GetStatistician(kSsrc1);
ASSERT_TRUE(statistician != NULL);
StreamDataCounters counters;
statistician->GetReceiveStreamDataCounters(&counters);
EXPECT_GT(counters.first_packet_time_ms, -1);
EXPECT_EQ(1u, counters.transmitted.packets);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
statistician->GetReceiveStreamDataCounters(&counters);
EXPECT_GT(counters.first_packet_time_ms, -1);
EXPECT_EQ(2u, counters.transmitted.packets);
}
class MockRtcpCallback : public RtcpStatisticsCallback {
public:
MOCK_METHOD2(StatisticsUpdated,
void(const RtcpStatistics& statistics, uint32_t ssrc));
MOCK_METHOD2(CNameChanged, void(const char* cname, uint32_t ssrc));
};
// Test that the RTCP statistics callback is invoked every time a packet is
// received (so that at the application level, GetStats will return up-to-date
// stats, not just stats from the last generated RTCP SR or RR).
TEST_F(ReceiveStatisticsTest,
RtcpStatisticsCallbackInvokedForEveryPacketReceived) {
MockRtcpCallback callback;
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
// Just receive the same packet multiple times; doesn't really matter for the
// purposes of this test.
EXPECT_CALL(callback, StatisticsUpdated(_, _)).Times(3);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
}
// The callback should also be invoked when |fraction_lost| is updated due to
// GetStatistics being called.
TEST_F(ReceiveStatisticsTest,
RtcpStatisticsCallbackInvokedWhenFractionLostUpdated) {
MockRtcpCallback callback;
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
EXPECT_CALL(callback, StatisticsUpdated(_, _)).Times(2);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// This just returns the current statistics without updating anything, so no
// need to invoke the callback.
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/false);
// Update fraction lost, expecting a new callback.
EXPECT_CALL(callback, StatisticsUpdated(_, _)).Times(1);
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
}
TEST_F(ReceiveStatisticsTest,
RtcpStatisticsCallbackNotInvokedAfterDeregistered) {
// Register the callback and receive a couple packets.
MockRtcpCallback callback;
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
EXPECT_CALL(callback, StatisticsUpdated(_, _)).Times(2);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Dereigster the callback. Neither receiving a packet nor generating a
// report (calling GetStatistics) should result in another callback.
receive_statistics_->RegisterRtcpStatisticsCallback(nullptr);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
}
// Test that the RtcpStatisticsCallback sees the exact same values as returned
// from GetStatistics.
TEST_F(ReceiveStatisticsTest,
RtcpStatisticsFromCallbackMatchThoseFromGetStatistics) {
MockRtcpCallback callback;
RtcpStatistics stats_from_callback;
EXPECT_CALL(callback, StatisticsUpdated(_, _))
.WillRepeatedly(SaveArg<0>(&stats_from_callback));
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
// Using units of milliseconds.
header1_.payload_type_frequency = 1000;
// Add some arbitrary data, with loss and jitter.
header1_.sequenceNumber = 1;
clock_.AdvanceTimeMilliseconds(7);
header1_.timestamp += 3;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber += 2;
clock_.AdvanceTimeMilliseconds(9);
header1_.timestamp += 9;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
--header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(13);
header1_.timestamp += 47;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, true);
header1_.sequenceNumber += 3;
clock_.AdvanceTimeMilliseconds(11);
header1_.timestamp += 17;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// The stats from the last callback due to IncomingPacket should match
// those returned by GetStatistics afterwards.
RtcpStatistics stats_from_getstatistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&stats_from_getstatistics, /*update_fraction_lost=*/false);
EXPECT_EQ(stats_from_getstatistics.packets_lost,
stats_from_callback.packets_lost);
EXPECT_EQ(stats_from_getstatistics.extended_highest_sequence_number,
stats_from_callback.extended_highest_sequence_number);
EXPECT_EQ(stats_from_getstatistics.fraction_lost,
stats_from_callback.fraction_lost);
EXPECT_EQ(stats_from_getstatistics.jitter, stats_from_callback.jitter);
// Now update fraction lost, and check that we got matching values from the
// new callback.
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&stats_from_getstatistics, /*update_fraction_lost=*/true);
EXPECT_EQ(stats_from_getstatistics.packets_lost,
stats_from_callback.packets_lost);
EXPECT_EQ(stats_from_getstatistics.extended_highest_sequence_number,
stats_from_callback.extended_highest_sequence_number);
EXPECT_EQ(stats_from_getstatistics.fraction_lost,
stats_from_callback.fraction_lost);
EXPECT_EQ(stats_from_getstatistics.jitter, stats_from_callback.jitter);
}
// Test that |fraction_lost| is only updated when a report is generated (when
// GetStatistics is called with |update_fraction_lost| set to true). Meaning
// that it will always represent a value computed between two RTCP SR or RRs.
TEST_F(ReceiveStatisticsTest, FractionLostOnlyUpdatedWhenReportGenerated) {
MockRtcpCallback callback;
RtcpStatistics stats_from_callback;
EXPECT_CALL(callback, StatisticsUpdated(_, _))
.WillRepeatedly(SaveArg<0>(&stats_from_callback));
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
// Simulate losing one packet.
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 2;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 4;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Haven't generated a report yet, so |fraction_lost| should still be 0.
EXPECT_EQ(0u, stats_from_callback.fraction_lost);
// Call GetStatistics with |update_fraction_lost| set to false; should be a
// no-op.
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/false);
EXPECT_EQ(0u, stats_from_callback.fraction_lost);
// Call GetStatistics with |update_fraction_lost| set to true, simulating a
// report being generated.
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 25% = 63/255.
EXPECT_EQ(63u, stats_from_callback.fraction_lost);
// Lose another packet.
header1_.sequenceNumber = 6;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Should return same value as before since we haven't generated a new report
// yet.
EXPECT_EQ(63u, stats_from_callback.fraction_lost);
// Simulate another report being generated.
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 50% = 127/255.
EXPECT_EQ(127, stats_from_callback.fraction_lost);
}
// Simple test for fraction/cumulative loss computation, with only loss, no
// duplicates or reordering.
TEST_F(ReceiveStatisticsTest, SimpleLossComputation) {
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 3;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 4;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 5;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 20% = 51/255.
EXPECT_EQ(51u, statistics.fraction_lost);
EXPECT_EQ(1, statistics.packets_lost);
}
// Test that fraction/cumulative loss is computed correctly when there's some
// reordering.
TEST_F(ReceiveStatisticsTest, LossComputationWithReordering) {
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 3;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 2;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 5;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 20% = 51/255.
EXPECT_EQ(51u, statistics.fraction_lost);
}
// Somewhat unintuitively, duplicate packets count against lost packets
// according to RFC3550.
TEST_F(ReceiveStatisticsTest, LossComputationWithDuplicates) {
// Lose 2 packets, but also receive 1 duplicate. Should actually count as
// only 1 packet being lost.
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 4;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 4;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 5;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 20% = 51/255.
EXPECT_EQ(51u, statistics.fraction_lost);
EXPECT_EQ(1, statistics.packets_lost);
}
// Test that sequence numbers wrapping around doesn't screw up loss
// computations.
TEST_F(ReceiveStatisticsTest, LossComputationWithSequenceNumberWrapping) {
// First, test loss computation over a period that included a sequence number
// rollover.
header1_.sequenceNumber = 65533;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 0;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 65534;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Only one packet was actually lost, 65535.
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 20% = 51/255.
EXPECT_EQ(51u, statistics.fraction_lost);
EXPECT_EQ(1, statistics.packets_lost);
// Now test losing one packet *after* the rollover.
header1_.sequenceNumber = 3;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 50% = 127/255.
EXPECT_EQ(127u, statistics.fraction_lost);
EXPECT_EQ(2, statistics.packets_lost);
}
// Somewhat unintuitively, since duplicate packets count against loss, you can
// actually end up with negative loss. |fraction_lost| should be clamped to
// zero in this case, since it's signed, while |packets_lost| is signed so it
// should be negative.
TEST_F(ReceiveStatisticsTest, NegativeLoss) {
// Receive one packet and simulate a report being generated by calling
// GetStatistics, to establish a baseline for |fraction_lost|.
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// Receive some duplicate packets. Results in "negative" loss, since
// "expected packets since last report" is 3 and "received" is 4, and 3 minus
// 4 is -1. See RFC3550 Appendix A.3.
header1_.sequenceNumber = 4;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 2;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 2;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
header1_.sequenceNumber = 2;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
EXPECT_EQ(0u, statistics.fraction_lost);
EXPECT_EQ(-1, statistics.packets_lost);
// Lose 2 packets; now cumulative loss should become positive again.
header1_.sequenceNumber = 7;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/true);
// 66% = 170/255.
EXPECT_EQ(170u, statistics.fraction_lost);
EXPECT_EQ(1, statistics.packets_lost);
}
// Since cumulative loss is carried in a signed 24-bit field, it should be
// clamped to 0x7fffff in the positive direction, 0x800000 in the negative
// direction.
TEST_F(ReceiveStatisticsTest, PositiveCumulativeLossClamped) {
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Lose 2^23 packets, expecting loss to be clamped to 2^23-1.
for (int i = 0; i < 0x800000; ++i) {
header1_.sequenceNumber = (header1_.sequenceNumber + 2 % 65536);
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
}
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/false);
EXPECT_EQ(0x7fffff, statistics.packets_lost);
}
TEST_F(ReceiveStatisticsTest, NegativeCumulativeLossClamped) {
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
// Receive 2^23+1 duplicate packets (counted as negative loss), expecting
// loss to be clamped to -2^23.
for (int i = 0; i < 0x800001; ++i) {
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
}
RtcpStatistics statistics;
receive_statistics_->GetStatistician(kSsrc1)->GetStatistics(
&statistics, /*update_fraction_lost=*/false);
EXPECT_EQ(-0x800000, statistics.packets_lost);
}
// Test that the extended highest sequence number is computed correctly when
// sequence numbers wrap around or packets are received out of order.
TEST_F(ReceiveStatisticsTest, ExtendedHighestSequenceNumberComputation) {
MockRtcpCallback callback;
RtcpStatistics stats_from_callback;
EXPECT_CALL(callback, StatisticsUpdated(_, _))
.WillRepeatedly(SaveArg<0>(&stats_from_callback));
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
header1_.sequenceNumber = 65535;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(65535u, stats_from_callback.extended_highest_sequence_number);
// Wrap around.
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(65536u + 1u, stats_from_callback.extended_highest_sequence_number);
// Should be treated as out of order; shouldn't increment highest extended
// sequence number.
header1_.sequenceNumber = 65530;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(65536u + 1u, stats_from_callback.extended_highest_sequence_number);
// Receive a couple packets then wrap around again.
// TODO(bugs.webrtc.org/9445): With large jumps like this, RFC3550 suggests
// for the receiver to assume the other side restarted, and reset all its
// sequence number counters. Why aren't we doing this?
header1_.sequenceNumber = 30000;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(65536u + 30000u,
stats_from_callback.extended_highest_sequence_number);
header1_.sequenceNumber = 50000;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(65536u + 50000u,
stats_from_callback.extended_highest_sequence_number);
header1_.sequenceNumber = 10000;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(2 * 65536u + 10000u,
stats_from_callback.extended_highest_sequence_number);
// If a packet is received more than "MaxReorderingThreshold" packets out of
// order (defaults to 50), it's assumed to be in order.
// TODO(bugs.webrtc.org/9445): RFC3550 would recommend treating this as a
// restart as mentioned above.
header1_.sequenceNumber = 9900;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(3 * 65536u + 9900u,
stats_from_callback.extended_highest_sequence_number);
}
// Test jitter computation with no loss/reordering/etc.
TEST_F(ReceiveStatisticsTest, SimpleJitterComputation) {
MockRtcpCallback callback;
RtcpStatistics stats_from_callback;
EXPECT_CALL(callback, StatisticsUpdated(_, _))
.WillRepeatedly(SaveArg<0>(&stats_from_callback));
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
// Using units of milliseconds.
header1_.payload_type_frequency = 1000;
// Regardless of initial timestamps, jitter should start at 0.
header1_.sequenceNumber = 1;
clock_.AdvanceTimeMilliseconds(7);
header1_.timestamp += 3;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(0u, stats_from_callback.jitter);
// Incrementing timestamps by the same amount shouldn't increase jitter.
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(50);
header1_.timestamp += 50;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(0u, stats_from_callback.jitter);
// Difference of 16ms, divided by 16 yields exactly 1.
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(32);
header1_.timestamp += 16;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, true);
EXPECT_EQ(1u, stats_from_callback.jitter);
// (90 + 1 * 15) / 16 = 6.5625; should round down to 6.
// TODO(deadbeef): Why don't we round to the nearest integer?
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(10);
header1_.timestamp += 100;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, true);
EXPECT_EQ(6u, stats_from_callback.jitter);
// (30 + 6.5625 * 15) / 16 = 8.0273; should round down to 8.
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(50);
header1_.timestamp += 20;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, true);
EXPECT_EQ(8u, stats_from_callback.jitter);
}
// TODO(deadbeef): Why do we do this? It goes against RFC3550, which explicitly
// says the calculation should be based on order of arrival and packets may not
// necessarily arrive in sequence.
TEST_F(ReceiveStatisticsTest, JitterComputationIgnoresReorderedPackets) {
MockRtcpCallback callback;
RtcpStatistics stats_from_callback;
EXPECT_CALL(callback, StatisticsUpdated(_, _))
.WillRepeatedly(SaveArg<0>(&stats_from_callback));
receive_statistics_->RegisterRtcpStatisticsCallback(&callback);
// Using units of milliseconds.
header1_.payload_type_frequency = 1000;
// Regardless of initial timestamps, jitter should start at 0.
header1_.sequenceNumber = 1;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(0u, stats_from_callback.jitter);
// This should be ignored, even though there's a difference of 70 here.
header1_.sequenceNumber = 0;
clock_.AdvanceTimeMilliseconds(50);
header1_.timestamp -= 20;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(0u, stats_from_callback.jitter);
// Relative to the first packet there's a difference of 181ms in arrival
// time, 20ms in timestamp, so jitter should be 161/16 = 10.
header1_.sequenceNumber = 2;
clock_.AdvanceTimeMilliseconds(131);
header1_.timestamp += 40;
receive_statistics_->IncomingPacket(header1_, kPacketSize1, false);
EXPECT_EQ(10u, stats_from_callback.jitter);
}
class RtpTestCallback : public StreamDataCountersCallback {
public:
RtpTestCallback()
: StreamDataCountersCallback(), num_calls_(0), ssrc_(0), stats_() {}
~RtpTestCallback() override = default;
void DataCountersUpdated(const StreamDataCounters& counters,
uint32_t ssrc) override {
ssrc_ = ssrc;
stats_ = counters;
++num_calls_;
}
void MatchPacketCounter(const RtpPacketCounter& expected,
const RtpPacketCounter& actual) {
EXPECT_EQ(expected.payload_bytes, actual.payload_bytes);
EXPECT_EQ(expected.header_bytes, actual.header_bytes);
EXPECT_EQ(expected.padding_bytes, actual.padding_bytes);
EXPECT_EQ(expected.packets, actual.packets);
}
void Matches(uint32_t num_calls,
uint32_t ssrc,
const StreamDataCounters& expected) {
EXPECT_EQ(num_calls, num_calls_);
EXPECT_EQ(ssrc, ssrc_);
MatchPacketCounter(expected.transmitted, stats_.transmitted);
MatchPacketCounter(expected.retransmitted, stats_.retransmitted);
MatchPacketCounter(expected.fec, stats_.fec);
}
uint32_t num_calls_;
uint32_t ssrc_;
StreamDataCounters stats_;
};
TEST_F(ReceiveStatisticsTest, RtpCallbacks) {
RtpTestCallback callback;
receive_statistics_->RegisterRtpStatisticsCallback(&callback);
const size_t kHeaderLength = 20;
const size_t kPaddingLength = 9;
// One packet of size kPacketSize1.
header1_.headerLength = kHeaderLength;
receive_statistics_->IncomingPacket(header1_, kPacketSize1 + kHeaderLength,
false);
StreamDataCounters expected;
expected.transmitted.payload_bytes = kPacketSize1;
expected.transmitted.header_bytes = kHeaderLength;
expected.transmitted.padding_bytes = 0;
expected.transmitted.packets = 1;
expected.retransmitted.payload_bytes = 0;
expected.retransmitted.header_bytes = 0;
expected.retransmitted.padding_bytes = 0;
expected.retransmitted.packets = 0;
expected.fec.packets = 0;
callback.Matches(1, kSsrc1, expected);
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(5);
header1_.paddingLength = 9;
// Another packet of size kPacketSize1 with 9 bytes padding.
receive_statistics_->IncomingPacket(
header1_, kPacketSize1 + kHeaderLength + kPaddingLength, false);
expected.transmitted.payload_bytes = kPacketSize1 * 2;
expected.transmitted.header_bytes = kHeaderLength * 2;
expected.transmitted.padding_bytes = kPaddingLength;
expected.transmitted.packets = 2;
callback.Matches(2, kSsrc1, expected);
clock_.AdvanceTimeMilliseconds(5);
// Retransmit last packet.
receive_statistics_->IncomingPacket(
header1_, kPacketSize1 + kHeaderLength + kPaddingLength, true);
expected.transmitted.payload_bytes = kPacketSize1 * 3;
expected.transmitted.header_bytes = kHeaderLength * 3;
expected.transmitted.padding_bytes = kPaddingLength * 2;
expected.transmitted.packets = 3;
expected.retransmitted.payload_bytes = kPacketSize1;
expected.retransmitted.header_bytes = kHeaderLength;
expected.retransmitted.padding_bytes = kPaddingLength;
expected.retransmitted.packets = 1;
callback.Matches(3, kSsrc1, expected);
header1_.paddingLength = 0;
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(5);
// One FEC packet.
receive_statistics_->IncomingPacket(header1_, kPacketSize1 + kHeaderLength,
false);
receive_statistics_->FecPacketReceived(header1_,
kPacketSize1 + kHeaderLength);
expected.transmitted.payload_bytes = kPacketSize1 * 4;
expected.transmitted.header_bytes = kHeaderLength * 4;
expected.transmitted.packets = 4;
expected.fec.payload_bytes = kPacketSize1;
expected.fec.header_bytes = kHeaderLength;
expected.fec.packets = 1;
callback.Matches(5, kSsrc1, expected);
receive_statistics_->RegisterRtpStatisticsCallback(NULL);
// New stats, but callback should not be called.
++header1_.sequenceNumber;
clock_.AdvanceTimeMilliseconds(5);
receive_statistics_->IncomingPacket(header1_, kPacketSize1 + kHeaderLength,
true);
callback.Matches(5, kSsrc1, expected);
}
TEST_F(ReceiveStatisticsTest, RtpCallbacksFecFirst) {
RtpTestCallback callback;
receive_statistics_->RegisterRtpStatisticsCallback(&callback);
const uint32_t kHeaderLength = 20;
header1_.headerLength = kHeaderLength;
// If first packet is FEC, ignore it.
receive_statistics_->FecPacketReceived(header1_,
kPacketSize1 + kHeaderLength);
EXPECT_EQ(0u, callback.num_calls_);
receive_statistics_->IncomingPacket(header1_, kPacketSize1 + kHeaderLength,
false);
StreamDataCounters expected;
expected.transmitted.payload_bytes = kPacketSize1;
expected.transmitted.header_bytes = kHeaderLength;
expected.transmitted.padding_bytes = 0;
expected.transmitted.packets = 1;
expected.fec.packets = 0;
callback.Matches(1, kSsrc1, expected);
receive_statistics_->FecPacketReceived(header1_,
kPacketSize1 + kHeaderLength);
expected.fec.payload_bytes = kPacketSize1;
expected.fec.header_bytes = kHeaderLength;
expected.fec.packets = 1;
callback.Matches(2, kSsrc1, expected);
}
} // namespace
} // namespace webrtc