xref: /aosp_15_r20/external/openscreen/cast/streaming/compound_rtcp_builder_unittest.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/streaming/compound_rtcp_builder.h"
6 
7 #include <algorithm>
8 #include <chrono>
9 
10 #include "cast/streaming/compound_rtcp_parser.h"
11 #include "cast/streaming/constants.h"
12 #include "cast/streaming/mock_compound_rtcp_parser_client.h"
13 #include "cast/streaming/rtcp_session.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
16 #include "platform/api/time.h"
17 #include "util/chrono_helpers.h"
18 
19 using testing::_;
20 using testing::Invoke;
21 using testing::Mock;
22 using testing::SaveArg;
23 using testing::StrictMock;
24 
25 namespace openscreen {
26 namespace cast {
27 namespace {
28 
29 constexpr Ssrc kSenderSsrc{1};
30 constexpr Ssrc kReceiverSsrc{2};
31 
32 class CompoundRtcpBuilderTest : public testing::Test {
33  public:
session()34   RtcpSession* session() { return &session_; }
builder()35   CompoundRtcpBuilder* builder() { return &builder_; }
client()36   StrictMock<MockCompoundRtcpParserClient>* client() { return &client_; }
parser()37   CompoundRtcpParser* parser() { return &parser_; }
38 
39   // Return |timestamp| converted to the NtpTimestamp wire format and then
40   // converted back to the local Clock's time_point. The result will be either
41   // exactly equal to |original|, or one tick off from it due to the lossy
42   // conversions.
ViaNtpTimestampTranslation(Clock::time_point timestamp) const43   Clock::time_point ViaNtpTimestampTranslation(
44       Clock::time_point timestamp) const {
45     return session_.ntp_converter().ToLocalTime(
46         session_.ntp_converter().ToNtpTimestamp(timestamp));
47   }
48 
49  private:
50   RtcpSession session_{kSenderSsrc, kReceiverSsrc, Clock::now()};
51   CompoundRtcpBuilder builder_{&session_};
52   StrictMock<MockCompoundRtcpParserClient> client_;
53   CompoundRtcpParser parser_{&session_, &client_};
54 };
55 
56 // Tests that the builder, by default, produces RTCP packets that always include
57 // the receiver's reference time and checkpoint information.
TEST_F(CompoundRtcpBuilderTest,TheBasics)58 TEST_F(CompoundRtcpBuilderTest, TheBasics) {
59   const FrameId checkpoint = FrameId::first() + 42;
60   builder()->SetCheckpointFrame(checkpoint);
61   const milliseconds playout_delay{321};
62   builder()->SetPlayoutDelay(playout_delay);
63 
64   const auto send_time = Clock::now();
65   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
66   const auto packet = builder()->BuildPacket(send_time, buffer);
67   ASSERT_TRUE(packet.data());
68 
69   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
70                                ViaNtpTimestampTranslation(send_time)));
71   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
72   ASSERT_TRUE(parser()->Parse(packet, checkpoint));
73 }
74 
75 // Tests that the builder correctly serializes a Receiver Report Block and
76 // includes it only in the next-built RTCP packet.
TEST_F(CompoundRtcpBuilderTest,WithReceiverReportBlock)77 TEST_F(CompoundRtcpBuilderTest, WithReceiverReportBlock) {
78   const FrameId checkpoint = FrameId::first() + 42;
79   builder()->SetCheckpointFrame(checkpoint);
80   const auto playout_delay = builder()->playout_delay();
81 
82   RtcpReportBlock original;
83   original.ssrc = kSenderSsrc;
84   original.packet_fraction_lost_numerator = 1;
85   original.cumulative_packets_lost = 2;
86   original.extended_high_sequence_number = 3;
87   original.jitter = RtpTimeDelta::FromTicks(4);
88   original.last_status_report_id = StatusReportId{0x05060708};
89   original.delay_since_last_report = RtcpReportBlock::Delay(9);
90   builder()->IncludeReceiverReportInNextPacket(original);
91 
92   const auto send_time = Clock::now();
93   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
94   const auto packet = builder()->BuildPacket(send_time, buffer);
95   ASSERT_TRUE(packet.data());
96 
97   // Expect that the builder has produced a RTCP packet that includes the
98   // receiver report block.
99   const auto max_feedback_frame_id = checkpoint + 2;
100   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
101                                ViaNtpTimestampTranslation(send_time)));
102   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
103   RtcpReportBlock parsed;
104   EXPECT_CALL(*(client()), OnReceiverReport(_)).WillOnce(SaveArg<0>(&parsed));
105   ASSERT_TRUE(parser()->Parse(packet, max_feedback_frame_id));
106   Mock::VerifyAndClearExpectations(client());
107   EXPECT_EQ(original.ssrc, parsed.ssrc);
108   EXPECT_EQ(original.packet_fraction_lost_numerator,
109             parsed.packet_fraction_lost_numerator);
110   EXPECT_EQ(original.cumulative_packets_lost, parsed.cumulative_packets_lost);
111   EXPECT_EQ(original.extended_high_sequence_number,
112             parsed.extended_high_sequence_number);
113   EXPECT_EQ(original.jitter, parsed.jitter);
114   EXPECT_EQ(original.last_status_report_id, parsed.last_status_report_id);
115   EXPECT_EQ(original.delay_since_last_report, parsed.delay_since_last_report);
116 
117   // Build again, but this time the builder should not include the receiver
118   // report block.
119   const auto second_send_time = send_time + milliseconds(500);
120   const auto second_packet = builder()->BuildPacket(second_send_time, buffer);
121   ASSERT_TRUE(second_packet.data());
122   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
123                                ViaNtpTimestampTranslation(second_send_time)));
124   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
125   EXPECT_CALL(*(client()), OnReceiverReport(_)).Times(0);
126   ASSERT_TRUE(parser()->Parse(second_packet, max_feedback_frame_id));
127   Mock::VerifyAndClearExpectations(client());
128 }
129 
130 // Tests that the builder repeatedly produces packets with the PLI message as
131 // long as the PLI flag is set, and produces packets without the PLI message
132 // while the flag is not set.
TEST_F(CompoundRtcpBuilderTest,WithPictureLossIndicator)133 TEST_F(CompoundRtcpBuilderTest, WithPictureLossIndicator) {
134   // Turn the PLI flag off and on twice, generating several packets while the
135   // flag is in each state.
136   FrameId checkpoint = FrameId::first();
137   auto send_time = Clock::now();
138   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
139   for (int status = 0; status <= 3; ++status) {
140     const bool pli_flag_set = ((status % 2) != 0);
141     builder()->SetPictureLossIndicator(pli_flag_set);
142 
143     // Produce three packets while the PLI flag is not changing, and confirm the
144     // PLI condition is being parsed on the other end.
145     for (int i = 0; i < 3; ++i) {
146       SCOPED_TRACE(testing::Message() << "status=" << status << ", i=" << i);
147 
148       EXPECT_EQ(pli_flag_set, builder()->is_picture_loss_indicator_set());
149       builder()->SetCheckpointFrame(checkpoint);
150       const auto playout_delay = builder()->playout_delay();
151       const auto packet = builder()->BuildPacket(send_time, buffer);
152       ASSERT_TRUE(packet.data());
153 
154       const auto max_feedback_frame_id = checkpoint + 1;
155       EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
156                                    ViaNtpTimestampTranslation(send_time)));
157       EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
158       EXPECT_CALL(*(client()), OnReceiverIndicatesPictureLoss())
159           .Times(pli_flag_set ? 1 : 0);
160       ASSERT_TRUE(parser()->Parse(packet, max_feedback_frame_id));
161       Mock::VerifyAndClearExpectations(client());
162 
163       ++checkpoint;
164       send_time += milliseconds(500);
165     }
166   }
167 }
168 
169 // Tests that the builder produces packets with frame-level and specific-packet
170 // NACKs, but includes this information only in the next-built RTCP packet.
TEST_F(CompoundRtcpBuilderTest,WithNacks)171 TEST_F(CompoundRtcpBuilderTest, WithNacks) {
172   const FrameId checkpoint = FrameId::first() + 15;
173   builder()->SetCheckpointFrame(checkpoint);
174   const auto playout_delay = builder()->playout_delay();
175 
176   const std::vector<PacketNack> kPacketNacks = {
177       {FrameId::first() + 16, FramePacketId{0}},
178       {FrameId::first() + 16, FramePacketId{1}},
179       {FrameId::first() + 16, FramePacketId{2}},
180       {FrameId::first() + 16, FramePacketId{7}},
181       {FrameId::first() + 16, FramePacketId{15}},
182       {FrameId::first() + 17, FramePacketId{19}},
183       {FrameId::first() + 18, kAllPacketsLost},
184       {FrameId::first() + 19, kAllPacketsLost},
185   };
186   builder()->IncludeFeedbackInNextPacket(kPacketNacks, {});
187 
188   const auto send_time = Clock::now();
189   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
190   const auto packet = builder()->BuildPacket(send_time, buffer);
191   ASSERT_TRUE(packet.data());
192 
193   // Expect that the builder has produced a RTCP packet that also includes the
194   // NACK feedback.
195   const auto kMaxFeedbackFrameId = FrameId::first() + 19;
196   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
197                                ViaNtpTimestampTranslation(send_time)));
198   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
199   EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(kPacketNacks));
200   ASSERT_TRUE(parser()->Parse(packet, kMaxFeedbackFrameId));
201   Mock::VerifyAndClearExpectations(client());
202 
203   // Build again, but this time the builder should not include the feedback.
204   const auto second_send_time = send_time + milliseconds(500);
205   const auto second_packet = builder()->BuildPacket(second_send_time, buffer);
206   ASSERT_TRUE(second_packet.data());
207   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
208                                ViaNtpTimestampTranslation(second_send_time)));
209   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
210   EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(_)).Times(0);
211   ASSERT_TRUE(parser()->Parse(second_packet, kMaxFeedbackFrameId));
212   Mock::VerifyAndClearExpectations(client());
213 }
214 
215 // Tests that the builder produces packets with frame-level ACKs, but includes
216 // this information only in the next-built RTCP packet. Both a single-frame ACK
217 // and a multi-frame ACK are tested, to exercise the various code paths
218 // containing the serialization logic that auto-extends the ACK bit vector
219 // length when necessary.
TEST_F(CompoundRtcpBuilderTest,WithAcks)220 TEST_F(CompoundRtcpBuilderTest, WithAcks) {
221   const FrameId checkpoint = FrameId::first() + 22;
222   builder()->SetCheckpointFrame(checkpoint);
223   const auto playout_delay = builder()->playout_delay();
224 
225   const std::vector<FrameId> kTestCases[] = {
226       // One frame ACK will result in building an ACK bit vector of 2 bytes
227       // only.
228       {FrameId::first() + 24},
229 
230       // These frame ACKs were chosen so that the ACK bit vector must expand to
231       // be 6 (2 + 4) bytes long.
232       {FrameId::first() + 25, FrameId::first() + 42, FrameId::first() + 43},
233   };
234   const auto kMaxFeedbackFrameId = FrameId::first() + 50;
235   auto send_time = Clock::now();
236   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
237   for (const std::vector<FrameId>& frame_acks : kTestCases) {
238     // Include the frame ACK feedback, and expect that the builder will produce
239     // a RTCP packet that also includes the ACK feedback.
240     builder()->IncludeFeedbackInNextPacket({}, frame_acks);
241     const auto packet = builder()->BuildPacket(send_time, buffer);
242     ASSERT_TRUE(packet.data());
243     EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
244                                  ViaNtpTimestampTranslation(send_time)));
245     EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
246     EXPECT_CALL(*(client()), OnReceiverHasFrames(frame_acks));
247     ASSERT_TRUE(parser()->Parse(packet, kMaxFeedbackFrameId));
248     Mock::VerifyAndClearExpectations(client());
249 
250     // Build again, but this time the builder should not include the feedback
251     // because it was already provided in the prior packet.
252     send_time += milliseconds(500);
253     const auto second_packet = builder()->BuildPacket(send_time, buffer);
254     ASSERT_TRUE(second_packet.data());
255     EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(
256                                  ViaNtpTimestampTranslation(send_time)));
257     EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, playout_delay));
258     EXPECT_CALL(*(client()), OnReceiverHasFrames(_)).Times(0);
259     ASSERT_TRUE(parser()->Parse(second_packet, kMaxFeedbackFrameId));
260     Mock::VerifyAndClearExpectations(client());
261 
262     send_time += milliseconds(500);
263   }
264 }
265 
266 // Tests that the builder handles scenarios where the provided buffer isn't big
267 // enough to hold all the ACK/NACK details. The expected behavior is that it
268 // will include as many of the NACKs as possible, followed by as many of the
269 // ACKs as possible.
TEST_F(CompoundRtcpBuilderTest,WithEverythingThatCanFit)270 TEST_F(CompoundRtcpBuilderTest, WithEverythingThatCanFit) {
271   const FrameId checkpoint = FrameId::first();
272   builder()->SetCheckpointFrame(checkpoint);
273 
274   // For this test, use an abnormally-huge, but not impossible, list of NACKs
275   // and ACKs. Each NACK is for a separate frame so that a separate "loss field"
276   // will be generated in the serialized output.
277   std::vector<PacketNack> nacks;
278   for (FrameId f = checkpoint + 1; f != checkpoint + 64; ++f) {
279     nacks.push_back(PacketNack{f, FramePacketId{0}});
280   }
281   std::vector<FrameId> acks;
282   for (FrameId f = checkpoint + 64; f < checkpoint + kMaxUnackedFrames; ++f) {
283     acks.push_back(f);
284   }
285   ASSERT_FALSE(acks.empty());
286 
287   const auto max_feedback_frame_id = checkpoint + kMaxUnackedFrames;
288 
289   // First test: Include too many NACKs so that some of them will be dropped and
290   // none of the ACKs will be included.
291   builder()->IncludeFeedbackInNextPacket(nacks, acks);
292   uint8_t buffer[CompoundRtcpBuilder::kRequiredBufferSize];
293   const auto packet = builder()->BuildPacket(Clock::now(), buffer);
294   ASSERT_TRUE(packet.data());
295   EXPECT_EQ(sizeof(buffer), packet.size());  // The whole buffer should be used.
296 
297   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(_));
298   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, _));
299   // No ACKs could be included.
300   EXPECT_CALL(*(client()), OnReceiverHasFrames(_)).Times(0);
301   EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(_))
302       .WillOnce(Invoke([&](std::vector<PacketNack> parsed_nacks) {
303         // Some should be dropped.
304         ASSERT_LT(parsed_nacks.size(), nacks.size());
305         EXPECT_TRUE(std::equal(parsed_nacks.begin(), parsed_nacks.end(),
306                                nacks.begin()));
307       }));
308   ASSERT_TRUE(parser()->Parse(packet, max_feedback_frame_id));
309   Mock::VerifyAndClearExpectations(client());
310 
311   // Second test: Include fewer NACKs this time, so that none of the NACKs are
312   // dropped, but not all of the ACKs can be included. With internal knowledge
313   // of the wire format, it turns out that limiting serialization to 48 loss
314   // fields will free-up just enough space for 2 bytes of ACK bit vector.
315   constexpr int kFewerNackCount = 48;
316   builder()->IncludeFeedbackInNextPacket(
317       std::vector<PacketNack>(nacks.begin(), nacks.begin() + kFewerNackCount),
318       acks);
319   const auto second_packet = builder()->BuildPacket(Clock::now(), buffer);
320   ASSERT_TRUE(second_packet.data());
321   // The whole buffer should be used.
322   EXPECT_EQ(sizeof(buffer), second_packet.size());
323 
324   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(_));
325   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, _));
326   EXPECT_CALL(*(client()), OnReceiverHasFrames(_))
327       .WillOnce(Invoke([&](std::vector<FrameId> parsed_acks) {
328         // Some of the ACKs should be dropped.
329         ASSERT_LT(parsed_acks.size(), acks.size());
330         EXPECT_TRUE(
331             std::equal(parsed_acks.begin(), parsed_acks.end(), acks.begin()));
332       }));
333   EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(_))
334       .WillOnce(Invoke([&](absl::Span<const PacketNack> parsed_nacks) {
335         // All of the 48 NACKs provided should be present.
336         ASSERT_EQ(kFewerNackCount, static_cast<int>(parsed_nacks.size()));
337         EXPECT_TRUE(std::equal(parsed_nacks.begin(), parsed_nacks.end(),
338                                nacks.begin()));
339       }));
340   ASSERT_TRUE(parser()->Parse(second_packet, max_feedback_frame_id));
341   Mock::VerifyAndClearExpectations(client());
342 
343   // Third test: Include even fewer NACKs, so that nothing is dropped.
344   constexpr int kEvenFewerNackCount = 46;
345   builder()->IncludeFeedbackInNextPacket(
346       std::vector<PacketNack>(nacks.begin(),
347                               nacks.begin() + kEvenFewerNackCount),
348       acks);
349   const auto third_packet = builder()->BuildPacket(Clock::now(), buffer);
350   ASSERT_TRUE(third_packet.data());
351 
352   EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(_));
353   EXPECT_CALL(*(client()), OnReceiverCheckpoint(checkpoint, _));
354   EXPECT_CALL(*(client()), OnReceiverHasFrames(_))
355       .WillOnce(Invoke([&](std::vector<FrameId> parsed_acks) {
356         // All acks should be present.
357         EXPECT_EQ(acks, parsed_acks);
358       }));
359   EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(_))
360       .WillOnce(Invoke([&](absl::Span<const PacketNack> parsed_nacks) {
361         // Only the first 46 NACKs provided should be present.
362         ASSERT_EQ(kEvenFewerNackCount, static_cast<int>(parsed_nacks.size()));
363         EXPECT_TRUE(std::equal(parsed_nacks.begin(), parsed_nacks.end(),
364                                nacks.begin()));
365       }));
366   ASSERT_TRUE(parser()->Parse(third_packet, max_feedback_frame_id));
367   Mock::VerifyAndClearExpectations(client());
368 }
369 
370 }  // namespace
371 }  // namespace cast
372 }  // namespace openscreen
373