blob: db1ae6430b886a93ffcf4c3555c82da94dd7c7fb [file]
/*
* Copyright 2026 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/rtp_rtcp/source/rtp_packetizer_sframe.h"
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "api/media_types.h"
#include "modules/rtp_rtcp/source/rtp_format.h"
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
#include "modules/rtp_rtcp/source/sframe_descriptor.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
uint8_t MakeDescriptorByte(bool s, bool e, bool t) {
std::bitset<SframeDescriptor::kNumBits> bits;
bits.set(SframeDescriptor::kSBit, s);
bits.set(SframeDescriptor::kEBit, e);
bits.set(SframeDescriptor::kTBit, t);
return static_cast<uint8_t>(bits.to_ulong());
}
RtpPacketToSend MakeSendPacket(std::vector<uint8_t> payload) {
RtpPacketToSend packet(/*extensions=*/nullptr);
packet.SetSequenceNumber(0x4242);
packet.SetTimestamp(0xDEADBEEF);
packet.SetPayloadType(96);
packet.SetPayload(payload);
return packet;
}
TEST(RtpPacketizerSframeTest, SmallFrameFitsInOneMarkedPacket) {
const std::vector<uint8_t> payload = {1, 2, 3, 4};
RtpPacketizerSframe packetizer(payload, RtpPacketizer::PayloadSizeLimits(),
/*is_last_frame_in_picture=*/true);
ASSERT_EQ(packetizer.NumPackets(), 1u);
RtpPacketToSend packet = MakeSendPacket({});
ASSERT_TRUE(packetizer.NextPacket(&packet));
EXPECT_THAT(packet.payload(), ElementsAre(0b110'00000, 1, 2, 3, 4));
EXPECT_TRUE(packet.Marker());
}
TEST(RtpPacketizerSframeTest, ReturnsFalseAfterLastPacket) {
const std::vector<uint8_t> payload = {1, 2, 3, 4};
RtpPacketizerSframe packetizer(payload, RtpPacketizer::PayloadSizeLimits(),
/*is_last_frame_in_picture=*/true);
RtpPacketToSend packet = MakeSendPacket({});
ASSERT_TRUE(packetizer.NextPacket(&packet));
EXPECT_EQ(packetizer.NumPackets(), 0u);
EXPECT_FALSE(packetizer.NextPacket(&packet));
}
// The frame and the descriptor would fit in one 5-byte packet, but not with
// the single packet reduction, so the frame is split into two packets.
TEST(RtpPacketizerSframeTest, HonorsPacketReductionsWithDescriptor) {
const std::vector<uint8_t> payload = {1, 2, 3, 4};
RtpPacketizer::PayloadSizeLimits limits;
limits.max_payload_len = 5;
limits.single_packet_reduction_len = 1;
RtpPacketizerSframe packetizer(payload, limits,
/*is_last_frame_in_picture=*/true);
EXPECT_EQ(packetizer.NumPackets(), 2u);
}
// The limits cover the descriptor, so each packet carries 3 bytes of the frame.
TEST(RtpPacketizerSframeTest, FragmentsFrameAndPrependsDescriptorToEachPacket) {
const std::vector<uint8_t> payload = {0, 1, 2, 3, 4, 5};
RtpPacketizer::PayloadSizeLimits limits;
limits.max_payload_len = 4;
RtpPacketizerSframe packetizer(payload, limits,
/*is_last_frame_in_picture=*/true);
ASSERT_EQ(packetizer.NumPackets(), 2u);
RtpPacketToSend first = MakeSendPacket({});
ASSERT_TRUE(packetizer.NextPacket(&first));
EXPECT_THAT(first.payload(), ElementsAre(0b100'00000, 0, 1, 2));
EXPECT_FALSE(first.Marker());
RtpPacketToSend last = MakeSendPacket({});
ASSERT_TRUE(packetizer.NextPacket(&last));
EXPECT_THAT(last.payload(), ElementsAre(0b010'00000, 3, 4, 5));
EXPECT_TRUE(last.Marker());
}
// With spatial scalability only the last frame of a picture ends with a marked
// packet.
TEST(RtpPacketizerSframeTest, DoesNotMarkFrameThatIsNotLastInPicture) {
const std::vector<uint8_t> payload = {1, 2, 3, 4};
RtpPacketizerSframe packetizer(payload, RtpPacketizer::PayloadSizeLimits(),
/*is_last_frame_in_picture=*/false);
RtpPacketToSend packet = MakeSendPacket({});
ASSERT_TRUE(packetizer.NextPacket(&packet));
EXPECT_FALSE(packet.Marker());
}
TEST(RtpPacketizerSframeTest, RejectsLimitsWithRoomOnlyForDescriptor) {
const std::vector<uint8_t> payload = {0x12};
RtpPacketizer::PayloadSizeLimits limits;
limits.max_payload_len = SframeDescriptor::kSize;
RtpPacketizerSframe packetizer(payload, limits,
/*is_last_frame_in_picture=*/true);
EXPECT_EQ(packetizer.NumPackets(), 0u);
RtpPacketToSend packet = MakeSendPacket({});
EXPECT_FALSE(packetizer.NextPacket(&packet));
}
TEST(PacketizeSframeRtpPacketOrErrorTest, PrependsDescriptorByteToPayload) {
const std::vector<uint8_t> payload = {0x01, 0x02, 0x03, 0x04, 0x05};
RtpPacketToSend packet = MakeSendPacket(payload);
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
std::vector<uint8_t> expected = payload;
expected.insert(expected.begin(),
MakeDescriptorByte(/*s=*/true, /*e=*/true, /*t=*/false));
EXPECT_THAT(packet.payload(), ElementsAreArray(expected));
}
TEST(PacketizeSframeRtpPacketOrErrorTest, EncodesTBitAsPacketEncryptionLevel) {
RtpPacketToSend packet = MakeSendPacket({0xAA});
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kPacket)
.ok());
EXPECT_EQ(packet.payload()[0],
MakeDescriptorByte(/*s=*/true, /*e=*/true, /*t=*/true));
}
TEST(PacketizeSframeRtpPacketOrErrorTest, AcceptsEmptyPayload) {
RtpPacketToSend packet = MakeSendPacket({});
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_THAT(packet.payload(),
ElementsAreArray(std::vector<uint8_t>{MakeDescriptorByte(
/*s=*/true, /*e=*/true, /*t=*/false)}));
}
TEST(PacketizeSframeRtpPacketOrErrorTest, PreservesRtpHeaderFields) {
RtpPacketToSend packet = MakeSendPacket({0xAB, 0xCD});
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(false);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.SequenceNumber(), 0x4242);
EXPECT_EQ(packet.Timestamp(), 0xDEADBEEFu);
EXPECT_EQ(packet.PayloadType(), 96);
}
TEST(PacketizeSframeRtpPacketOrErrorTest, PreservesMarkerBit) {
for (bool marker : {false, true}) {
SCOPED_TRACE(marker);
RtpPacketToSend packet = MakeSendPacket({0xAA});
packet.SetMarker(marker);
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.Marker(), marker);
}
}
TEST(PacketizeSframeRtpPacketOrErrorTest, PreservesSsrc) {
RtpPacketToSend packet = MakeSendPacket({0xAA});
packet.SetSsrc(0xCAFEBABE);
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.Ssrc(), 0xCAFEBABEu);
}
TEST(PacketizeSframeRtpPacketOrErrorTest, ShrinksPaddingByOneWhenPresent) {
const std::vector<uint8_t> payload = {0xAB, 0xCD};
RtpPacketToSend packet = MakeSendPacket(payload);
ASSERT_TRUE(packet.SetPadding(4));
const size_t size_before = packet.size();
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.padding_size(), 3u);
EXPECT_EQ(packet.size(), size_before);
std::vector<uint8_t> expected = payload;
expected.insert(expected.begin(),
MakeDescriptorByte(/*s=*/true, /*e=*/true, /*t=*/false));
EXPECT_THAT(packet.payload(), ElementsAreArray(expected));
}
TEST(PacketizeSframeRtpPacketOrErrorTest, ClearsPaddingWhenOnlyOneBytePresent) {
RtpPacketToSend packet = MakeSendPacket({0xAB});
ASSERT_TRUE(packet.SetPadding(1));
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.padding_size(), 0u);
EXPECT_FALSE(packet.has_padding());
}
TEST(PacketizeSframeRtpPacketOrErrorTest, FailsWhenNoRoomForDescriptor) {
RtpPacketToSend packet(/*extensions=*/nullptr, /*capacity=*/12 + 1);
packet.SetSequenceNumber(1);
packet.SetTimestamp(2);
packet.SetPayloadType(96);
packet.SetPayload(std::vector<uint8_t>{0x01});
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
EXPECT_FALSE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
}
TEST(PacketizeSframeRtpPacketOrErrorTest,
SucceedsForPaddedPacketAtCapacityByShrinkingPadding) {
RtpPacketToSend packet(/*extensions=*/nullptr, /*capacity=*/12 + 2 + 4);
packet.SetSequenceNumber(1);
packet.SetTimestamp(2);
packet.SetPayloadType(96);
packet.SetPayload(std::vector<uint8_t>{0x01, 0x02});
ASSERT_TRUE(packet.SetPadding(4));
packet.set_first_packet_of_frame(true);
packet.set_last_packet_of_frame(true);
EXPECT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::VIDEO, &packet,
SframeEncryptionLevel::kFrame)
.ok());
EXPECT_EQ(packet.padding_size(), 3u);
EXPECT_EQ(packet.size(), 12u + 2u + 4u);
}
TEST(PacketizeSframeRtpPacketOrErrorTest, AudioForcesStartAndEndBits) {
RtpPacketToSend packet = MakeSendPacket({0x55});
packet.set_first_packet_of_frame(false);
packet.set_last_packet_of_frame(false);
ASSERT_TRUE(PacketizeSframeRtpPacketOrError(MediaType::AUDIO, &packet,
SframeEncryptionLevel::kPacket)
.ok());
EXPECT_EQ(packet.payload()[0],
MakeDescriptorByte(/*s=*/true, /*e=*/true, /*t=*/true));
}
} // namespace
} // namespace webrtc