blob: 4aca855ad7023efe7a12c1683d2250213ce25cca [file]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cast/streaming/impl/receiver_impl.h"
#include <stdint.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <string>
#include <utility>
#include <vector>
#include "cast/streaming/impl/compound_rtcp_builder.h"
#include "cast/streaming/impl/compound_rtcp_parser.h"
#include "cast/streaming/impl/frame_crypto.h"
#include "cast/streaming/impl/receiver_packet_router.h"
#include "cast/streaming/impl/rtcp_common.h"
#include "cast/streaming/impl/rtcp_session.h"
#include "cast/streaming/impl/rtp_defines.h"
#include "cast/streaming/impl/rtp_packetizer.h"
#include "cast/streaming/impl/sender_report_builder.h"
#include "cast/streaming/impl/statistics_common.h"
#include "cast/streaming/public/constants.h"
#include "cast/streaming/public/encoded_frame.h"
#include "cast/streaming/public/environment.h"
#include "cast/streaming/public/session_config.h"
#include "cast/streaming/rtp_time.h"
#include "cast/streaming/ssrc.h"
#include "cast/streaming/testing/mock_environment.h"
#include "cast/streaming/testing/simple_socket_subscriber.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "platform/api/time.h"
#include "platform/api/udp_socket.h"
#include "platform/base/error.h"
#include "platform/base/ip_address.h"
#include "platform/base/span.h"
#include "platform/base/trivial_clock_traits.h"
#include "platform/base/udp_packet.h"
#include "platform/test/fake_clock.h"
#include "platform/test/fake_task_runner.h"
#include "util/chrono_helpers.h"
#include "util/osp_logging.h"
#include "util/raw_ptr.h"
#include "util/raw_ref.h"
using testing::_;
using testing::AllOf;
using testing::AtLeast;
using testing::ElementsAre;
using testing::Ge;
using testing::Gt;
using testing::Le;
using testing::NiceMock;
using testing::Return;
using testing::SaveArg;
namespace openscreen::cast {
namespace {
// Receiver configuration.
constexpr Ssrc kSenderSsrc = 1;
constexpr Ssrc kReceiverSsrc = 2;
constexpr int kRtpTimebase = 48000;
constexpr milliseconds kTargetPlayoutDelay(100);
constexpr auto kAesKey =
std::array<uint8_t, 16>{{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}};
constexpr auto kCastIvMask =
std::array<uint8_t, 16>{{0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80,
0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00}};
constexpr milliseconds kTargetPlayoutDelayChange(800);
// Additional configuration for the Sender.
constexpr RtpPayloadType kRtpPayloadType = RtpPayloadType::kVideoVp8;
constexpr int kMaxRtpPacketSize = 64;
// A simulated one-way network delay, and round-trip network delay.
constexpr auto kOneWayNetworkDelay = milliseconds(3);
constexpr auto kRoundTripNetworkDelay = 2 * kOneWayNetworkDelay;
static_assert(kRoundTripNetworkDelay < kTargetPlayoutDelay &&
kRoundTripNetworkDelay < kTargetPlayoutDelayChange,
"Network delay must be smaller than target playout delay.");
// An EncodedFrame for unit testing, one of a sequence of simulated frames, each
// of 10 ms duration. The first frame will be a key frame; and any later frames
// will be non-key, dependent on the prior frame. Frame 5 (the 6th frame in the
// zero-based sequence) will include a target playout delay change, an increase
// to 800 ms. Frames with different IDs will contain vary in their payload data
// size, but are always 3 or more packets' worth of data.
struct SimulatedFrame : public EncodedFrame {
static constexpr milliseconds kFrameDuration = milliseconds(10);
static constexpr milliseconds kTargetPlayoutDelayChange = milliseconds(800);
static constexpr int kPlayoutChangeAtFrame = 5;
SimulatedFrame(Clock::time_point first_frame_reference_time, int which) {
frame_id = FrameId::first() + which;
if (which == 0) {
dependency = EncodedFrame::Dependency::kKeyFrame;
referenced_frame_id = frame_id;
} else {
dependency = EncodedFrame::Dependency::kDependent;
referenced_frame_id = frame_id - 1;
}
rtp_timestamp =
GetRtpStartTime() +
RtpTimeDelta::FromDuration(kFrameDuration * which, kRtpTimebase);
reference_time = first_frame_reference_time + kFrameDuration * which;
if (which == kPlayoutChangeAtFrame) {
new_playout_delay = kTargetPlayoutDelayChange;
}
constexpr int kAdditionalBytesEachSuccessiveFrame = 3;
buffer_.resize(3 * kMaxRtpPacketSize +
which * kAdditionalBytesEachSuccessiveFrame);
for (size_t i = 0; i < buffer_.size(); ++i) {
buffer_[i] = static_cast<uint8_t>(which + static_cast<int>(i));
}
data = buffer_;
}
static RtpTimeTicks GetRtpStartTime() {
return RtpTimeTicks::FromTimeSinceOrigin(seconds(0), kRtpTimebase);
}
static milliseconds GetExpectedPlayoutDelay(int which) {
return (which < kPlayoutChangeAtFrame) ? kTargetPlayoutDelay
: kTargetPlayoutDelayChange;
}
private:
std::vector<uint8_t> buffer_;
};
// static
constexpr milliseconds SimulatedFrame::kFrameDuration;
constexpr milliseconds SimulatedFrame::kTargetPlayoutDelayChange;
constexpr int SimulatedFrame::kPlayoutChangeAtFrame;
template <typename T>
std::string ToString(T duration) {
std::ostringstream ss;
openscreen::clock_operators::operator<<(ss, duration);
return ss.str();
}
// TODO(jophba): this matcher is likely more generally useful and should
// be refactored.
MATCHER_P(EqualsDuration, expected, ToString(expected)) {
if (arg == expected) {
return true;
}
*result_listener << ToString(arg) << " (a difference of "
<< ToString(arg - expected) << ")";
return false;
}
// Processes packets from the Receiver under test, as a real Sender might, and
// allows the unit tests to set expectations on events of interest to confirm
// proper behavior of the Receiver.
class MockSender : public CompoundRtcpParser::Client {
public:
MockSender(TaskRunner& task_runner, UdpSocket::Client* receiver)
: task_runner_(task_runner),
receiver_(receiver),
sender_endpoint_{
// Use a random IPv6 address in the range reserved for
// "documentation purposes." Thus, the following is a fake address
// that should be blocked by the OS (and all network packet
// routers). But, these tests don't use real sockets, so...
IPAddress::Parse("2001:db8:0d93:69c2:fd1a:49a6:a7c0:e8a6").value(),
2344},
rtcp_session_(kSenderSsrc, kReceiverSsrc, FakeClock::now()),
sender_report_builder_(rtcp_session_),
rtcp_parser_(rtcp_session_, *this),
crypto_(kAesKey, kCastIvMask),
rtp_packetizer_(kRtpPayloadType, kSenderSsrc, kMaxRtpPacketSize) {}
~MockSender() override = default;
void set_max_feedback_frame_id(FrameId f) { max_feedback_frame_id_ = f; }
// Called by the test procedures to generate a Sender Report containing the
// given lip-sync timestamps, and send it to the Receiver. The caller must
// spin the TaskRunner for the RTCP packet to be delivered to the Receiver.
StatusReportId SendSenderReport(Clock::time_point reference_time,
RtpTimeTicks rtp_timestamp) {
// Generate the Sender Report RTCP packet.
uint8_t buffer[kMaxRtpPacketSizeForIpv4UdpOnEthernet];
RtcpSenderReport sender_report;
sender_report.reference_time = reference_time;
sender_report.rtp_timestamp = rtp_timestamp;
const auto packet_and_report_id =
sender_report_builder_.BuildPacket(sender_report, buffer);
// Send the RTCP packet as a UdpPacket directly to the Receiver instance.
UdpPacket packet_to_send(packet_and_report_id.first.begin(),
packet_and_report_id.first.end());
packet_to_send.set_source(sender_endpoint_);
task_runner_->PostTaskWithDelay(
[receiver = receiver_, packet = std::move(packet_to_send)]() mutable {
receiver->OnRead(nullptr, ErrorOr<UdpPacket>(std::move(packet)));
},
kOneWayNetworkDelay);
return packet_and_report_id.second;
}
// Sets which frame is currently being sent by this MockSender. Test code must
// call SendRtpPackets() to send the packets.
void SetFrameBeingSent(const EncodedFrame& frame) {
frame_being_sent_ = crypto_.Encrypt(frame);
}
// Returns a vector containing each packet ID once (of the current frame being
// sent). `permutation` controls the sort order of the vector: zero will
// provide all the packet IDs in order, and greater values will provide them
// in a different, predictable order.
std::vector<FramePacketId> GetAllPacketIds(int permutation = 0) {
const int num_packets =
rtp_packetizer_.ComputeNumberOfPackets(frame_being_sent_);
OSP_CHECK_GT(num_packets, 0);
std::vector<FramePacketId> ids;
ids.reserve(num_packets);
const FramePacketId last_packet_id =
static_cast<FramePacketId>(num_packets - 1);
for (FramePacketId packet_id = 0; packet_id <= last_packet_id;
++packet_id) {
ids.push_back(packet_id);
}
for (int i = 0; i < permutation; ++i) {
std::next_permutation(ids.begin(), ids.end());
}
return ids;
}
// Send the specified packets of the current frame being sent.
void SendRtpPackets(const std::vector<FramePacketId>& packets_to_send) {
uint8_t buffer[kMaxRtpPacketSize];
for (FramePacketId packet_id : packets_to_send) {
const auto span = rtp_packetizer_.GeneratePacket(
frame_being_sent_, packet_id, ByteBuffer(buffer, kMaxRtpPacketSize));
UdpPacket packet_to_send(span.begin(), span.end());
packet_to_send.set_source(sender_endpoint_);
task_runner_->PostTaskWithDelay(
[receiver = receiver_, packet = std::move(packet_to_send)]() mutable {
receiver->OnRead(nullptr, ErrorOr<UdpPacket>(std::move(packet)));
},
kOneWayNetworkDelay);
}
}
// Called to process a packet from the Receiver.
void OnPacketFromReceiver(ByteView packet) {
EXPECT_TRUE(rtcp_parser_.Parse(packet, max_feedback_frame_id_));
}
// CompoundRtcpParser::Client implementation: Tests set expectations on these
// mocks to confirm that the receiver is providing the right data to the
// sender in its RTCP packets.
MOCK_METHOD(void,
OnReceiverReferenceTimeAdvanced,
(Clock::time_point reference_time),
(override));
MOCK_METHOD(void,
OnReceiverReport,
(const RtcpReportBlock& receiver_report),
(override));
MOCK_METHOD(void,
OnCastReceiverFrameLogMessages,
(std::vector<RtcpReceiverFrameLogMessage> messages),
(override));
MOCK_METHOD(void, OnReceiverIndicatesPictureLoss, (), (override));
MOCK_METHOD(void,
OnReceiverCheckpoint,
(FrameId frame_id, milliseconds playout_delay),
(override));
MOCK_METHOD(void,
OnReceiverHasFrames,
(std::vector<FrameId> acks),
(override));
MOCK_METHOD(void,
OnReceiverIsMissingPackets,
(std::vector<PacketNack> nacks),
(override));
private:
const raw_ref<TaskRunner> task_runner_;
const raw_ptr<UdpSocket::Client> receiver_;
const IPEndpoint sender_endpoint_;
RtcpSession rtcp_session_;
SenderReportBuilder sender_report_builder_;
CompoundRtcpParser rtcp_parser_;
FrameCrypto crypto_;
RtpPacketizer rtp_packetizer_;
FrameId max_feedback_frame_id_ = FrameId::first() + kMaxUnackedFrames;
EncryptedFrame frame_being_sent_;
};
class MockConsumer : public Receiver::Consumer {
public:
MOCK_METHOD(void, OnFramesReady, (size_t next_frame_buffer_size), (override));
};
struct ReceiverOptions {
bool is_pli_enabled = true;
bool allow_skipping = false;
std::optional<std::chrono::milliseconds> receiver_proactive_pli_interval =
std::nullopt;
};
class ReceiverTest : public testing::Test {
public:
ReceiverTest()
: clock_(Clock::now()),
task_runner_(clock_),
env_(&FakeClock::now, task_runner_),
packet_router_(env_),
sender_(task_runner_, &env_) {
ConstructReceiver();
}
~ReceiverTest() override = default;
void ConstructReceiver(ReceiverOptions options = {}) {
receiver_.reset();
SessionConfig config(
kSenderSsrc, kReceiverSsrc, kRtpTimebase,
/* .channels = */ 2, kTargetPlayoutDelay, kAesKey, kCastIvMask,
/* .is_pli_enabled = */ options.is_pli_enabled, StreamType::kUnknown,
/* .are_receiver_event_logs_enabled = */ true, options.allow_skipping);
config.receiver_proactive_pli_interval =
options.receiver_proactive_pli_interval;
receiver_ =
std::make_unique<ReceiverImpl>(env_, packet_router_, std::move(config));
env_.SetSocketSubscriber(&socket_subscriber_);
ON_CALL(env_, SendPacket(_, _))
.WillByDefault([this](ByteView packet, PacketMetadata metadata) {
task_runner_.PostTaskWithDelay(
[sender = &sender_, copy_of_packet = std::vector<uint8_t>(
packet.begin(), packet.end())]() mutable {
sender->OnPacketFromReceiver(std::move(copy_of_packet));
},
kOneWayNetworkDelay);
});
receiver_->SetConsumer(&consumer_);
}
ReceiverImpl* receiver() { return receiver_.get(); }
MockSender* sender() { return &sender_; }
MockConsumer* consumer() { return &consumer_; }
Clock::time_point now() const { return clock_.now(); }
void AdvanceClockAndRunTasks(Clock::duration delta) { clock_.Advance(delta); }
void RunTasksUntilIdle() { task_runner_.RunTasksUntilIdle(); }
// Sends the initial Sender Report with lip-sync timing information to
// "unblock" the Receiver, and confirms the Receiver immediately replies with
// a corresponding Receiver Report.
void ExchangeInitialReportPackets(Clock::time_point start_time) {
sender()->SendSenderReport(start_time, SimulatedFrame::GetRtpStartTime());
AdvanceClockAndRunTasks(
kOneWayNetworkDelay); // Transmit report to Receiver.
// The Receiver will immediately reply with a Receiver Report.
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::leader(), kTargetPlayoutDelay))
.Times(1);
AdvanceClockAndRunTasks(kOneWayNetworkDelay); // Transmit reply to Sender.
testing::Mock::VerifyAndClearExpectations(sender());
}
void ReceiveFrame(int frame_index,
Clock::time_point reference_time,
std::chrono::milliseconds playout_delay,
Clock::duration clock_advancement) {
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(FrameId::first() + frame_index,
playout_delay))
.Times(1);
sender()->SetFrameBeingSent(SimulatedFrame(reference_time, frame_index));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(clock_advancement);
testing::Mock::VerifyAndClearExpectations(sender());
testing::Mock::VerifyAndClearExpectations(consumer());
}
// Consume one frame from the Receiver, and verify that it is the same as the
// `sent_frame`. Exception: The `reference_time` is the playout time on the
// Receiver's end, while it refers to the capture time on the Sender's end.
void ConsumeAndVerifyFrame(
const SimulatedFrame& sent_frame,
std::optional<milliseconds> expected_delay = std::nullopt) {
SCOPED_TRACE(testing::Message() << "for frame " << sent_frame.frame_id);
const std::optional<size_t> payload_size = receiver()->AdvanceToNextFrame();
ASSERT_TRUE(payload_size.has_value());
std::vector<uint8_t> buffer(*payload_size);
EncodedFrame received_frame = receiver()->ConsumeNextFrame(buffer);
const milliseconds playout_delay =
expected_delay.value_or(SimulatedFrame::GetExpectedPlayoutDelay(
sent_frame.frame_id - FrameId::first()));
EXPECT_EQ(sent_frame.dependency, received_frame.dependency);
EXPECT_EQ(sent_frame.frame_id, received_frame.frame_id);
EXPECT_EQ(sent_frame.referenced_frame_id,
received_frame.referenced_frame_id);
EXPECT_EQ(sent_frame.rtp_timestamp, received_frame.rtp_timestamp);
EXPECT_THAT(sent_frame.reference_time + kOneWayNetworkDelay +
playout_delay - expected_player_processing_time_,
EqualsDuration(received_frame.reference_time));
EXPECT_THAT(sent_frame.new_playout_delay,
EqualsDuration(received_frame.new_playout_delay));
EXPECT_THAT(sent_frame.data,
testing::ElementsAreArray(received_frame.data));
}
// Consume zero or more frames from the Receiver, verifying that they are the
// same as the SimulatedFrame that was sent.
void ConsumeAndVerifyFrames(int first,
int last,
Clock::time_point start_time) {
for (int i = first; i <= last; ++i) {
ConsumeAndVerifyFrame(SimulatedFrame(start_time, i));
}
}
protected:
Clock::duration expected_player_processing_time_ =
Receiver::kDefaultPlayerProcessingTime;
private:
FakeClock clock_;
FakeTaskRunner task_runner_;
testing::NiceMock<MockEnvironment> env_;
ReceiverPacketRouter packet_router_;
std::unique_ptr<ReceiverImpl> receiver_;
testing::NiceMock<MockSender> sender_;
testing::NiceMock<MockConsumer> consumer_;
SimpleSubscriber socket_subscriber_;
};
// Tests that the Receiver processes RTCP packets correctly and sends RTCP
// reports at regular intervals.
TEST_F(ReceiverTest, ReceivesAndSendsRtcpPackets) {
// Sender-side expectations, after the Receiver has processed the first Sender
// Report.
Clock::time_point receiver_reference_time{};
EXPECT_CALL(*sender(), OnReceiverReferenceTimeAdvanced(_))
.WillOnce(SaveArg<0>(&receiver_reference_time));
RtcpReportBlock receiver_report;
EXPECT_CALL(*sender(), OnReceiverReport(_))
.WillOnce(SaveArg<0>(&receiver_report));
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::leader(), kTargetPlayoutDelay))
.Times(1);
// Have the MockSender send a Sender Report with lip-sync timing information.
const Clock::time_point sender_reference_time = FakeClock::now();
const RtpTimeTicks sender_rtp_timestamp =
RtpTimeTicks::FromTimeSinceOrigin(seconds(1), kRtpTimebase);
const StatusReportId sender_report_id =
sender()->SendSenderReport(sender_reference_time, sender_rtp_timestamp);
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
// Expect the MockSender got back a Receiver Report that includes its SSRC and
// the last Sender Report ID.
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_EQ(kSenderSsrc, receiver_report.ssrc);
EXPECT_EQ(sender_report_id, receiver_report.last_status_report_id);
// Confirm the clock offset math: Since the Receiver and MockSender share the
// same underlying FakeClock, the Receiver should be ahead of the Sender,
// which reflects the simulated one-way network packet travel time (of the
// Sender Report).
//
// Note: The offset can be affected by the lossy conversion when going to and
// from the wire-format NtpTimestamps. See the unit tests in
// ntp_time_unittest.cc for further discussion.
constexpr auto kAllowedNtpRoundingError = microseconds(2);
EXPECT_NEAR(to_microseconds(kOneWayNetworkDelay).count(),
static_cast<double>(to_microseconds(receiver_reference_time -
sender_reference_time)
.count()),
kAllowedNtpRoundingError.count());
// Without the Sender doing anything, the Receiver should continue providing
// RTCP reports at regular intervals. Simulate three intervals of time,
// verifying that the Receiver did send reports.
Clock::time_point last_receiver_reference_time = receiver_reference_time;
for (int i = 0; i < 3; ++i) {
receiver_reference_time = Clock::time_point();
EXPECT_CALL(*sender(), OnReceiverReferenceTimeAdvanced(_))
.WillRepeatedly(SaveArg<0>(&receiver_reference_time));
AdvanceClockAndRunTasks(kRtcpReportInterval);
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_LT(last_receiver_reference_time, receiver_reference_time);
last_receiver_reference_time = receiver_reference_time;
}
}
// Tests that the Receiver processes RTP packets, which might arrive in-order or
// out of order, but such that each frame is completely received in-order. Also,
// confirms that target playout delay changes are processed/applied correctly.
TEST_F(ReceiverTest, ReceivesFramesInOrder) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(10);
for (int i = 0; i <= 9; ++i) {
EXPECT_CALL(*sender(), OnReceiverCheckpoint(
FrameId::first() + i,
SimulatedFrame::GetExpectedPlayoutDelay(i)))
.Times(1);
EXPECT_CALL(*sender(), OnReceiverIsMissingPackets(_)).Times(0);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, i));
// Send the frame's packets in-order half the time, out-of-order the other
// half.
const int permutation = (i % 2) ? i : 0;
sender()->SendRtpPackets(sender()->GetAllPacketIds(permutation));
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
// The Receiver should immediately ACK once it has received all the RTP
// packets to complete the frame.
testing::Mock::VerifyAndClearExpectations(sender());
// Advance to next frame transmission time.
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration -
kRoundTripNetworkDelay);
}
// When the Receiver has all of the frames and they are complete, it should
// send out a low-frequency periodic RTCP "ping." Verify that there is one and
// only one "ping" sent when the clock moves forward by one default report
// interval during a period of inactivity.
EXPECT_CALL(*sender(), OnReceiverCheckpoint(FrameId::first() + 9,
kTargetPlayoutDelayChange))
.Times(1);
AdvanceClockAndRunTasks(kRtcpReportInterval);
testing::Mock::VerifyAndClearExpectations(sender());
ConsumeAndVerifyFrames(0, 9, start_time);
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
}
// Tests that the Receiver processes RTP packets, can receive frames out of
// order, and issues the appropriate ACK/NACK feedback to the Sender as it
// realizes what it has and what it's missing.
TEST_F(ReceiverTest, ReceivesFramesOutOfOrder) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Send frames 3, 4, and 2. No NACKs expected yet.
sender()->SetFrameBeingSent(SimulatedFrame(start_time, 3));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, 4));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, 2));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
// Send frame 0. Now there's a hole (frame 1) between received frames, so
// expect a NACK.
EXPECT_CALL(*sender(), OnReceiverIsMissingPackets(ElementsAre(PacketNack{
FrameId::first() + 1, kAllPacketsLost})))
.Times(1);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, 0));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
testing::Mock::VerifyAndClearExpectations(sender());
// Send frame 1. Now all frames up to 4 are complete. Expect a checkpoint
// advancement, and also that the consumer is notified.
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(AtLeast(1));
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::first() + 4, kTargetPlayoutDelay))
.Times(1);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, 1));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
testing::Mock::VerifyAndClearExpectations(sender());
ConsumeAndVerifyFrames(0, 4, start_time);
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
}
// Tests that the Receiver will respond to a key frame request from its client
// by sending a Picture Loss Indicator (PLI) to the Sender, and then will
// automatically stop sending the PLI once a key frame has been received.
TEST_F(ReceiverTest, RequestsKeyFrameToRectifyPictureLoss) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Send and Receive three frames in-order, normally.
for (int i = 0; i <= 2; ++i) {
ReceiveFrame(i, start_time, kTargetPlayoutDelay, kRoundTripNetworkDelay);
// Advance to next frame transmission time.
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration -
kRoundTripNetworkDelay);
}
ConsumeAndVerifyFrames(0, 2, start_time);
// Simulate the Consumer requesting a key frame after picture loss (e.g., a
// decoder failure). Ensure the Sender is immediately notified.
EXPECT_CALL(*sender(), OnReceiverIndicatesPictureLoss()).Times(1);
receiver()->RequestKeyFrame();
AdvanceClockAndRunTasks(kOneWayNetworkDelay); // Propagate request to Sender.
testing::Mock::VerifyAndClearExpectations(sender());
// The Sender sends another frame that is not a key frame and, upon receipt,
// the Receiver should repeat its "cry" for a key frame.
ReceiveFrame(3, start_time, kTargetPlayoutDelay,
SimulatedFrame::kFrameDuration - kOneWayNetworkDelay);
ConsumeAndVerifyFrames(3, 3, start_time);
// Finally, the Sender responds to the PLI condition by sending a key frame.
// Confirm the Receiver has stopped indicating picture loss after having
// received the key frame.
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::first() + 4, kTargetPlayoutDelay))
.Times(1);
EXPECT_CALL(*sender(), OnReceiverIndicatesPictureLoss()).Times(0);
SimulatedFrame key_frame(start_time, 4);
key_frame.dependency = EncodedFrame::Dependency::kKeyFrame;
key_frame.referenced_frame_id = key_frame.frame_id;
sender()->SetFrameBeingSent(key_frame);
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
testing::Mock::VerifyAndClearExpectations(sender());
testing::Mock::VerifyAndClearExpectations(consumer());
// The client has not yet consumed the key frame, so any calls to
// RequestKeyFrame() should not set the PLI condition again.
EXPECT_CALL(*sender(), OnReceiverIndicatesPictureLoss()).Times(0);
receiver()->RequestKeyFrame();
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(sender());
// After consuming the requested key frame, the client should be able to set
// the PLI condition again with another RequestKeyFrame() call.
ConsumeAndVerifyFrame(key_frame);
EXPECT_CALL(*sender(), OnReceiverIndicatesPictureLoss()).Times(1);
receiver()->RequestKeyFrame();
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(sender());
}
TEST_F(ReceiverTest, PLICanBeDisabled) {
ConstructReceiver({.is_pli_enabled = false});
EXPECT_CALL(*sender(), OnReceiverIndicatesPictureLoss()).Times(0);
receiver()->RequestKeyFrame();
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(sender());
}
// Tests that the Receiver will start dropping packets once its frame queue is
// full (i.e., when the consumer is not pulling them out of the queue). Since
// the Receiver will stop ACK'ing frames, the Sender will become stalled.
TEST_F(ReceiverTest, EatsItsFill) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Send and Receive the maximum possible number of frames in-order, normally.
for (int i = 0; i < kMaxUnackedFrames; ++i) {
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(
FrameId::first() + i,
SimulatedFrame::GetExpectedPlayoutDelay(i)))
.Times(1);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, i));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
testing::Mock::VerifyAndClearExpectations(sender());
testing::Mock::VerifyAndClearExpectations(consumer());
}
// Sending one more frame should be ignored. Over and over. None of the
// feedback reports from the Receiver should indicate it is collecting packets
// for future frames.
int ignored_frame = kMaxUnackedFrames;
for (int i = 0; i < 5; ++i) {
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::first() + (ignored_frame - 1),
kTargetPlayoutDelayChange))
.Times(AtLeast(0));
EXPECT_CALL(*sender(), OnReceiverIsMissingPackets(_)).Times(0);
sender()->SetFrameBeingSent(SimulatedFrame(start_time, ignored_frame));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
testing::Mock::VerifyAndClearExpectations(sender());
testing::Mock::VerifyAndClearExpectations(consumer());
}
// Consume only one frame, and confirm the Receiver allows only one frame more
// to be received.
ConsumeAndVerifyFrames(0, 0, start_time);
int no_longer_ignored_frame = ignored_frame;
++ignored_frame;
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(AtLeast(1));
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::first() + no_longer_ignored_frame,
kTargetPlayoutDelayChange))
.Times(AtLeast(1));
EXPECT_CALL(*sender(), OnReceiverIsMissingPackets(_)).Times(0);
// This frame should be received successfully.
sender()->SetFrameBeingSent(
SimulatedFrame(start_time, no_longer_ignored_frame));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
// This second frame should be ignored, however.
sender()->SetFrameBeingSent(SimulatedFrame(start_time, ignored_frame));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
testing::Mock::VerifyAndClearExpectations(sender());
testing::Mock::VerifyAndClearExpectations(consumer());
}
// Tests that incomplete frames that would be played-out too late are dropped,
// but only as inter-frame data dependency requirements permit, and only if no
// target playout delay change information would have been missed.
TEST_F(ReceiverTest, DropsLateFrames) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Before any packets have been sent/received, the Receiver should indicate no
// frames are ready.
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Set a ridiculously-large estimated player processing time so that the logic
// thinks every frame going to play out too late.
receiver()->SetPlayerProcessingTime(seconds(3));
expected_player_processing_time_ = seconds(3);
// In this test there are eight frames total:
// - Frame 0: Key frame.
// - Frames 1-4: Non-key frames.
// - Frame 5: Non-key frame that contains a target playout delay change.
// - Frame 6: Key frame.
// - Frame 7: Non-key frame.
ASSERT_EQ(SimulatedFrame::kPlayoutChangeAtFrame, 5);
SimulatedFrame frames[8] = {{start_time, 0}, {start_time, 1}, {start_time, 2},
{start_time, 3}, {start_time, 4}, {start_time, 5},
{start_time, 6}, {start_time, 7}};
frames[6].dependency = EncodedFrame::Dependency::kKeyFrame;
frames[6].referenced_frame_id = frames[6].frame_id;
// Send just packet 1 (NOT packet 0) of all the frames. The Receiver should
// never notify the consumer via the callback, nor report that any frames are
// ready, because none of the frames have been completely received.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
for (int i = 0; i <= 7; ++i) {
sender()->SetFrameBeingSent(frames[i]);
// Assumption: There are at least three packets in each frame, else the test
// is not exercising the logic meaningfully.
ASSERT_LE(size_t{3}, sender()->GetAllPacketIds().size());
sender()->SendRtpPackets({FramePacketId{1}});
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
}
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Send all the packets of Frame 6 (the second key frame) and Frame 7. The
// Receiver still cannot drop any frames because it has not seen packet 0 of
// every prior frame. In other words, it cannot ignore any possibility of a
// target playout delay change from the Sender.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
for (int i = 6; i <= 7; ++i) {
sender()->SetFrameBeingSent(frames[i]);
sender()->SendRtpPackets(sender()->GetAllPacketIds());
}
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Send packet 0 for all but Frame 5, which contains a target playout delay
// change. All but the last two frames will still be incomplete. The Receiver
// still cannot drop any frames because it doesn't know whether Frame 5 had a
// target playout delay change.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
for (int i = 0; i <= 7; ++i) {
if (i == 5) {
continue;
}
sender()->SetFrameBeingSent(frames[i]);
sender()->SendRtpPackets({FramePacketId{0}});
}
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Finally, send packet 0 for Frame 5. Now, the Receiver will drop every frame
// before the completely-received second key frame, as they are all still
// incomplete and will play-out too late. When it drops the frames, it will
// notify the sender of the new checkpoint so that it stops trying to
// re-transmit the dropped frames.
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(FrameId::first() + 7,
kTargetPlayoutDelayChange))
.Times(1);
sender()->SetFrameBeingSent(frames[5]);
sender()->SendRtpPackets({FramePacketId{0}});
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
// Note: Consuming Frame 6 will trigger the checkpoint advancement, since the
// call to AdvanceToNextFrame() contains the frame skipping/dropping logic.
ConsumeAndVerifyFrame(frames[6]);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
// After consuming Frame 6, the Receiver knows Frame 7 is also available and
// should have scheduled an immediate task to notify the Consumer of this.
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
// Now consume Frame 7. This shouldn't trigger any further checkpoint
// advancement.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
ConsumeAndVerifyFrame(frames[7]);
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
}
TEST_F(ReceiverTest, DropsLateFramesWithSkipping) {
ConstructReceiver({.allow_skipping = true});
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Before any packets have been sent/received, the Receiver should indicate no
// frames are ready.
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Set a ridiculously-large estimated player processing time so that the logic
// thinks every frame going to play out too late.
receiver()->SetPlayerProcessingTime(seconds(3));
expected_player_processing_time_ = seconds(3);
// In this test there are eight frames total:
// - Frame 0: Key frame.
// - Frames 1-4: Non-key frames.
// - Frame 5: Non-key frame that contains a target playout delay change.
// - Frame 6: Key frame.
// - Frame 7: Non-key frame.
ASSERT_EQ(SimulatedFrame::kPlayoutChangeAtFrame, 5);
SimulatedFrame frames[8] = {{start_time, 0}, {start_time, 1}, {start_time, 2},
{start_time, 3}, {start_time, 4}, {start_time, 5},
{start_time, 6}, {start_time, 7}};
frames[6].dependency = EncodedFrame::Dependency::kKeyFrame;
frames[6].referenced_frame_id = frames[6].frame_id;
// Send just packet 1 (NOT packet 0) of all the frames. The Receiver should
// never notify the consumer via the callback, nor report that any frames are
// ready, because none of the frames have been completely received.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
for (int i = 0; i <= 7; ++i) {
sender()->SetFrameBeingSent(frames[i]);
// Assumption: There are at least three packets in each frame, else the test
// is not exercising the logic meaningfully.
ASSERT_LE(size_t{3}, sender()->GetAllPacketIds().size());
sender()->SendRtpPackets({FramePacketId{1}});
AdvanceClockAndRunTasks(SimulatedFrame::kFrameDuration);
}
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
EXPECT_FALSE(receiver()->AdvanceToNextFrame().has_value());
// Send all the packets of Frame 6 (the second key frame) and Frame 7.
// Under two-pass logic, the Receiver will drop every prior frame and skip
// ahead to Frame 6, because Frame 6 is an independent key frame and is
// complete, preventing head-of-line blocking from the older incomplete frames
// (even though their packet 0 is missing, meaning their
// estimated_capture_time is unset).
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
EXPECT_CALL(*sender(),
OnReceiverCheckpoint(FrameId::first() + 7, kTargetPlayoutDelay))
.Times(1);
for (int i = 6; i <= 7; ++i) {
sender()->SetFrameBeingSent(frames[i]);
sender()->SendRtpPackets(sender()->GetAllPacketIds());
}
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
// Note: Consuming Frame 6 will trigger the checkpoint advancement, since the
// call to AdvanceToNextFrame() contains the frame skipping/dropping logic.
ConsumeAndVerifyFrame(frames[6], kTargetPlayoutDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
// After consuming Frame 6, the Receiver knows Frame 7 is also available and
// should have scheduled an immediate task to notify the Consumer of this.
EXPECT_CALL(*consumer(), OnFramesReady(Gt(0))).Times(1);
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
// Now consume Frame 7. This shouldn't trigger any further checkpoint
// advancement.
EXPECT_CALL(*consumer(), OnFramesReady(_)).Times(0);
EXPECT_CALL(*sender(), OnReceiverCheckpoint(_, _)).Times(0);
ConsumeAndVerifyFrame(frames[7], kTargetPlayoutDelay);
AdvanceClockAndRunTasks(kOneWayNetworkDelay);
testing::Mock::VerifyAndClearExpectations(consumer());
testing::Mock::VerifyAndClearExpectations(sender());
}
// Verifies that a playout event is correctly reported to the sender.
TEST_F(ReceiverTest, ReportsFrameAckAndPacketReceivedEvents) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
const SimulatedFrame kFrame(start_time, 0);
// Intercept the playout event logs sent to the sender.
std::vector<RtcpReceiverFrameLogMessage> logs;
EXPECT_CALL(*sender(), OnCastReceiverFrameLogMessages(_))
.WillRepeatedly([&](std::vector<RtcpReceiverFrameLogMessage> new_logs) {
logs.insert(logs.end(), new_logs.begin(), new_logs.end());
});
// Send a frame, and consume it.
ReceiveFrame(0, start_time, kTargetPlayoutDelay, kRoundTripNetworkDelay);
ConsumeAndVerifyFrame(kFrame);
AdvanceClockAndRunTasks(kRtcpReportInterval);
ASSERT_EQ(logs.size(), 1u);
const auto& frame_log = logs[0];
EXPECT_EQ(frame_log.rtp_timestamp, kFrame.rtp_timestamp);
ASSERT_EQ(frame_log.messages.size(), 6u);
// We should have gotten five valid packet received messages.
for (int i = 0; i < 5; ++i) {
const auto& message = frame_log.messages[i];
EXPECT_EQ(message.type, StatisticsEvent::Type::kPacketReceived);
EXPECT_THAT(message.delay, EqualsDuration(milliseconds(0)));
EXPECT_THAT(message.timestamp,
EqualsDuration(start_time + milliseconds(9)));
}
// And one frame acknowledgement.
EXPECT_EQ(frame_log.messages[5].type, StatisticsEvent::Type::kFrameAckSent);
EXPECT_THAT(frame_log.messages[5].delay, EqualsDuration(milliseconds(0)));
EXPECT_THAT(frame_log.messages[5].timestamp,
EqualsDuration(start_time + milliseconds(9)));
}
// Verifies that a playout event is correctly reported to the sender.
TEST_F(ReceiverTest, ReportPlayoutEvent) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
const SimulatedFrame kPlayedOutFrame(start_time, 0);
// Send a frame, and consume it.
ReceiveFrame(0, start_time, kTargetPlayoutDelay, kRoundTripNetworkDelay);
ConsumeAndVerifyFrame(kPlayedOutFrame);
// Intercept the playout event logs sent to the sender.
std::vector<RtcpReceiverFrameLogMessage> logs;
EXPECT_CALL(*sender(), OnCastReceiverFrameLogMessages(_))
.WillRepeatedly([&](std::vector<RtcpReceiverFrameLogMessage> new_logs) {
logs.insert(logs.end(), new_logs.begin(), new_logs.end());
});
receiver()->ReportPlayoutEvent(kPlayedOutFrame.frame_id,
kPlayedOutFrame.rtp_timestamp, now());
AdvanceClockAndRunTasks(kRtcpReportInterval + kOneWayNetworkDelay);
ASSERT_EQ(logs.size(), 1u);
// Only check out the log for the frame playout event.
const auto& frame_log = logs[0];
EXPECT_EQ(frame_log.rtp_timestamp, kPlayedOutFrame.rtp_timestamp);
ASSERT_EQ(frame_log.messages.size(), 1u);
const auto& event_log1 = frame_log.messages[0];
EXPECT_EQ(event_log1.type, StatisticsEvent::Type::kFramePlayedOut);
EXPECT_THAT(event_log1.delay, EqualsDuration(milliseconds(9)));
EXPECT_THAT(event_log1.timestamp,
EqualsDuration(start_time + milliseconds(12)));
}
// This test ensures that frames played ahead of schedule are reported
// accurately, without causing any errors.
TEST_F(ReceiverTest, ReportPlayoutEventTooEarly) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
const SimulatedFrame kPlayedOutFrame(start_time, 0);
// Send a frame, and consume it.
ReceiveFrame(0, start_time, kTargetPlayoutDelay, kRoundTripNetworkDelay);
ConsumeAndVerifyFrame(kPlayedOutFrame);
// Intercept the playout event logs sent to the sender.
EXPECT_CALL(*sender(), OnCastReceiverFrameLogMessages(_))
.WillOnce([&](std::vector<RtcpReceiverFrameLogMessage> logs) {
ASSERT_EQ(logs.size(), 1u);
const auto& frame_log = logs[0];
EXPECT_EQ(frame_log.rtp_timestamp, kPlayedOutFrame.rtp_timestamp);
ASSERT_EQ(frame_log.messages.size(), 1u);
EXPECT_EQ(frame_log.messages[0].type,
StatisticsEvent::Type::kFramePlayedOut);
EXPECT_THAT(frame_log.messages[0].delay,
EqualsDuration(milliseconds(59)));
EXPECT_THAT(frame_log.messages[0].timestamp,
EqualsDuration(start_time + milliseconds(62)));
});
AdvanceClockAndRunTasks(milliseconds(50));
receiver()->ReportPlayoutEvent(kPlayedOutFrame.frame_id,
kPlayedOutFrame.rtp_timestamp, now());
AdvanceClockAndRunTasks(kRtcpReportInterval + kOneWayNetworkDelay);
}
// Verifies that reporting a playout event for a frame that is too old
// results in a specific error.
TEST_F(ReceiverTest, ReportPlayoutEventForTooOldFrame) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
// Send and consume kMaxUnackedFrames + 1 frames.
for (int i = 0; i < kMaxUnackedFrames + 1; ++i) {
sender()->SetFrameBeingSent(SimulatedFrame(start_time, i));
sender()->SendRtpPackets(sender()->GetAllPacketIds());
AdvanceClockAndRunTasks(kRoundTripNetworkDelay);
const std::optional<size_t> payload_size = receiver()->AdvanceToNextFrame();
ASSERT_TRUE(payload_size.has_value());
std::vector<uint8_t> buffer(*payload_size);
receiver()->ConsumeNextFrame(buffer);
}
// Now, try to report a playout event for the first frame, which is now too
// old.
const SimulatedFrame kTooOldFrame(start_time, 0);
const Error error = receiver()->ReportPlayoutEvent(
kTooOldFrame.frame_id, kTooOldFrame.rtp_timestamp, now());
EXPECT_EQ(error.code(), Error::Code::kParameterOutOfRange);
}
// This test verifies that when the calculated playout delay is negative, it
// is correctly reported as zero (adjusting for the clock offset and network
// delay).
TEST_F(ReceiverTest, ReportPlayoutEventWithZeroDelay) {
const Clock::time_point start_time = FakeClock::now();
ExchangeInitialReportPackets(start_time);
const SimulatedFrame kPlayedOutFrame(start_time, 0);
// Send a frame, and consume it.
ReceiveFrame(0, start_time, kTargetPlayoutDelay, kRoundTripNetworkDelay);
ConsumeAndVerifyFrame(kPlayedOutFrame);
// Intercept the playout event logs sent to the sender.
EXPECT_CALL(*sender(), OnCastReceiverFrameLogMessages(_))
.WillOnce([&](std::vector<RtcpReceiverFrameLogMessage> logs) {
ASSERT_EQ(logs.size(), 1u);
const auto& frame_log = logs[0];
EXPECT_EQ(frame_log.rtp_timestamp, kPlayedOutFrame.rtp_timestamp);
ASSERT_EQ(frame_log.messages.size(), 1u);
const auto& event_log1 = frame_log.messages[0];
EXPECT_EQ(event_log1.type, StatisticsEvent::Type::kFramePlayedOut);
EXPECT_THAT(event_log1.delay, EqualsDuration(milliseconds(9)));
EXPECT_THAT(event_log1.timestamp,
EqualsDuration(start_time + 2 * kRoundTripNetworkDelay));
});
receiver()->ReportPlayoutEvent(kPlayedOutFrame.frame_id,
kPlayedOutFrame.rtp_timestamp, now());
AdvanceClockAndRunTasks(kRtcpReportInterval + kOneWayNetworkDelay);
}
} // namespace
} // namespace openscreen::cast