203 lines
8.8 KiB
C++
203 lines
8.8 KiB
C++
// Copyright 2019 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "cast/streaming/packet_receive_stats_tracker.h"
|
|
|
|
#include <chrono>
|
|
#include <limits>
|
|
|
|
#include "cast/streaming/constants.h"
|
|
#include "gtest/gtest.h"
|
|
#include "util/chrono_helpers.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
namespace {
|
|
|
|
// Returns a RtcpReportBlock with all fields set to known values to see how the
|
|
// fields are modified by functions called during the tests.
|
|
RtcpReportBlock GetSentinel() {
|
|
RtcpReportBlock report;
|
|
report.ssrc = Ssrc{0x1337beef};
|
|
report.packet_fraction_lost_numerator = -999;
|
|
report.cumulative_packets_lost = -0x1337cafe;
|
|
report.extended_high_sequence_number = 0x98765432;
|
|
report.jitter =
|
|
RtpTimeDelta::FromTicks(std::numeric_limits<int64_t>::max() - 42);
|
|
report.last_status_report_id = StatusReportId{2222222222};
|
|
report.delay_since_last_report = RtcpReportBlock::Delay(-0x3550641);
|
|
return report;
|
|
}
|
|
|
|
// Run gtest expectations, that no fields were changed.
|
|
#define EXPECT_FIELDS_NOT_POPULATED(x) \
|
|
do { \
|
|
const RtcpReportBlock sentinel = GetSentinel(); \
|
|
EXPECT_EQ(sentinel.ssrc, (x).ssrc); \
|
|
EXPECT_EQ(sentinel.packet_fraction_lost_numerator, \
|
|
(x).packet_fraction_lost_numerator); \
|
|
EXPECT_EQ(sentinel.cumulative_packets_lost, (x).cumulative_packets_lost); \
|
|
EXPECT_EQ(sentinel.extended_high_sequence_number, \
|
|
(x).extended_high_sequence_number); \
|
|
EXPECT_EQ(sentinel.jitter, (x).jitter); \
|
|
EXPECT_EQ(sentinel.last_status_report_id, (x).last_status_report_id); \
|
|
EXPECT_EQ(sentinel.delay_since_last_report, (x).delay_since_last_report); \
|
|
} while (false)
|
|
|
|
// Run gtest expectations, that only the fields changed by
|
|
// PacketReceiveStatsTracker::PopulateNextReport() were changed.
|
|
#define EXPECT_FIELDS_POPULATED(x) \
|
|
do { \
|
|
const RtcpReportBlock sentinel = GetSentinel(); \
|
|
/* Fields that should remain untouched by PopulateNextReport(). */ \
|
|
EXPECT_EQ(sentinel.ssrc, (x).ssrc); \
|
|
EXPECT_EQ(sentinel.last_status_report_id, (x).last_status_report_id); \
|
|
EXPECT_EQ(sentinel.delay_since_last_report, (x).delay_since_last_report); \
|
|
/* Fields that should have changed.*/ \
|
|
EXPECT_NE(sentinel.packet_fraction_lost_numerator, \
|
|
(x).packet_fraction_lost_numerator); \
|
|
EXPECT_NE(sentinel.cumulative_packets_lost, (x).cumulative_packets_lost); \
|
|
EXPECT_NE(sentinel.extended_high_sequence_number, \
|
|
(x).extended_high_sequence_number); \
|
|
EXPECT_NE(sentinel.jitter, (x).jitter); \
|
|
} while (false)
|
|
|
|
TEST(PacketReceiveStatsTrackerTest, DoesNotPopulateReportWithoutData) {
|
|
PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
|
|
RtcpReportBlock report = GetSentinel();
|
|
tracker.PopulateNextReport(&report);
|
|
EXPECT_FIELDS_NOT_POPULATED(report);
|
|
}
|
|
|
|
TEST(PacketReceiveStatsTrackerTest, PopulatesReportWithOnePacketTracked) {
|
|
constexpr uint16_t kSequenceNumber = 1234;
|
|
constexpr RtpTimeTicks kRtpTimestamp =
|
|
RtpTimeTicks() + RtpTimeDelta::FromTicks(42);
|
|
constexpr auto kArrivalTime = Clock::time_point() + seconds(3600);
|
|
|
|
PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
|
|
tracker.OnReceivedValidRtpPacket(kSequenceNumber, kRtpTimestamp,
|
|
kArrivalTime);
|
|
|
|
RtcpReportBlock report = GetSentinel();
|
|
tracker.PopulateNextReport(&report);
|
|
EXPECT_FIELDS_POPULATED(report);
|
|
EXPECT_EQ(0, report.packet_fraction_lost_numerator);
|
|
EXPECT_EQ(0, report.cumulative_packets_lost);
|
|
EXPECT_EQ(kSequenceNumber, report.extended_high_sequence_number);
|
|
EXPECT_EQ(RtpTimeDelta(), report.jitter);
|
|
}
|
|
|
|
TEST(PacketReceiveStatsTrackerTest, WhenReceivingAllPackets) {
|
|
// Set the first sequence number such that wraparound is going to be tested.
|
|
constexpr uint16_t kFirstSequenceNumber =
|
|
std::numeric_limits<uint16_t>::max() - 2;
|
|
constexpr RtpTimeTicks kFirstRtpTimestamp =
|
|
RtpTimeTicks() + RtpTimeDelta::FromTicks(42);
|
|
constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(3600);
|
|
|
|
PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
|
|
|
|
// Record 10 packets arrived exactly one second apart with media timestamps
|
|
// also exactly one second apart.
|
|
for (int i = 0; i < 10; ++i) {
|
|
tracker.OnReceivedValidRtpPacket(
|
|
kFirstSequenceNumber + i,
|
|
kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i,
|
|
kFirstArrivalTime + seconds(i));
|
|
}
|
|
|
|
RtcpReportBlock report = GetSentinel();
|
|
tracker.PopulateNextReport(&report);
|
|
EXPECT_FIELDS_POPULATED(report);
|
|
|
|
// Nothing should indicate to the tracker that any packets were dropped.
|
|
EXPECT_EQ(0, report.packet_fraction_lost_numerator);
|
|
EXPECT_EQ(0, report.cumulative_packets_lost);
|
|
|
|
// The |extended_high_sequence_number| should reflect the wraparound of the
|
|
// 16-bit counter value.
|
|
EXPECT_EQ(uint32_t{65542}, report.extended_high_sequence_number);
|
|
|
|
// There should be zero jitter, based on the timing information that was given
|
|
// for each RTP packet.
|
|
EXPECT_EQ(RtpTimeDelta(), report.jitter);
|
|
}
|
|
|
|
TEST(PacketReceiveStatsTrackerTest, WhenReceivingAboutHalfThePackets) {
|
|
constexpr uint16_t kFirstSequenceNumber = 3;
|
|
constexpr RtpTimeTicks kFirstRtpTimestamp =
|
|
RtpTimeTicks() + RtpTimeDelta::FromTicks(99);
|
|
constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(8888);
|
|
|
|
PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
|
|
|
|
// Record 10 packet arrivals whose sequence numbers step by 2, which should
|
|
// indicate half of the packets didn't arrive.
|
|
//
|
|
// Ten arrived: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19
|
|
// Nine inferred missing: 2, 4, 6, 8, 10, 12, 14, 16, 18
|
|
for (int i = 0; i < 10; ++i) {
|
|
tracker.OnReceivedValidRtpPacket(
|
|
kFirstSequenceNumber + (i * 2 + 1),
|
|
kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i,
|
|
kFirstArrivalTime + seconds(i));
|
|
}
|
|
|
|
RtcpReportBlock report = GetSentinel();
|
|
tracker.PopulateNextReport(&report);
|
|
EXPECT_FIELDS_POPULATED(report);
|
|
EXPECT_EQ(121, report.packet_fraction_lost_numerator);
|
|
EXPECT_EQ(9, report.cumulative_packets_lost);
|
|
EXPECT_EQ(uint32_t{22}, report.extended_high_sequence_number);
|
|
// There should be zero jitter, based on the timing information that was given
|
|
// for each RTP packet.
|
|
EXPECT_EQ(RtpTimeDelta(), report.jitter);
|
|
}
|
|
|
|
TEST(PacketReceiveStatsTrackerTest, ComputesJitterCorrectly) {
|
|
constexpr uint16_t kFirstSequenceNumber = 3;
|
|
constexpr RtpTimeTicks kFirstRtpTimestamp =
|
|
RtpTimeTicks() + RtpTimeDelta::FromTicks(99);
|
|
constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(8888);
|
|
|
|
// Record 100 packet arrivals, one second apart, where each packet's RTP
|
|
// timestamps are progressing 2 seconds forward. Thus, the jitter calculation
|
|
// should gradually converge towards a difference of one second.
|
|
constexpr auto kTrueJitter = Clock::to_duration(seconds(1));
|
|
PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
|
|
Clock::duration last_diff = Clock::duration::max();
|
|
for (int i = 0; i < 100; ++i) {
|
|
tracker.OnReceivedValidRtpPacket(
|
|
kFirstSequenceNumber + i,
|
|
kFirstRtpTimestamp +
|
|
RtpTimeDelta::FromTicks(kRtpVideoTimebase) * (i * 2),
|
|
kFirstArrivalTime + seconds(i));
|
|
|
|
// Expect that the jitter is becoming closer to the actual value in each
|
|
// iteration.
|
|
RtcpReportBlock report;
|
|
tracker.PopulateNextReport(&report);
|
|
const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>(
|
|
kRtpVideoTimebase);
|
|
EXPECT_LT(diff, last_diff);
|
|
last_diff = diff;
|
|
}
|
|
|
|
// Because the jitter calculation is a weighted moving average, and also
|
|
// because the timebase has to be converted here, the metric might not ever
|
|
// become exactly kTrueJitter. Ensure that it has converged reasonably close
|
|
// to that value.
|
|
RtcpReportBlock report;
|
|
tracker.PopulateNextReport(&report);
|
|
const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>(
|
|
kRtpVideoTimebase);
|
|
constexpr auto kMaxDiffAtEnd = Clock::to_duration(milliseconds(2));
|
|
EXPECT_NEAR(0, diff.count(), kMaxDiffAtEnd.count());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cast
|
|
} // namespace openscreen
|