You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1150 lines
49 KiB
1150 lines
49 KiB
// Copyright 2020 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/sender.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "absl/types/span.h"
|
|
#include "cast/streaming/compound_rtcp_builder.h"
|
|
#include "cast/streaming/constants.h"
|
|
#include "cast/streaming/encoded_frame.h"
|
|
#include "cast/streaming/frame_collector.h"
|
|
#include "cast/streaming/frame_crypto.h"
|
|
#include "cast/streaming/frame_id.h"
|
|
#include "cast/streaming/mock_environment.h"
|
|
#include "cast/streaming/packet_util.h"
|
|
#include "cast/streaming/rtcp_session.h"
|
|
#include "cast/streaming/rtp_defines.h"
|
|
#include "cast/streaming/rtp_packet_parser.h"
|
|
#include "cast/streaming/sender_packet_router.h"
|
|
#include "cast/streaming/sender_report_parser.h"
|
|
#include "cast/streaming/session_config.h"
|
|
#include "cast/streaming/ssrc.h"
|
|
#include "cast/streaming/testing/simple_socket_subscriber.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "platform/test/fake_clock.h"
|
|
#include "platform/test/fake_task_runner.h"
|
|
#include "util/alarm.h"
|
|
#include "util/chrono_helpers.h"
|
|
#include "util/yet_another_bit_vector.h"
|
|
|
|
using testing::_;
|
|
using testing::AtLeast;
|
|
using testing::Invoke;
|
|
using testing::InvokeWithoutArgs;
|
|
using testing::Mock;
|
|
using testing::NiceMock;
|
|
using testing::Return;
|
|
using testing::Sequence;
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
namespace {
|
|
|
|
// Sender configuration.
|
|
constexpr Ssrc kSenderSsrc = 1;
|
|
constexpr Ssrc kReceiverSsrc = 2;
|
|
constexpr int kRtpTimebase = 48000;
|
|
constexpr milliseconds kTargetPlayoutDelay{400};
|
|
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 RtpPayloadType kRtpPayloadType = RtpPayloadType::kVideoVp8;
|
|
|
|
// The number of RTP ticks advanced per frame, for 100 FPS media.
|
|
constexpr int kRtpTicksPerFrame = kRtpTimebase / 100;
|
|
|
|
// The number of milliseconds advanced per frame, for 100 FPS media.
|
|
constexpr milliseconds kFrameDuration{1000 / 100};
|
|
static_assert(kFrameDuration < (kTargetPlayoutDelay / 10),
|
|
"Kickstart test assumes frame duration is far less than the "
|
|
"playout delay.");
|
|
|
|
// An Encoded frame that also holds onto its own copy of data.
|
|
struct EncodedFrameWithBuffer : public EncodedFrame {
|
|
// |EncodedFrame::data| always points inside buffer.begin()...buffer.end().
|
|
std::vector<uint8_t> buffer;
|
|
};
|
|
|
|
// SenderPacketRouter configuration for these tests.
|
|
constexpr int kNumPacketsPerBurst = 20;
|
|
constexpr milliseconds kBurstInterval{10};
|
|
|
|
// An arbitrary value, subtracted from "now," to specify the reference_time on
|
|
// frames that are about to be enqueued. This simulates that capture+encode
|
|
// happened in the past, before Sender::EnqueueFrame() is called.
|
|
constexpr milliseconds kCaptureDelay{11};
|
|
|
|
// In some tests, the computed time values could be off a little bit due to
|
|
// imprecision in certain wire-format timestamp types. The following macro
|
|
// behaves just like Gtest's EXPECT_NEAR(), but works with all the time types
|
|
// too.
|
|
#define EXPECT_NEARLY_EQUAL(duration_a, duration_b, epsilon) \
|
|
if ((duration_a) >= (duration_b)) { \
|
|
EXPECT_LE((duration_a), (duration_b) + (epsilon)); \
|
|
} else { \
|
|
EXPECT_GE((duration_a), (duration_b) - (epsilon)); \
|
|
}
|
|
|
|
void OverrideRtpTimestamp(int frame_count, EncodedFrame* frame, int fps) {
|
|
const int ticks = frame_count * kRtpTimebase / fps;
|
|
frame->rtp_timestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(ticks);
|
|
}
|
|
|
|
// Simulates UDP/IPv6 traffic in one direction (from Sender→Receiver, or
|
|
// Receiver→Sender), with a settable amount of delay.
|
|
class SimulatedNetworkPipe {
|
|
public:
|
|
SimulatedNetworkPipe(TaskRunner* task_runner,
|
|
Environment::PacketConsumer* remote)
|
|
: task_runner_(task_runner), remote_(remote) {
|
|
// Create a fake IPv6 address using the "documentative purposes" prefix
|
|
// concatenated with the |this| pointer.
|
|
std::array<uint16_t, 8> hextets{};
|
|
hextets[0] = 0x2001;
|
|
hextets[1] = 0x0db8;
|
|
auto* const this_pointer = this;
|
|
static_assert(sizeof(this_pointer) <= (6 * sizeof(uint16_t)), "");
|
|
memcpy(&hextets[2], &this_pointer, sizeof(this_pointer));
|
|
local_endpoint_ = IPEndpoint{IPAddress(hextets), 2344};
|
|
}
|
|
|
|
const IPEndpoint& local_endpoint() const { return local_endpoint_; }
|
|
|
|
Clock::duration network_delay() const { return network_delay_; }
|
|
void set_network_delay(Clock::duration delay) { network_delay_ = delay; }
|
|
|
|
// The caller needs to spin the task runner before |packet| will reach the
|
|
// other side.
|
|
void StartPacketTransmission(std::vector<uint8_t> packet) {
|
|
task_runner_->PostTaskWithDelay(
|
|
[this, packet = std::move(packet)]() mutable {
|
|
remote_->OnReceivedPacket(local_endpoint_, FakeClock::now(),
|
|
std::move(packet));
|
|
},
|
|
network_delay_);
|
|
}
|
|
|
|
private:
|
|
TaskRunner* const task_runner_;
|
|
Environment::PacketConsumer* const remote_;
|
|
|
|
IPEndpoint local_endpoint_;
|
|
|
|
// The amount of time for the packet to transmit over this simulated network
|
|
// pipe. Defaults to zero to simplify the tests that don't care about delays.
|
|
Clock::duration network_delay_{};
|
|
};
|
|
|
|
// Processes packets from the Sender under test, allowing unit tests to set
|
|
// expectations for parsed RTP or RTCP packets, to confirm proper behavior of
|
|
// the Sender.
|
|
class MockReceiver : public Environment::PacketConsumer {
|
|
public:
|
|
explicit MockReceiver(SimulatedNetworkPipe* pipe_to_sender)
|
|
: pipe_to_sender_(pipe_to_sender),
|
|
rtcp_session_(kSenderSsrc, kReceiverSsrc, FakeClock::now()),
|
|
sender_report_parser_(&rtcp_session_),
|
|
rtcp_builder_(&rtcp_session_),
|
|
rtp_parser_(kSenderSsrc),
|
|
crypto_(kAesKey, kCastIvMask) {
|
|
rtcp_builder_.SetPlayoutDelay(kTargetPlayoutDelay);
|
|
}
|
|
|
|
~MockReceiver() override = default;
|
|
|
|
// Simulate the Receiver ACK'ing all frames up to and including the
|
|
// |new_checkpoint|.
|
|
void SetCheckpointFrame(FrameId new_checkpoint) {
|
|
OSP_CHECK_GE(new_checkpoint, rtcp_builder_.checkpoint_frame());
|
|
rtcp_builder_.SetCheckpointFrame(new_checkpoint);
|
|
}
|
|
|
|
// Automatically advances the checkpoint based on what is found in
|
|
// |complete_frames_|, returning true if the checkpoint moved forward.
|
|
bool AutoAdvanceCheckpoint() {
|
|
const FrameId old_checkpoint = rtcp_builder_.checkpoint_frame();
|
|
FrameId new_checkpoint = old_checkpoint;
|
|
for (auto it = complete_frames_.upper_bound(old_checkpoint);
|
|
it != complete_frames_.end(); ++it) {
|
|
if (it->first != new_checkpoint + 1) {
|
|
break;
|
|
}
|
|
++new_checkpoint;
|
|
}
|
|
if (new_checkpoint > old_checkpoint) {
|
|
rtcp_builder_.SetCheckpointFrame(new_checkpoint);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SetPictureLossIndicator(bool picture_is_lost) {
|
|
rtcp_builder_.SetPictureLossIndicator(picture_is_lost);
|
|
}
|
|
|
|
void SetReceiverReport(StatusReportId reply_for,
|
|
RtcpReportBlock::Delay processing_delay) {
|
|
RtcpReportBlock receiver_report;
|
|
receiver_report.ssrc = kSenderSsrc;
|
|
receiver_report.last_status_report_id = reply_for;
|
|
receiver_report.delay_since_last_report = processing_delay;
|
|
rtcp_builder_.IncludeReceiverReportInNextPacket(receiver_report);
|
|
}
|
|
|
|
void SetNacksAndAcks(std::vector<PacketNack> packet_nacks,
|
|
std::vector<FrameId> frame_acks) {
|
|
rtcp_builder_.IncludeFeedbackInNextPacket(std::move(packet_nacks),
|
|
std::move(frame_acks));
|
|
}
|
|
|
|
// Builds and sends a RTCP packet containing one or more of: checkpoint, PLI,
|
|
// Receiver Report, NACKs, ACKs.
|
|
void TransmitRtcpFeedbackPacket() {
|
|
uint8_t buffer[kMaxRtpPacketSizeForIpv6UdpOnEthernet];
|
|
const absl::Span<uint8_t> packet =
|
|
rtcp_builder_.BuildPacket(FakeClock::now(), buffer);
|
|
pipe_to_sender_->StartPacketTransmission(
|
|
std::vector<uint8_t>(packet.begin(), packet.end()));
|
|
}
|
|
|
|
// Used by tests to simulate the Receiver not seeing specific packets come in
|
|
// from the network (e.g., because the network dropped the packets).
|
|
void SetIgnoreList(std::vector<PacketNack> ignore_list) {
|
|
ignore_list_ = ignore_list;
|
|
}
|
|
|
|
// Environment::PacketConsumer implementation.
|
|
//
|
|
// Called to process a packet from the Sender, simulating basic RTP frame
|
|
// collection and Sender Report parsing/handling.
|
|
void OnReceivedPacket(const IPEndpoint& source,
|
|
Clock::time_point arrival_time,
|
|
std::vector<uint8_t> packet) override {
|
|
const auto type_and_ssrc = InspectPacketForRouting(packet);
|
|
EXPECT_NE(ApparentPacketType::UNKNOWN, type_and_ssrc.first);
|
|
EXPECT_EQ(kSenderSsrc, type_and_ssrc.second);
|
|
if (type_and_ssrc.first == ApparentPacketType::RTP) {
|
|
const absl::optional<RtpPacketParser::ParseResult> part_of_frame =
|
|
rtp_parser_.Parse(packet);
|
|
ASSERT_TRUE(part_of_frame);
|
|
|
|
// Return early if simulating packet drops over the network.
|
|
if (std::find_if(ignore_list_.begin(), ignore_list_.end(),
|
|
[&](const PacketNack& baddie) {
|
|
return (
|
|
baddie.frame_id == part_of_frame->frame_id &&
|
|
(baddie.packet_id == kAllPacketsLost ||
|
|
baddie.packet_id == part_of_frame->packet_id));
|
|
}) != ignore_list_.end()) {
|
|
return;
|
|
}
|
|
|
|
OnRtpPacket(*part_of_frame);
|
|
CollectRtpPacket(*part_of_frame, std::move(packet));
|
|
} else if (type_and_ssrc.first == ApparentPacketType::RTCP) {
|
|
absl::optional<SenderReportParser::SenderReportWithId> report =
|
|
sender_report_parser_.Parse(packet);
|
|
ASSERT_TRUE(report);
|
|
OnSenderReport(*report);
|
|
}
|
|
}
|
|
|
|
std::map<FrameId, EncodedFrameWithBuffer> TakeCompleteFrames() {
|
|
std::map<FrameId, EncodedFrameWithBuffer> result;
|
|
result.swap(complete_frames_);
|
|
return result;
|
|
}
|
|
|
|
// Tests set expectations on these mocks to monitor events of interest, and/or
|
|
// invoke additional behaviors.
|
|
MOCK_METHOD1(OnRtpPacket,
|
|
void(const RtpPacketParser::ParseResult& parsed_packet));
|
|
MOCK_METHOD1(OnFrameComplete, void(FrameId frame_id));
|
|
MOCK_METHOD1(OnSenderReport,
|
|
void(const SenderReportParser::SenderReportWithId& report));
|
|
|
|
private:
|
|
// Collects the individual RTP packets until a whole frame can be formed, then
|
|
// calls OnFrameComplete(). Ignores extra RTP packets that are no longer
|
|
// needed.
|
|
void CollectRtpPacket(const RtpPacketParser::ParseResult& part_of_frame,
|
|
std::vector<uint8_t> packet) {
|
|
const FrameId frame_id = part_of_frame.frame_id;
|
|
if (complete_frames_.find(frame_id) != complete_frames_.end()) {
|
|
return;
|
|
}
|
|
FrameCollector& collector = incomplete_frames_[frame_id];
|
|
collector.set_frame_id(frame_id);
|
|
EXPECT_TRUE(collector.CollectRtpPacket(part_of_frame, &packet));
|
|
if (!collector.is_complete()) {
|
|
return;
|
|
}
|
|
const EncryptedFrame& encrypted = collector.PeekAtAssembledFrame();
|
|
EncodedFrameWithBuffer* const decrypted = &complete_frames_[frame_id];
|
|
// Note: Not setting decrypted->reference_time here since the logic around
|
|
// calculating the playout time is rather complex, and is definitely outside
|
|
// the scope of the testing being done in this module. Instead, end-to-end
|
|
// testing should exist elsewhere to confirm frame play-out times with real
|
|
// Receivers.
|
|
decrypted->buffer.resize(FrameCrypto::GetPlaintextSize(encrypted));
|
|
decrypted->data = absl::Span<uint8_t>(decrypted->buffer);
|
|
crypto_.Decrypt(encrypted, decrypted);
|
|
incomplete_frames_.erase(frame_id);
|
|
OnFrameComplete(frame_id);
|
|
}
|
|
|
|
SimulatedNetworkPipe* const pipe_to_sender_;
|
|
RtcpSession rtcp_session_;
|
|
SenderReportParser sender_report_parser_;
|
|
CompoundRtcpBuilder rtcp_builder_;
|
|
RtpPacketParser rtp_parser_;
|
|
FrameCrypto crypto_;
|
|
|
|
std::vector<PacketNack> ignore_list_;
|
|
std::map<FrameId, FrameCollector> incomplete_frames_;
|
|
std::map<FrameId, EncodedFrameWithBuffer> complete_frames_;
|
|
};
|
|
|
|
class MockObserver : public Sender::Observer {
|
|
public:
|
|
MOCK_METHOD1(OnFrameCanceled, void(FrameId frame_id));
|
|
MOCK_METHOD0(OnPictureLost, void());
|
|
};
|
|
|
|
class SenderTest : public testing::Test {
|
|
public:
|
|
SenderTest()
|
|
: fake_clock_(Clock::now()),
|
|
task_runner_(&fake_clock_),
|
|
sender_environment_(&FakeClock::now, &task_runner_),
|
|
sender_packet_router_(&sender_environment_,
|
|
kNumPacketsPerBurst,
|
|
kBurstInterval),
|
|
sender_(&sender_environment_,
|
|
&sender_packet_router_,
|
|
{/* .sender_ssrc = */ kSenderSsrc,
|
|
/* .receiver_ssrc = */ kReceiverSsrc,
|
|
/* .rtp_timebase = */ kRtpTimebase,
|
|
/* .channels = */ 2,
|
|
/* .target_playout_delay = */ kTargetPlayoutDelay,
|
|
/* .aes_secret_key = */ kAesKey,
|
|
/* .aes_iv_mask = */ kCastIvMask,
|
|
/* .is_pli_enabled = */ true},
|
|
kRtpPayloadType),
|
|
receiver_to_sender_pipe_(&task_runner_, &sender_packet_router_),
|
|
receiver_(&receiver_to_sender_pipe_),
|
|
sender_to_receiver_pipe_(&task_runner_, &receiver_) {
|
|
sender_environment_.SetSocketSubscriber(&socket_subscriber_);
|
|
sender_environment_.set_remote_endpoint(
|
|
receiver_to_sender_pipe_.local_endpoint());
|
|
ON_CALL(sender_environment_, SendPacket(_))
|
|
.WillByDefault(Invoke([this](absl::Span<const uint8_t> packet) {
|
|
sender_to_receiver_pipe_.StartPacketTransmission(
|
|
std::vector<uint8_t>(packet.begin(), packet.end()));
|
|
}));
|
|
}
|
|
|
|
~SenderTest() override = default;
|
|
|
|
Sender* sender() { return &sender_; }
|
|
MockReceiver* receiver() { return &receiver_; }
|
|
|
|
void SetReceiverToSenderNetworkDelay(Clock::duration delay) {
|
|
receiver_to_sender_pipe_.set_network_delay(delay);
|
|
}
|
|
|
|
void SetSenderToReceiverNetworkDelay(Clock::duration delay) {
|
|
sender_to_receiver_pipe_.set_network_delay(delay);
|
|
}
|
|
|
|
void SimulateExecution(Clock::duration how_long = Clock::duration::zero()) {
|
|
fake_clock_.Advance(how_long);
|
|
}
|
|
|
|
static void PopulateFramePayloadBuffer(int seed,
|
|
int num_bytes,
|
|
std::vector<uint8_t>* payload) {
|
|
payload->clear();
|
|
payload->reserve(num_bytes);
|
|
for (int i = 0; i < num_bytes; ++i) {
|
|
payload->push_back(static_cast<uint8_t>(seed + i));
|
|
}
|
|
}
|
|
|
|
static void PopulateFrameWithDefaults(FrameId frame_id,
|
|
Clock::time_point reference_time,
|
|
int seed,
|
|
int num_payload_bytes,
|
|
EncodedFrameWithBuffer* frame) {
|
|
frame->dependency = (frame_id == FrameId::first())
|
|
? EncodedFrame::KEY_FRAME
|
|
: EncodedFrame::DEPENDS_ON_ANOTHER;
|
|
frame->frame_id = frame_id;
|
|
frame->referenced_frame_id = frame->frame_id;
|
|
if (frame_id != FrameId::first()) {
|
|
--frame->referenced_frame_id;
|
|
}
|
|
frame->rtp_timestamp =
|
|
RtpTimeTicks() + (RtpTimeDelta::FromTicks(kRtpTicksPerFrame) *
|
|
(frame_id - FrameId::first()));
|
|
frame->reference_time = reference_time;
|
|
PopulateFramePayloadBuffer(seed, num_payload_bytes, &frame->buffer);
|
|
frame->data = absl::Span<uint8_t>(frame->buffer);
|
|
}
|
|
|
|
// Confirms that all |sent_frames| exist in |received_frames|, with identical
|
|
// data and metadata.
|
|
static void ExpectFramesReceivedCorrectly(
|
|
absl::Span<EncodedFrameWithBuffer> sent_frames,
|
|
const std::map<FrameId, EncodedFrameWithBuffer> received_frames) {
|
|
ASSERT_EQ(sent_frames.size(), received_frames.size());
|
|
|
|
for (const EncodedFrameWithBuffer& sent_frame : sent_frames) {
|
|
SCOPED_TRACE(testing::Message()
|
|
<< "Checking sent frame " << sent_frame.frame_id);
|
|
const auto received_it = received_frames.find(sent_frame.frame_id);
|
|
if (received_it == received_frames.end()) {
|
|
ADD_FAILURE() << "Did not receive frame.";
|
|
continue;
|
|
}
|
|
const EncodedFrame& received_frame = received_it->second;
|
|
EXPECT_EQ(sent_frame.dependency, received_frame.dependency);
|
|
EXPECT_EQ(sent_frame.referenced_frame_id,
|
|
received_frame.referenced_frame_id);
|
|
EXPECT_EQ(sent_frame.rtp_timestamp, received_frame.rtp_timestamp);
|
|
EXPECT_TRUE(sent_frame.data == received_frame.data);
|
|
}
|
|
}
|
|
|
|
private:
|
|
FakeClock fake_clock_;
|
|
FakeTaskRunner task_runner_;
|
|
NiceMock<MockEnvironment> sender_environment_;
|
|
SenderPacketRouter sender_packet_router_;
|
|
Sender sender_;
|
|
SimulatedNetworkPipe receiver_to_sender_pipe_;
|
|
NiceMock<MockReceiver> receiver_;
|
|
SimulatedNetworkPipe sender_to_receiver_pipe_;
|
|
SimpleSubscriber socket_subscriber_;
|
|
};
|
|
|
|
// Tests that the Sender can send EncodedFrames over an ideal network (i.e., low
|
|
// latency, no loss), and does so without having to transmit the same packet
|
|
// twice.
|
|
TEST_F(SenderTest, SendsFramesEfficiently) {
|
|
constexpr milliseconds kOneWayNetworkDelay{1};
|
|
SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay);
|
|
SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay);
|
|
|
|
// Expect that each packet is only sent once.
|
|
std::set<std::pair<FrameId, FramePacketId>> received_packets;
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_))
|
|
.WillRepeatedly(
|
|
Invoke([&](const RtpPacketParser::ParseResult& parsed_packet) {
|
|
std::pair<FrameId, FramePacketId> id(parsed_packet.frame_id,
|
|
parsed_packet.packet_id);
|
|
const auto insert_result = received_packets.insert(id);
|
|
EXPECT_TRUE(insert_result.second)
|
|
<< "Received duplicate packet: " << id.first << ':'
|
|
<< static_cast<int>(id.second);
|
|
}));
|
|
|
|
// Simulate normal frame ACK'ing behavior.
|
|
ON_CALL(*receiver(), OnFrameComplete(_)).WillByDefault(InvokeWithoutArgs([&] {
|
|
if (receiver()->AutoAdvanceCheckpoint()) {
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}
|
|
}));
|
|
|
|
NiceMock<MockObserver> observer;
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first())).Times(1);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1)).Times(1);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2)).Times(1);
|
|
sender()->SetObserver(&observer);
|
|
|
|
EncodedFrameWithBuffer frames[3];
|
|
constexpr int kFrameDataSizes[] = {8196, 12, 1900};
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i == 0) {
|
|
EXPECT_TRUE(sender()->NeedsKeyFrame());
|
|
} else {
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
}
|
|
PopulateFrameWithDefaults(FrameId::first() + i,
|
|
FakeClock::now() - kCaptureDelay, 0xbf - i,
|
|
kFrameDataSizes[i], &frames[i]);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i]));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
|
|
ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames());
|
|
}
|
|
|
|
// Tests that the Sender correctly computes the current in-flight media
|
|
// duration, a backlog signal for clients.
|
|
TEST_F(SenderTest, ComputesInFlightMediaDuration) {
|
|
// With no frames enqueued, the in-flight media duration should be zero.
|
|
EXPECT_EQ(Clock::duration::zero(),
|
|
sender()->GetInFlightMediaDuration(RtpTimeTicks()));
|
|
EXPECT_EQ(Clock::duration::zero(),
|
|
sender()->GetInFlightMediaDuration(
|
|
RtpTimeTicks() + RtpTimeDelta::FromTicks(kRtpTicksPerFrame)));
|
|
|
|
// Enqueue a frame.
|
|
EncodedFrameWithBuffer frame;
|
|
PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0,
|
|
13 /* bytes */, &frame);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
|
|
// Now, the in-flight media duration should depend on the RTP timestamp of the
|
|
// next frame.
|
|
EXPECT_EQ(kFrameDuration, sender()->GetInFlightMediaDuration(
|
|
frame.rtp_timestamp +
|
|
RtpTimeDelta::FromTicks(kRtpTicksPerFrame)));
|
|
EXPECT_EQ(10 * kFrameDuration,
|
|
sender()->GetInFlightMediaDuration(
|
|
frame.rtp_timestamp +
|
|
RtpTimeDelta::FromTicks(10 * kRtpTicksPerFrame)));
|
|
}
|
|
|
|
// Tests that the Sender computes the maximum in-flight media duration based on
|
|
// its analysis of current network conditions. By implication, this demonstrates
|
|
// that the Sender is also measuring the network round-trip time.
|
|
TEST_F(SenderTest, RespondsToNetworkLatencyChanges) {
|
|
// The expected maximum error in time calculations is one tick of the RTCP
|
|
// report block's delay type.
|
|
constexpr auto kEpsilon = to_nanoseconds(RtcpReportBlock::Delay(1));
|
|
|
|
// Before the Sender has the necessary information to compute the network
|
|
// round-trip time, GetMaxInFlightMediaDuration() will return half the target
|
|
// playout delay.
|
|
EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2,
|
|
sender()->GetMaxInFlightMediaDuration(), kEpsilon);
|
|
|
|
// No network is perfect. Simulate different one-way network delays.
|
|
constexpr milliseconds kOutboundDelay{2};
|
|
constexpr milliseconds kInboundDelay{4};
|
|
constexpr milliseconds kRoundTripDelay = kOutboundDelay + kInboundDelay;
|
|
SetSenderToReceiverNetworkDelay(kOutboundDelay);
|
|
SetReceiverToSenderNetworkDelay(kInboundDelay);
|
|
|
|
// Enqueue a frame in the Sender to start emitting periodic RTCP reports.
|
|
{
|
|
EncodedFrameWithBuffer frame;
|
|
PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0,
|
|
1 /* byte */, &frame);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
}
|
|
|
|
// Run one network round-trip from Sender→Receiver→Sender.
|
|
StatusReportId sender_report_id{};
|
|
EXPECT_CALL(*receiver(), OnSenderReport(_))
|
|
.WillOnce(Invoke(
|
|
[&](const SenderReportParser::SenderReportWithId& sender_report) {
|
|
sender_report_id = sender_report.report_id;
|
|
}));
|
|
// Simulate the passage of time for the Sender Report to reach the Receiver.
|
|
SimulateExecution(kOutboundDelay);
|
|
// The Receiver should have received the Sender Report at this point.
|
|
Mock::VerifyAndClearExpectations(receiver());
|
|
ASSERT_NE(StatusReportId{}, sender_report_id);
|
|
// Simulate the passage of time in the Receiver doing "other tasks" before
|
|
// replying back to the Sender. This delay is included in the Receiver Report
|
|
// so that the Sender can isolate the delays caused by the network.
|
|
constexpr milliseconds kReceiverProcessingDelay{2};
|
|
SimulateExecution(kReceiverProcessingDelay);
|
|
// Create the Receiver Report "reply," and simulate it being sent across the
|
|
// network, back to the Sender.
|
|
receiver()->SetReceiverReport(
|
|
sender_report_id, std::chrono::duration_cast<RtcpReportBlock::Delay>(
|
|
kReceiverProcessingDelay));
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(kInboundDelay);
|
|
|
|
// At this point, the Sender should have computed the network round-trip time,
|
|
// and so GetMaxInFlightMediaDuration() will return half the target playout
|
|
// delay PLUS half the network round-trip time.
|
|
EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2 + kRoundTripDelay / 2,
|
|
sender()->GetMaxInFlightMediaDuration(), kEpsilon);
|
|
|
|
// Increase the outbound delay, which will increase the total round-trip time.
|
|
constexpr milliseconds kIncreasedOutboundDelay{6};
|
|
constexpr milliseconds kIncreasedRoundTripDelay =
|
|
kIncreasedOutboundDelay + kInboundDelay;
|
|
SetSenderToReceiverNetworkDelay(kIncreasedOutboundDelay);
|
|
|
|
// With increased network delay, run several more network round-trips. Expect
|
|
// the Sender to gradually converge towards the new network round-trip time.
|
|
constexpr int kNumReportIntervals = 50;
|
|
EXPECT_CALL(*receiver(), OnSenderReport(_))
|
|
.Times(kNumReportIntervals)
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SenderReportParser::SenderReportWithId& sender_report) {
|
|
receiver()->SetReceiverReport(sender_report.report_id,
|
|
RtcpReportBlock::Delay::zero());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}));
|
|
Clock::duration last_max = sender()->GetMaxInFlightMediaDuration();
|
|
for (int i = 0; i < kNumReportIntervals; ++i) {
|
|
SimulateExecution(kRtcpReportInterval);
|
|
const Clock::duration updated_value =
|
|
sender()->GetMaxInFlightMediaDuration();
|
|
EXPECT_LE(last_max, updated_value);
|
|
last_max = updated_value;
|
|
}
|
|
EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2 + kIncreasedRoundTripDelay / 2,
|
|
sender()->GetMaxInFlightMediaDuration(), kEpsilon);
|
|
}
|
|
|
|
// Tests that the Sender rejects frames if too large a span of FrameIds would be
|
|
// in-flight at once.
|
|
TEST_F(SenderTest, RejectsEnqueuingBeforeProtocolDesignLimit) {
|
|
// For this test, use 1000 FPS. This makes the frames all one millisecond
|
|
// apart to avoid triggering the media-duration rejection logic.
|
|
constexpr int kFramesPerSecond = 1000;
|
|
constexpr milliseconds kFrameDuration{1};
|
|
|
|
// Send the absolute design-limit maximum number of frames.
|
|
int frame_count = 0;
|
|
for (; frame_count < kMaxUnackedFrames; ++frame_count) {
|
|
EncodedFrameWithBuffer frame;
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &frame);
|
|
OverrideRtpTimestamp(frame_count, &frame, kFramesPerSecond);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
|
|
// Now, attempting to enqueue just one more frame should fail.
|
|
EncodedFrameWithBuffer one_frame_too_much;
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &one_frame_too_much);
|
|
OverrideRtpTimestamp(frame_count++, &one_frame_too_much, kFramesPerSecond);
|
|
EXPECT_EQ(Sender::REACHED_ID_SPAN_LIMIT,
|
|
sender()->EnqueueFrame(one_frame_too_much));
|
|
SimulateExecution(kFrameDuration);
|
|
|
|
// Now, simulate the Receiver ACKing the first frame, and enqueuing should
|
|
// then succeed again.
|
|
receiver()->SetCheckpointFrame(FrameId::first());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
EXPECT_EQ(Sender::OK, sender()->EnqueueFrame(one_frame_too_much));
|
|
SimulateExecution(kFrameDuration);
|
|
|
|
// Finally, attempting to enqueue another frame should fail again.
|
|
EncodedFrameWithBuffer another_frame_too_much;
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &another_frame_too_much);
|
|
OverrideRtpTimestamp(frame_count++, &another_frame_too_much,
|
|
kFramesPerSecond);
|
|
EXPECT_EQ(Sender::REACHED_ID_SPAN_LIMIT,
|
|
sender()->EnqueueFrame(another_frame_too_much));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
|
|
TEST_F(SenderTest, CanCancelAllInFlightFrames) {
|
|
NiceMock<MockObserver> observer;
|
|
sender()->SetObserver(&observer);
|
|
|
|
// Send the absolute design-limit maximum number of frames.
|
|
for (int i = 0; i < kMaxUnackedFrames; ++i) {
|
|
EncodedFrameWithBuffer frame;
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &frame);
|
|
OverrideRtpTimestamp(i, &frame, 1000 /* fps */);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
|
|
EXPECT_CALL(observer, OnFrameCanceled(_)).Times(kMaxUnackedFrames);
|
|
sender()->CancelInFlightData();
|
|
}
|
|
|
|
// Tests that the Sender rejects frames if too-long a media duration is
|
|
// in-flight. This is the Sender's primary flow control mechanism.
|
|
TEST_F(SenderTest, RejectsEnqueuingIfTooLongMediaDurationIsInFlight) {
|
|
// For this test, use 20 FPS. This makes all frames 50 ms apart, which should
|
|
// make it easy to trigger the media-duration rejection logic.
|
|
constexpr int kFramesPerSecond = 20;
|
|
constexpr milliseconds kFrameDuration{50};
|
|
|
|
// Enqueue frames until one is rejected because the in-flight duration would
|
|
// be too high.
|
|
EncodedFrameWithBuffer frame;
|
|
int frame_count = 0;
|
|
for (; frame_count < kMaxUnackedFrames; ++frame_count) {
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &frame);
|
|
OverrideRtpTimestamp(frame_count, &frame, kFramesPerSecond);
|
|
const auto result = sender()->EnqueueFrame(frame);
|
|
SimulateExecution(kFrameDuration);
|
|
if (result == Sender::MAX_DURATION_IN_FLIGHT) {
|
|
break;
|
|
}
|
|
ASSERT_EQ(Sender::OK, result);
|
|
}
|
|
|
|
// Now, simulate the Receiver ACKing the first frame, and enqueuing should
|
|
// then succeed again.
|
|
receiver()->SetCheckpointFrame(FrameId::first());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
EXPECT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
SimulateExecution(kFrameDuration);
|
|
|
|
// However, attempting to enqueue another frame should fail again.
|
|
EncodedFrameWithBuffer one_frame_too_much;
|
|
PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0,
|
|
13 /* bytes */, &one_frame_too_much);
|
|
OverrideRtpTimestamp(++frame_count, &one_frame_too_much, kFramesPerSecond);
|
|
EXPECT_EQ(Sender::MAX_DURATION_IN_FLIGHT,
|
|
sender()->EnqueueFrame(one_frame_too_much));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
|
|
// Tests that the Sender propagates the Receiver's picture loss indicator to the
|
|
// Observer::OnPictureLost(), and via calls to NeedsKeyFrame(); but only when
|
|
// producing a key frame is absolutely necessary.
|
|
TEST_F(SenderTest, ManagesReceiverPictureLossWorkflow) {
|
|
NiceMock<MockObserver> observer;
|
|
sender()->SetObserver(&observer);
|
|
|
|
// Send three frames...
|
|
EncodedFrameWithBuffer frames[6];
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i == 0) {
|
|
EXPECT_TRUE(sender()->NeedsKeyFrame());
|
|
} else {
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
}
|
|
PopulateFrameWithDefaults(FrameId::first() + i,
|
|
FakeClock::now() - kCaptureDelay, 0,
|
|
24 /* bytes */, &frames[i]);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i]));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
|
|
// Simulate the Receiver ACK'ing the first three frames.
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first())).Times(1);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1)).Times(1);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2)).Times(1);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
receiver()->SetCheckpointFrame(frames[2].frame_id);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
|
|
// Simulate something going wrong in the Receiver, and have it report picture
|
|
// loss to the Sender. The Sender should then propagate this to its Observer
|
|
// and return true when NeedsKeyFrame() is called.
|
|
EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(1);
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
receiver()->SetPictureLossIndicator(true);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_TRUE(sender()->NeedsKeyFrame());
|
|
|
|
// Send a non-key frame, and expect NeedsKeyFrame() still returns true. The
|
|
// Observer is not re-notified. This accounts for the case where a client's
|
|
// media encoder had frames in its processing pipeline before NeedsKeyFrame()
|
|
// began returning true.
|
|
EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
EncodedFrameWithBuffer& nonkey_frame = frames[3];
|
|
PopulateFrameWithDefaults(FrameId::first() + 3,
|
|
FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */,
|
|
&nonkey_frame);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(nonkey_frame));
|
|
SimulateExecution(kFrameDuration);
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_TRUE(sender()->NeedsKeyFrame());
|
|
|
|
// Now send a key frame, and expect NeedsKeyFrame() returns false. Note that
|
|
// the Receiver hasn't cleared the PLI condition, but the Sender knows more
|
|
// key frames won't be needed.
|
|
EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
EncodedFrameWithBuffer& recovery_frame = frames[4];
|
|
PopulateFrameWithDefaults(FrameId::first() + 4,
|
|
FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */,
|
|
&recovery_frame);
|
|
recovery_frame.dependency = EncodedFrame::KEY_FRAME;
|
|
recovery_frame.referenced_frame_id = recovery_frame.frame_id;
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(recovery_frame));
|
|
SimulateExecution(kFrameDuration);
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
|
|
// Let's say the Receiver hasn't received the key frame yet, and it reports
|
|
// its picture loss again to the Sender. Observer::OnPictureLost() should not
|
|
// be called, and NeedsKeyFrame() should NOT return true, because the Sender
|
|
// knows the Receiver hasn't acknowledged the key frame (just sent) yet.
|
|
EXPECT_CALL(observer, OnFrameCanceled(nonkey_frame.frame_id)).Times(1);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
receiver()->SetCheckpointFrame(nonkey_frame.frame_id);
|
|
receiver()->SetPictureLossIndicator(true);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
|
|
// Now, simulate the Receiver getting the key frame, but NOT recovering. This
|
|
// should cause Observer::OnPictureLost() to be called, and cause
|
|
// NeedsKeyFrame() to return true again.
|
|
EXPECT_CALL(observer, OnFrameCanceled(recovery_frame.frame_id)).Times(1);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(1);
|
|
receiver()->SetCheckpointFrame(recovery_frame.frame_id);
|
|
receiver()->SetPictureLossIndicator(true);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_TRUE(sender()->NeedsKeyFrame());
|
|
|
|
// Send another key frame, and expect NeedsKeyFrame() returns false.
|
|
EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
EncodedFrameWithBuffer& another_recovery_frame = frames[5];
|
|
PopulateFrameWithDefaults(FrameId::first() + 5,
|
|
FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */,
|
|
&another_recovery_frame);
|
|
another_recovery_frame.dependency = EncodedFrame::KEY_FRAME;
|
|
another_recovery_frame.referenced_frame_id = another_recovery_frame.frame_id;
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(another_recovery_frame));
|
|
SimulateExecution(kFrameDuration);
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
|
|
// Now, simulate the Receiver recovering. It will report this to the Sender,
|
|
// and NeedsKeyFrame() will still return false.
|
|
EXPECT_CALL(observer, OnFrameCanceled(another_recovery_frame.frame_id))
|
|
.Times(1);
|
|
EXPECT_CALL(observer, OnPictureLost()).Times(0);
|
|
receiver()->SetCheckpointFrame(another_recovery_frame.frame_id);
|
|
receiver()->SetPictureLossIndicator(false);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
Mock::VerifyAndClearExpectations(&observer);
|
|
EXPECT_FALSE(sender()->NeedsKeyFrame());
|
|
|
|
ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames());
|
|
}
|
|
|
|
// Tests that the Receiver should get a Sender Report just before the first RTP
|
|
// packet, and at regular intervals thereafter. The Sender Report contains the
|
|
// lip-sync information necessary for play-out timing.
|
|
TEST_F(SenderTest, ProvidesSenderReports) {
|
|
std::vector<SenderReportParser::SenderReportWithId> sender_reports;
|
|
Sequence packet_sequence;
|
|
EXPECT_CALL(*receiver(), OnSenderReport(_))
|
|
.InSequence(packet_sequence)
|
|
.WillOnce(
|
|
Invoke([&](const SenderReportParser::SenderReportWithId& report) {
|
|
sender_reports.push_back(report);
|
|
}))
|
|
.RetiresOnSaturation();
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(1).InSequence(packet_sequence);
|
|
EXPECT_CALL(*receiver(), OnSenderReport(_))
|
|
.Times(3)
|
|
.InSequence(packet_sequence)
|
|
.WillRepeatedly(
|
|
Invoke([&](const SenderReportParser::SenderReportWithId& report) {
|
|
sender_reports.push_back(report);
|
|
}));
|
|
|
|
EncodedFrameWithBuffer frame;
|
|
constexpr int kFrameDataSize = 250;
|
|
PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0,
|
|
kFrameDataSize, &frame);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame));
|
|
SimulateExecution(); // Should send one Sender Report + one RTP packet.
|
|
EXPECT_EQ(size_t{1}, sender_reports.size());
|
|
|
|
// Have the Receiver ACK the frame to prevent retransmitting the RTP packet.
|
|
receiver()->SetCheckpointFrame(FrameId::first());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
|
|
// Advance through three more reporting intervals. One Sender Report should be
|
|
// sent each interval, making a total of 4 reports sent.
|
|
constexpr auto kThreeReportIntervals = 3 * kRtcpReportInterval;
|
|
SimulateExecution(kThreeReportIntervals); // Three more Sender Reports.
|
|
ASSERT_EQ(size_t{4}, sender_reports.size());
|
|
|
|
// The first report should contain the same timestamps as the frame because
|
|
// the Clock did not advance. Also, its packet count and octet count fields
|
|
// should be zero since the report was sent before the RTP packet.
|
|
EXPECT_EQ(frame.reference_time, sender_reports.front().reference_time);
|
|
EXPECT_EQ(frame.rtp_timestamp, sender_reports.front().rtp_timestamp);
|
|
EXPECT_EQ(uint32_t{0}, sender_reports.front().send_packet_count);
|
|
EXPECT_EQ(uint32_t{0}, sender_reports.front().send_octet_count);
|
|
|
|
// The last report should contain the timestamps extrapolated into the future
|
|
// because the Clock did move forward. Also, the packet count and octet fields
|
|
// should now be non-zero because the report was sent after the RTP packet.
|
|
EXPECT_EQ(frame.reference_time + kThreeReportIntervals,
|
|
sender_reports.back().reference_time);
|
|
EXPECT_EQ(frame.rtp_timestamp +
|
|
RtpTimeDelta::FromDuration(kThreeReportIntervals, kRtpTimebase),
|
|
sender_reports.back().rtp_timestamp);
|
|
EXPECT_EQ(uint32_t{1}, sender_reports.back().send_packet_count);
|
|
EXPECT_EQ(uint32_t{kFrameDataSize}, sender_reports.back().send_octet_count);
|
|
}
|
|
|
|
// Tests that the Sender provides Kickstart packets whenever the Receiver may
|
|
// not know about new frames.
|
|
TEST_F(SenderTest, ProvidesKickstartPacketsIfReceiverDoesNotACK) {
|
|
// Have the Receiver move the checkpoint forward only for the first frame, and
|
|
// none of the later frames. This will force the Sender to eventually send a
|
|
// Kickstart packet.
|
|
ON_CALL(*receiver(), OnFrameComplete(_))
|
|
.WillByDefault(Invoke([&](FrameId frame_id) {
|
|
if (frame_id == FrameId::first()) {
|
|
receiver()->SetCheckpointFrame(FrameId::first());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}
|
|
}));
|
|
|
|
// Send three frames, paced to the media.
|
|
EncodedFrameWithBuffer frames[3];
|
|
for (int i = 0; i < 3; ++i) {
|
|
PopulateFrameWithDefaults(FrameId::first() + i,
|
|
FakeClock::now() - kCaptureDelay, i,
|
|
48 /* bytes */, &frames[i]);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i]));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
|
|
// Now, do nothing for a while. Because the Receiver isn't moving the
|
|
// checkpoint forward, the Sender will have sent all the RTP packets at least
|
|
// once, and then will start sending just Kickstart packets.
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
|
|
// Keep doing nothing for a while, and confirm the Sender is just sending the
|
|
// same Kickstart packet over and over. The Kickstart packet is supposed to be
|
|
// the last packet of the latest frame.
|
|
std::set<std::pair<FrameId, FramePacketId>> unique_received_packet_ids;
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_))
|
|
.WillRepeatedly(
|
|
Invoke([&](const RtpPacketParser::ParseResult& parsed_packet) {
|
|
unique_received_packet_ids.emplace(parsed_packet.frame_id,
|
|
parsed_packet.packet_id);
|
|
}));
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
Mock::VerifyAndClearExpectations(receiver());
|
|
EXPECT_EQ(size_t{1}, unique_received_packet_ids.size());
|
|
EXPECT_EQ(frames[2].frame_id, unique_received_packet_ids.begin()->first);
|
|
|
|
// Now, simulate the Receiver ACKing all the frames.
|
|
receiver()->SetCheckpointFrame(frames[2].frame_id);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
SimulateExecution(); // RTCP transmitted to Sender.
|
|
|
|
// With all the frames sent, the Sender should not be transmitting anything.
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0);
|
|
SimulateExecution(10 * kTargetPlayoutDelay);
|
|
|
|
ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames());
|
|
}
|
|
|
|
// Tests that the Sender only retransmits packets specifically NACK'ed by the
|
|
// Receiver.
|
|
TEST_F(SenderTest, ResendsIndividuallyNackedPackets) {
|
|
// Populate the frame data in each frame with enough bytes to force at least
|
|
// three RTP packets per frame.
|
|
constexpr int kFrameDataSize = 3 * kMaxRtpPacketSizeForIpv6UdpOnEthernet;
|
|
|
|
// Use a 1ms network delay in each direction to make the sequence of events
|
|
// clearer in this test.
|
|
constexpr milliseconds kOneWayNetworkDelay{1};
|
|
SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay);
|
|
SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay);
|
|
|
|
// Simulate that three specific packets will be dropped by the network, one
|
|
// from each frame (about to be sent).
|
|
const std::vector<PacketNack> dropped_packets{
|
|
{FrameId::first(), FramePacketId{2}},
|
|
{FrameId::first() + 1, FramePacketId{1}},
|
|
{FrameId::first() + 2, FramePacketId{0}},
|
|
};
|
|
receiver()->SetIgnoreList(dropped_packets);
|
|
|
|
// Send three frames, paced to the media. The Receiver won't completely
|
|
// receive any of these frames due to dropped packets.
|
|
EXPECT_CALL(*receiver(), OnFrameComplete(_)).Times(0);
|
|
EncodedFrameWithBuffer frames[3];
|
|
for (int i = 0; i < 3; ++i) {
|
|
PopulateFrameWithDefaults(FrameId::first() + i,
|
|
FakeClock::now() - kCaptureDelay, i,
|
|
kFrameDataSize, &frames[i]);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i]));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
Mock::VerifyAndClearExpectations(receiver());
|
|
EXPECT_EQ(3, sender()->GetInFlightFrameCount());
|
|
|
|
// The Receiver NACKs the three dropped packets...
|
|
receiver()->SetNacksAndAcks(dropped_packets, {});
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
|
|
// In the meantime, the network recovers (i.e., no more dropped packets)...
|
|
receiver()->SetIgnoreList({});
|
|
|
|
// The NACKs reach the Sender, and it acts on them by retransmitting.
|
|
SimulateExecution(kOneWayNetworkDelay);
|
|
|
|
// As each retransmitted packet arrives at the Receiver, advance the
|
|
// checkpoint forward to notify the Sender of frames that are now completely
|
|
// received. Also, confirm that only the three specifically-NACK'ed packets
|
|
// were retransmitted.
|
|
EXPECT_CALL(*receiver(), OnFrameComplete(_))
|
|
.Times(3)
|
|
.WillRepeatedly(InvokeWithoutArgs([&] {
|
|
if (receiver()->AutoAdvanceCheckpoint()) {
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}
|
|
}));
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_))
|
|
.Times(3)
|
|
.WillRepeatedly(Invoke([&](const RtpPacketParser::ParseResult& packet) {
|
|
EXPECT_FALSE(std::find(dropped_packets.begin(), dropped_packets.end(),
|
|
PacketNack{packet.frame_id, packet.packet_id}) ==
|
|
dropped_packets.end());
|
|
}));
|
|
SimulateExecution(kOneWayNetworkDelay);
|
|
Mock::VerifyAndClearExpectations(receiver());
|
|
|
|
// The Receiver checkpoint feedback(s) travel back to the Sender, and there
|
|
// should no longer be any frames in-flight.
|
|
SimulateExecution(kOneWayNetworkDelay);
|
|
EXPECT_EQ(0, sender()->GetInFlightFrameCount());
|
|
|
|
// The Sender should not be transmitting anything from now on since all frames
|
|
// are known to have been completely received.
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0);
|
|
SimulateExecution(10 * kTargetPlayoutDelay);
|
|
|
|
ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames());
|
|
}
|
|
|
|
// Tests that the Sender retransmits an entire frame if the Receiver requests it
|
|
// (i.e., a full frame NACK), but does not retransmit any packets for frames
|
|
// (before or after) that have been acknowledged.
|
|
TEST_F(SenderTest, ResendsMissingFrames) {
|
|
// Populate the frame data in each frame with enough bytes to force at least
|
|
// three RTP packets per frame.
|
|
constexpr int kFrameDataSize = 3 * kMaxRtpPacketSizeForIpv6UdpOnEthernet;
|
|
|
|
// Use a 1ms network delay in each direction to make the sequence of events
|
|
// clearer in this test.
|
|
constexpr milliseconds kOneWayNetworkDelay{1};
|
|
SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay);
|
|
SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay);
|
|
|
|
// Simulate that all of the packets for the second frame will be dropped by
|
|
// the network, but only the packets for that frame.
|
|
const std::vector<PacketNack> dropped_packets{
|
|
{FrameId::first() + 1, kAllPacketsLost},
|
|
};
|
|
receiver()->SetIgnoreList(dropped_packets);
|
|
|
|
NiceMock<MockObserver> observer;
|
|
sender()->SetObserver(&observer);
|
|
|
|
// The expectations below track the story and execute simulated Receiver
|
|
// responses. The Sender will have three frames enqueued by its client, and
|
|
// then...
|
|
//
|
|
// The first frame is received and the Receiver ACKs it by moving the
|
|
// checkpoint forward.
|
|
Sequence completion_sequence;
|
|
EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first()))
|
|
.InSequence(completion_sequence)
|
|
.WillOnce(InvokeWithoutArgs([&] {
|
|
receiver()->SetCheckpointFrame(FrameId::first());
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}));
|
|
// Since all of the packets for the second frame are being dropped, the third
|
|
// frame will finish next. The Receiver responds by NACKing the second frame
|
|
// and ACKing the third frame. The checkpoint does not move forward because
|
|
// the second frame has not been received yet.
|
|
//
|
|
// NETWORK CHANGE: After the third frame is received, stop dropping packets.
|
|
EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first() + 2))
|
|
.InSequence(completion_sequence)
|
|
.WillOnce(InvokeWithoutArgs([&] {
|
|
receiver()->SetNacksAndAcks(dropped_packets,
|
|
std::vector<FrameId>{FrameId::first() + 2});
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
receiver()->SetIgnoreList({});
|
|
}));
|
|
// Finally, the Sender should respond to the whole-frame NACK by re-sending
|
|
// all of the packets for the second frame, and so the Receiver should
|
|
// completely receive the frame.
|
|
EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first() + 1))
|
|
.InSequence(completion_sequence)
|
|
.WillOnce(InvokeWithoutArgs([&] {
|
|
receiver()->SetCheckpointFrame(FrameId::first() + 2);
|
|
receiver()->TransmitRtcpFeedbackPacket();
|
|
}));
|
|
|
|
// From the Sender's perspective, the Receiver will ACK the first frame, then
|
|
// the third frame, then the second frame.
|
|
Sequence cancel_sequence;
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first()))
|
|
.Times(1)
|
|
.InSequence(cancel_sequence);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2))
|
|
.Times(1)
|
|
.InSequence(cancel_sequence);
|
|
EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1))
|
|
.Times(1)
|
|
.InSequence(cancel_sequence);
|
|
|
|
// With all the expectations/sequences in-place, let 'er rip!
|
|
EncodedFrameWithBuffer frames[3];
|
|
for (int i = 0; i < 3; ++i) {
|
|
PopulateFrameWithDefaults(FrameId::first() + i,
|
|
FakeClock::now() - kCaptureDelay, i,
|
|
kFrameDataSize, &frames[i]);
|
|
ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i]));
|
|
SimulateExecution(kFrameDuration);
|
|
}
|
|
SimulateExecution(kTargetPlayoutDelay);
|
|
Mock::VerifyAndClearExpectations(receiver());
|
|
EXPECT_EQ(0, sender()->GetInFlightFrameCount());
|
|
|
|
// The Sender should not be transmitting anything from now on since all frames
|
|
// are known to have been completely received.
|
|
EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0);
|
|
SimulateExecution(10 * kTargetPlayoutDelay);
|
|
|
|
ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cast
|
|
} // namespace openscreen
|