// 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 #include #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::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::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( 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( kRtpVideoTimebase); constexpr auto kMaxDiffAtEnd = Clock::to_duration(milliseconds(2)); EXPECT_NEAR(0, diff.count(), kMaxDiffAtEnd.count()); } } // namespace } // namespace cast } // namespace openscreen