210 lines
8.3 KiB
C++
210 lines
8.3 KiB
C++
// Copyright 2019 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "cast/streaming/rtp_packetizer.h"
|
|
|
|
#include <chrono>
|
|
#include <memory>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "cast/streaming/frame_crypto.h"
|
|
#include "cast/streaming/rtp_defines.h"
|
|
#include "cast/streaming/rtp_packet_parser.h"
|
|
#include "cast/streaming/ssrc.h"
|
|
#include "gtest/gtest.h"
|
|
#include "util/chrono_helpers.h"
|
|
#include "util/crypto/random_bytes.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
namespace {
|
|
|
|
constexpr RtpPayloadType kPayloadType = RtpPayloadType::kAudioOpus;
|
|
|
|
// Returns true if |needle| is fully within |haystack|.
|
|
bool IsSubspan(absl::Span<const uint8_t> needle,
|
|
absl::Span<const uint8_t> haystack) {
|
|
return (needle.data() >= haystack.data()) &&
|
|
((needle.data() + needle.size()) <=
|
|
(haystack.data() + haystack.size()));
|
|
}
|
|
|
|
class RtpPacketizerTest : public testing::Test {
|
|
public:
|
|
RtpPacketizerTest() = default;
|
|
~RtpPacketizerTest() = default;
|
|
|
|
RtpPacketizer* packetizer() { return &packetizer_; }
|
|
|
|
EncryptedFrame CreateFrame(FrameId frame_id,
|
|
bool is_key_frame,
|
|
milliseconds new_playout_delay,
|
|
int payload_size) const {
|
|
EncodedFrame frame;
|
|
frame.dependency = is_key_frame ? EncodedFrame::KEY_FRAME
|
|
: EncodedFrame::DEPENDS_ON_ANOTHER;
|
|
frame.frame_id = frame_id;
|
|
frame.referenced_frame_id = is_key_frame ? frame_id : (frame_id - 1);
|
|
frame.rtp_timestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(987);
|
|
frame.reference_time = Clock::now();
|
|
frame.new_playout_delay = new_playout_delay;
|
|
|
|
std::unique_ptr<uint8_t[]> buffer(new uint8_t[payload_size]);
|
|
for (int i = 0; i < payload_size; ++i) {
|
|
buffer[i] = static_cast<uint8_t>(i);
|
|
}
|
|
frame.data = absl::Span<uint8_t>(buffer.get(), payload_size);
|
|
|
|
return crypto_.Encrypt(frame);
|
|
}
|
|
|
|
// Generates one of the frame's packets, then parses it and checks for the
|
|
// expected values. Thus, this test assumes PacketParser is already working
|
|
// (i.e., all RtpPacketParser unit tests are passing).
|
|
void TestGeneratePacket(const EncryptedFrame& frame,
|
|
FramePacketId packet_id) {
|
|
SCOPED_TRACE(testing::Message() << "packet_id=" << packet_id);
|
|
|
|
const int frame_payload_size = frame.data.size();
|
|
constexpr int kExpectedRtpHeaderSize = 23;
|
|
const int packet_payload_size =
|
|
kMaxRtpPacketSizeForIpv4UdpOnEthernet - kExpectedRtpHeaderSize;
|
|
const int final_packet_payload_size =
|
|
frame_payload_size % packet_payload_size;
|
|
const int num_packets = 1 + frame_payload_size / packet_payload_size;
|
|
|
|
// Generate a RTP packet and parse it.
|
|
uint8_t scratch[kMaxRtpPacketSizeForIpv4UdpOnEthernet];
|
|
memset(scratch, 0, sizeof(scratch));
|
|
const auto packet = packetizer_.GeneratePacket(frame, packet_id, scratch);
|
|
ASSERT_TRUE(IsSubspan(packet, scratch));
|
|
|
|
const auto result = parser_.Parse(packet);
|
|
ASSERT_TRUE(result);
|
|
|
|
// Check that RTP header fields match expected values.
|
|
EXPECT_EQ(kPayloadType, result->payload_type);
|
|
EXPECT_EQ(frame.rtp_timestamp, result->rtp_timestamp);
|
|
EXPECT_EQ(frame.dependency == EncodedFrame::KEY_FRAME,
|
|
result->is_key_frame);
|
|
EXPECT_EQ(frame.frame_id, result->frame_id);
|
|
EXPECT_EQ(packet_id, result->packet_id);
|
|
EXPECT_EQ(static_cast<FramePacketId>(num_packets - 1),
|
|
result->max_packet_id);
|
|
EXPECT_EQ(frame.referenced_frame_id, result->referenced_frame_id);
|
|
|
|
// The sequence number field MUST be different for each packet, regardless
|
|
// of whether the exact same packet is being re-generated.
|
|
if (last_sequence_number_) {
|
|
EXPECT_EQ(static_cast<uint16_t>(*last_sequence_number_ + 1),
|
|
result->sequence_number);
|
|
}
|
|
last_sequence_number_ = result->sequence_number;
|
|
|
|
// If there is a playout delay change starting with this |frame|, it must
|
|
// only be mentioned in the first packet.
|
|
if (packet_id == FramePacketId{0}) {
|
|
EXPECT_EQ(frame.new_playout_delay, result->new_playout_delay);
|
|
} else {
|
|
EXPECT_EQ(milliseconds(0), result->new_playout_delay);
|
|
}
|
|
|
|
// Check that the RTP payload is correct for this packet.
|
|
ASSERT_TRUE(IsSubspan(result->payload, packet));
|
|
// Last packet is smaller, as its payload is just the remaining bytes.
|
|
const int expected_payload_size = (int{packet_id} == (num_packets - 1))
|
|
? final_packet_payload_size
|
|
: packet_payload_size;
|
|
EXPECT_EQ(expected_payload_size, static_cast<int>(result->payload.size()));
|
|
const absl::Span<const uint8_t> expected_bytes(
|
|
frame.data.data() + (packet_id * packet_payload_size),
|
|
expected_payload_size);
|
|
EXPECT_EQ(expected_bytes, result->payload);
|
|
}
|
|
|
|
private:
|
|
// The RtpPacketizer instance under test, plus some surrounding dependencies
|
|
// to generate its input and examine its output.
|
|
const Ssrc ssrc_{GenerateSsrc(true)};
|
|
const FrameCrypto crypto_{GenerateRandomBytes16(), GenerateRandomBytes16()};
|
|
RtpPacketizer packetizer_{kPayloadType, ssrc_,
|
|
kMaxRtpPacketSizeForIpv4UdpOnEthernet};
|
|
RtpPacketParser parser_{ssrc_};
|
|
|
|
// absl::nullopt until the random starting sequence number, from the first
|
|
// packet generated by TestGeneratePacket(), is known.
|
|
absl::optional<uint16_t> last_sequence_number_;
|
|
};
|
|
|
|
// Tests that all packets are generated for one key frame, followed by 9 "delta"
|
|
// frames. The key frame is larger than the other frames, as is typical in a
|
|
// real-world usage scenario.
|
|
TEST_F(RtpPacketizerTest, GeneratesPacketsForSequenceOfFrames) {
|
|
for (int i = 0; i < 10; ++i) {
|
|
const bool is_key_frame = (i == 0);
|
|
const int frame_payload_size = is_key_frame ? 48269 : 10000;
|
|
const EncryptedFrame frame =
|
|
CreateFrame(FrameId::first() + i, is_key_frame, milliseconds(0),
|
|
frame_payload_size);
|
|
SCOPED_TRACE(testing::Message() << "frame_id=" << frame.frame_id);
|
|
const int num_packets = packetizer()->ComputeNumberOfPackets(frame);
|
|
ASSERT_EQ(is_key_frame ? 34 : 7, num_packets);
|
|
|
|
for (int j = 0; j < num_packets; ++j) {
|
|
TestGeneratePacket(frame, static_cast<FramePacketId>(j));
|
|
if (testing::Test::HasFailure()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that all packets are generated for a key frame that includes a playout
|
|
// delay change. Only the first packet should mention the playout delay change.
|
|
TEST_F(RtpPacketizerTest, GeneratesPacketsForFrameWithLatencyChange) {
|
|
const int frame_payload_size = 38383;
|
|
const EncryptedFrame frame = CreateFrame(
|
|
FrameId::first() + 42, true, milliseconds(543), frame_payload_size);
|
|
const int num_packets = packetizer()->ComputeNumberOfPackets(frame);
|
|
ASSERT_EQ(27, num_packets);
|
|
|
|
for (int i = 0; i < num_packets; ++i) {
|
|
TestGeneratePacket(frame, static_cast<FramePacketId>(i));
|
|
if (testing::Test::HasFailure()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that a single, valid RTP packet is generated for a frame with no data
|
|
// payload. Having no payload is valid with some codecs (e.g., complete audio
|
|
// silence can be represented by an empty payload).
|
|
TEST_F(RtpPacketizerTest, GeneratesOnePacketForFrameWithNoPayload) {
|
|
const int frame_payload_size = 0;
|
|
const EncryptedFrame frame = CreateFrame(FrameId::first() + 99, false,
|
|
milliseconds(0), frame_payload_size);
|
|
ASSERT_EQ(1, packetizer()->ComputeNumberOfPackets(frame));
|
|
TestGeneratePacket(frame, FramePacketId{0});
|
|
}
|
|
|
|
// Tests that re-generating the same packet for re-transmission works, including
|
|
// a different sequence counter value in the packet each time.
|
|
TEST_F(RtpPacketizerTest, GeneratesPacketForRetransmission) {
|
|
const int frame_payload_size = 16384;
|
|
const EncryptedFrame frame =
|
|
CreateFrame(FrameId::first(), true, milliseconds(0), frame_payload_size);
|
|
const int num_packets = packetizer()->ComputeNumberOfPackets(frame);
|
|
ASSERT_EQ(12, num_packets);
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
// Keep generating the same packet. TestGeneratePacket() will check that a
|
|
// different sequence number is used each time.
|
|
TestGeneratePacket(frame, FramePacketId{3});
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cast
|
|
} // namespace openscreen
|