aboutsummaryrefslogtreecommitdiff
path: root/cast/streaming/packet_receive_stats_tracker_unittest.cc
blob: c386a7990d15c137d828dbc7d19b92736caad993 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// 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 <limits>

#include "cast/streaming/constants.h"
#include "gtest/gtest.h"

using openscreen::Clock;

namespace cast {
namespace streaming {
namespace {

constexpr int kSomeRtpTimebase = static_cast<int>(kVideoTimebase::den);

// 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(kSomeRtpTimebase);
  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() + std::chrono::seconds(3600);

  PacketReceiveStatsTracker tracker(kSomeRtpTimebase);
  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() + std::chrono::seconds(3600);

  PacketReceiveStatsTracker tracker(kSomeRtpTimebase);

  // 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(kSomeRtpTimebase) * i,
        kFirstArrivalTime + std::chrono::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() + std::chrono::seconds(8888);

  PacketReceiveStatsTracker tracker(kSomeRtpTimebase);

  // 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(kSomeRtpTimebase) * i,
        kFirstArrivalTime + std::chrono::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() + std::chrono::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 =
      std::chrono::duration_cast<Clock::duration>(std::chrono::seconds(1));
  PacketReceiveStatsTracker tracker(kSomeRtpTimebase);
  Clock::duration last_diff = Clock::duration::max();
  for (int i = 0; i < 100; ++i) {
    tracker.OnReceivedValidRtpPacket(
        kFirstSequenceNumber + i,
        kFirstRtpTimestamp +
            RtpTimeDelta::FromTicks(kSomeRtpTimebase) * (i * 2),
        kFirstArrivalTime + std::chrono::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>(
                                        kSomeRtpTimebase);
    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>(kSomeRtpTimebase);
  constexpr auto kMaxDiffAtEnd =
      std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(2));
  EXPECT_NEAR(0, diff.count(), kMaxDiffAtEnd.count());
}

}  // namespace
}  // namespace streaming
}  // namespace cast