blob: 3d2f21e932babc44e2ed11ede42f4f9d2ecea729 [file] [log] [blame]
// 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