srs/trunk/src/utest/srs_utest_ai10.cpp
Haibo Chen(陈海博) ef048b0d65
RTC: Fix DVR missing first 4-6 seconds by initializing rate from SDP (#4541)
for issue #4418, #4151, #4076 .DVR Missing First Few Seconds of
Audio/Video

### Root Cause
When recording WebRTC streams to FLV files using DVR, the first 4-6
seconds of audio/video are missing. This occurs because:

1. **Packets are discarded before A/V sync is available**: The
RTC-to-RTMP conversion pipeline actively discards all RTP packets when
avsync_time <= 0.
2. **Original algorithm requires 2 RTCP SR packets**: The previous
implementation needed to receive two RTCP Sender Report (SR) packets
before it could calculate the rate for audio/video synchronization
timestamp conversion.
3. **Delay causes packet loss**: Since RTCP SR packets typically arrive
every 2-3 seconds, waiting for 2 SRs means 4-6 seconds of packets are
discarded before A/V sync becomes available.
4. **Audio SR arrives slower than video SR**: As reported in the issue,
video RTCP SR packets arrive much faster than audio SR packets. This
asymmetry causes audio packets to be discarded for a longer period,
resulting in the audio loss observed in DVR recordings.

### Solution
1. **Initialize rate from SDP**: Use the sample rate from SDP (Session
Description Protocol) to calculate the initial rate immediately when the
track is created.
Audio (Opus): 48000 Hz → rate = 48 (RTP units per millisecond)
Video (H.264/H.265): 90000 Hz → rate = 90 (RTP units per millisecond)
2. **Enable immediate A/V sync:** With the SDP rate available,
cal_avsync_time() can calculate valid timestamps from the very first RTP
packet, eliminating packet loss.
3. **Smooth transition to precise rate**: After receiving the 2nd RTCP
SR, update to the precisely calculated rate based on actual RTP/NTP
timestamp mapping.

## Configuration

Added new configuration option `init_rate_from_sdp` in the RTC vhost
section:

```nginx
vhost rtc.vhost.srs.com {
    rtc {
        # Whether initialize RTP rate from SDP sample rate for immediate A/V sync.
        # When enabled, the RTP rate (units per millisecond) is initialized from the SDP
        # sample rate (e.g., 90 for video 90kHz, 48 for audio 48kHz) before receiving
        # 2 RTCP SR packets. This allows immediate audio/video synchronization.
        # The rate will be updated to a more precise value after receiving the 2nd SR.
        # Overwrite by env SRS_VHOST_RTC_INIT_RATE_FROM_SDP for all vhosts.
        # Default: off
        init_rate_from_sdp off;
    }
}
```

**⚠️ Important Note**: This config defaults to **off** because:
-  When **enabled**: Fixes the audio loss problem (no missing first 4-6
seconds)
-  When **enabled**: VLC on macOS cannot play the video properly
-  Other platforms work fine (Windows, Linux)
-  FFplay works fine on all platforms

Users experiencing audio loss in DVR recordings can enable this option
if they don't need VLC macOS compatibility. We're investigating the VLC
macOS issue to make this feature safe to enable by default in the
future.

---------

Co-authored-by: winlin <winlinvip@gmail.com>
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
2025-10-28 09:33:40 -04:00

3183 lines
126 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_ai10.hpp>
using namespace std;
#include <srs_app_rtc_codec.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_core_autofree.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_packet.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#include <srs_protocol_format.hpp>
#include <srs_protocol_rtmp_stack.hpp>
#include <srs_utest_ai11.hpp>
// Helper functions
SrsRtpPacket *create_test_rtp_packet(uint16_t seq, uint32_t ts, uint32_t ssrc, bool marker)
{
SrsRtpPacket *pkt = new SrsRtpPacket();
pkt->header_.set_sequence(seq);
pkt->header_.set_timestamp(ts);
pkt->header_.set_ssrc(ssrc);
pkt->header_.set_marker(marker);
pkt->header_.set_payload_type(96);
// Add some dummy payload using SrsRtpRawPayload
SrsRtpRawPayload *raw_payload = new SrsRtpRawPayload();
char *payload_data = new char[64];
memset(payload_data, 0x42, 64);
raw_payload->payload_ = payload_data;
raw_payload->nn_payload_ = 64;
pkt->set_payload(raw_payload, SrsRtpPacketPayloadTypeRaw);
return pkt;
}
SrsRtcTrackDescription *create_test_track_description(std::string type, uint32_t ssrc)
{
SrsRtcTrackDescription *desc = new SrsRtcTrackDescription();
desc->type_ = type;
desc->ssrc_ = ssrc;
desc->id_ = "test-track-" + type;
desc->is_active_ = true;
desc->direction_ = "sendrecv";
desc->mid_ = "0";
return desc;
}
SrsCodecPayload *create_test_codec_payload(uint8_t pt, std::string name, int sample)
{
SrsCodecPayload *payload = new SrsCodecPayload(pt, name, sample);
return payload;
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with basic cases
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeBasic)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test with NULL packet
int null_size = builder.calculate_packet_payload_size(NULL);
EXPECT_EQ(0, null_size);
// Test with packet that has no payload
SrsUniquePtr<SrsRtpPacket> empty_pkt(new SrsRtpPacket());
int empty_size = builder.calculate_packet_payload_size(empty_pkt.get());
EXPECT_EQ(0, empty_size);
// Test with raw payload packet
SrsUniquePtr<SrsRtpPacket> raw_pkt(create_test_rtp_packet(100, 1000, 12345));
int raw_size = builder.calculate_packet_payload_size(raw_pkt.get());
EXPECT_EQ(68, raw_size); // 4 bytes length prefix + 64 bytes payload
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with H.264 FU-A payload
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeFUA)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.264 FU-A payload (SrsRtpFUAPayload2) - start fragment
SrsUniquePtr<SrsRtpPacket> fua_start_pkt(new SrsRtpPacket());
SrsRtpFUAPayload2 *fua_start = new SrsRtpFUAPayload2();
fua_start->start_ = true;
fua_start->end_ = false;
fua_start->nalu_type_ = SrsAvcNaluTypeIDR;
fua_start->size_ = 100; // 100 bytes of payload data
char fua_data[100];
memset(fua_data, 0x42, sizeof(fua_data));
fua_start->payload_ = fua_data;
fua_start_pkt->set_payload(fua_start, SrsRtpPacketPayloadTypeFUA2);
int fua_start_size = builder.calculate_packet_payload_size(fua_start_pkt.get());
EXPECT_EQ(105, fua_start_size); // 100 + 1 (NALU header) + 4 (length prefix) = 105
// Test H.264 FU-A payload - middle fragment
SrsUniquePtr<SrsRtpPacket> fua_middle_pkt(new SrsRtpPacket());
SrsRtpFUAPayload2 *fua_middle = new SrsRtpFUAPayload2();
fua_middle->start_ = false;
fua_middle->end_ = false;
fua_middle->nalu_type_ = SrsAvcNaluTypeIDR;
fua_middle->size_ = 80; // 80 bytes of payload data
char fua_middle_data[80];
memset(fua_middle_data, 0x43, sizeof(fua_middle_data));
fua_middle->payload_ = fua_middle_data;
fua_middle_pkt->set_payload(fua_middle, SrsRtpPacketPayloadTypeFUA2);
int fua_middle_size = builder.calculate_packet_payload_size(fua_middle_pkt.get());
EXPECT_EQ(80, fua_middle_size); // Only payload size, no additional headers for middle fragments
// Test H.264 FU-A payload - end fragment
SrsUniquePtr<SrsRtpPacket> fua_end_pkt(new SrsRtpPacket());
SrsRtpFUAPayload2 *fua_end = new SrsRtpFUAPayload2();
fua_end->start_ = false;
fua_end->end_ = true;
fua_end->nalu_type_ = SrsAvcNaluTypeIDR;
fua_end->size_ = 60; // 60 bytes of payload data
char fua_end_data[60];
memset(fua_end_data, 0x44, sizeof(fua_end_data));
fua_end->payload_ = fua_end_data;
fua_end_pkt->set_payload(fua_end, SrsRtpPacketPayloadTypeFUA2);
int fua_end_size = builder.calculate_packet_payload_size(fua_end_pkt.get());
EXPECT_EQ(60, fua_end_size); // Only payload size, no additional headers for end fragments
// Test H.264 FU-A payload with zero size
SrsUniquePtr<SrsRtpPacket> fua_zero_pkt(new SrsRtpPacket());
SrsRtpFUAPayload2 *fua_zero = new SrsRtpFUAPayload2();
fua_zero->start_ = true;
fua_zero->end_ = false;
fua_zero->nalu_type_ = SrsAvcNaluTypeIDR;
fua_zero->size_ = 0; // Zero size payload
fua_zero->payload_ = NULL;
fua_zero_pkt->set_payload(fua_zero, SrsRtpPacketPayloadTypeFUA2);
int fua_zero_size = builder.calculate_packet_payload_size(fua_zero_pkt.get());
EXPECT_EQ(0, fua_zero_size); // Should return 0 for zero-size payload
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with H.264 STAP-A payload
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeSTAP)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.264 STAP-A payload (SrsRtpSTAPPayload) with multiple NALUs
SrsUniquePtr<SrsRtpPacket> stap_pkt(new SrsRtpPacket());
SrsRtpSTAPPayload *stap = new SrsRtpSTAPPayload();
// Add first NALU (SPS)
SrsNaluSample *sps_sample = new SrsNaluSample();
char sps_data[20];
memset(sps_data, 0x67, sizeof(sps_data)); // SPS NALU type
sps_sample->bytes_ = sps_data;
sps_sample->size_ = sizeof(sps_data);
stap->nalus_.push_back(sps_sample);
// Add second NALU (PPS)
SrsNaluSample *pps_sample = new SrsNaluSample();
char pps_data[10];
memset(pps_data, 0x68, sizeof(pps_data)); // PPS NALU type
pps_sample->bytes_ = pps_data;
pps_sample->size_ = sizeof(pps_data);
stap->nalus_.push_back(pps_sample);
// Add third NALU (IDR slice)
SrsNaluSample *idr_sample = new SrsNaluSample();
char idr_data[100];
memset(idr_data, 0x65, sizeof(idr_data)); // IDR NALU type
idr_sample->bytes_ = idr_data;
idr_sample->size_ = sizeof(idr_data);
stap->nalus_.push_back(idr_sample);
stap_pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAP);
int stap_size = builder.calculate_packet_payload_size(stap_pkt.get());
// Expected: (4 + 20) + (4 + 10) + (4 + 100) = 24 + 14 + 104 = 142
EXPECT_EQ(142, stap_size);
// Test STAP-A payload with empty NALUs (should be skipped)
SrsUniquePtr<SrsRtpPacket> stap_empty_pkt(new SrsRtpPacket());
SrsRtpSTAPPayload *stap_empty = new SrsRtpSTAPPayload();
// Add NALU with zero size (should be skipped)
SrsNaluSample *empty_sample = new SrsNaluSample();
empty_sample->bytes_ = NULL;
empty_sample->size_ = 0;
stap_empty->nalus_.push_back(empty_sample);
// Add valid NALU
SrsNaluSample *valid_sample = new SrsNaluSample();
char valid_data[30];
memset(valid_data, 0x41, sizeof(valid_data));
valid_sample->bytes_ = valid_data;
valid_sample->size_ = sizeof(valid_data);
stap_empty->nalus_.push_back(valid_sample);
stap_empty_pkt->set_payload(stap_empty, SrsRtpPacketPayloadTypeSTAP);
int stap_empty_size = builder.calculate_packet_payload_size(stap_empty_pkt.get());
EXPECT_EQ(34, stap_empty_size); // Only valid NALU: 4 + 30 = 34
// Test STAP-A payload with no NALUs
SrsUniquePtr<SrsRtpPacket> stap_no_nalus_pkt(new SrsRtpPacket());
SrsRtpSTAPPayload *stap_no_nalus = new SrsRtpSTAPPayload();
stap_no_nalus_pkt->set_payload(stap_no_nalus, SrsRtpPacketPayloadTypeSTAP);
int stap_no_nalus_size = builder.calculate_packet_payload_size(stap_no_nalus_pkt.get());
EXPECT_EQ(0, stap_no_nalus_size); // No NALUs, should return 0
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with H.265 FU-A payload
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeFUAHevc)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.265 FU-A payload (SrsRtpFUAPayloadHevc2) - start fragment
SrsUniquePtr<SrsRtpPacket> fua_hevc_start_pkt(new SrsRtpPacket());
SrsRtpFUAPayloadHevc2 *fua_hevc_start = new SrsRtpFUAPayloadHevc2();
fua_hevc_start->start_ = true;
fua_hevc_start->end_ = false;
fua_hevc_start->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
fua_hevc_start->size_ = 120; // 120 bytes of payload data
char fua_hevc_data[120];
memset(fua_hevc_data, 0x26, sizeof(fua_hevc_data)); // HEVC IDR slice
fua_hevc_start->payload_ = fua_hevc_data;
fua_hevc_start_pkt->set_payload(fua_hevc_start, SrsRtpPacketPayloadTypeFUAHevc2);
int fua_hevc_start_size = builder.calculate_packet_payload_size(fua_hevc_start_pkt.get());
EXPECT_EQ(126, fua_hevc_start_size); // 120 + 2 (HEVC NALU header) + 4 (length prefix) = 126
// Test H.265 FU-A payload - middle fragment
SrsUniquePtr<SrsRtpPacket> fua_hevc_middle_pkt(new SrsRtpPacket());
SrsRtpFUAPayloadHevc2 *fua_hevc_middle = new SrsRtpFUAPayloadHevc2();
fua_hevc_middle->start_ = false;
fua_hevc_middle->end_ = false;
fua_hevc_middle->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
fua_hevc_middle->size_ = 90; // 90 bytes of payload data
char fua_hevc_middle_data[90];
memset(fua_hevc_middle_data, 0x27, sizeof(fua_hevc_middle_data));
fua_hevc_middle->payload_ = fua_hevc_middle_data;
fua_hevc_middle_pkt->set_payload(fua_hevc_middle, SrsRtpPacketPayloadTypeFUAHevc2);
int fua_hevc_middle_size = builder.calculate_packet_payload_size(fua_hevc_middle_pkt.get());
EXPECT_EQ(90, fua_hevc_middle_size); // Only payload size, no additional headers for middle fragments
// Test H.265 FU-A payload - end fragment
SrsUniquePtr<SrsRtpPacket> fua_hevc_end_pkt(new SrsRtpPacket());
SrsRtpFUAPayloadHevc2 *fua_hevc_end = new SrsRtpFUAPayloadHevc2();
fua_hevc_end->start_ = false;
fua_hevc_end->end_ = true;
fua_hevc_end->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
fua_hevc_end->size_ = 70; // 70 bytes of payload data
char fua_hevc_end_data[70];
memset(fua_hevc_end_data, 0x28, sizeof(fua_hevc_end_data));
fua_hevc_end->payload_ = fua_hevc_end_data;
fua_hevc_end_pkt->set_payload(fua_hevc_end, SrsRtpPacketPayloadTypeFUAHevc2);
int fua_hevc_end_size = builder.calculate_packet_payload_size(fua_hevc_end_pkt.get());
EXPECT_EQ(70, fua_hevc_end_size); // Only payload size, no additional headers for end fragments
// Test H.265 FU-A payload with zero size
SrsUniquePtr<SrsRtpPacket> fua_hevc_zero_pkt(new SrsRtpPacket());
SrsRtpFUAPayloadHevc2 *fua_hevc_zero = new SrsRtpFUAPayloadHevc2();
fua_hevc_zero->start_ = true;
fua_hevc_zero->end_ = false;
fua_hevc_zero->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
fua_hevc_zero->size_ = 0; // Zero size payload
fua_hevc_zero->payload_ = NULL;
fua_hevc_zero_pkt->set_payload(fua_hevc_zero, SrsRtpPacketPayloadTypeFUAHevc2);
int fua_hevc_zero_size = builder.calculate_packet_payload_size(fua_hevc_zero_pkt.get());
EXPECT_EQ(0, fua_hevc_zero_size); // Should return 0 for zero-size payload
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with H.265 STAP payload
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeSTAPHevc)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.265 STAP payload (SrsRtpSTAPPayloadHevc) with multiple NALUs
SrsUniquePtr<SrsRtpPacket> stap_hevc_pkt(new SrsRtpPacket());
SrsRtpSTAPPayloadHevc *stap_hevc = new SrsRtpSTAPPayloadHevc();
// Add first NALU (VPS)
SrsNaluSample *vps_sample = new SrsNaluSample();
char vps_data[25];
memset(vps_data, 0x40, sizeof(vps_data)); // VPS NALU type
vps_sample->bytes_ = vps_data;
vps_sample->size_ = sizeof(vps_data);
stap_hevc->nalus_.push_back(vps_sample);
// Add second NALU (SPS)
SrsNaluSample *sps_sample = new SrsNaluSample();
char sps_data[30];
memset(sps_data, 0x42, sizeof(sps_data)); // SPS NALU type
sps_sample->bytes_ = sps_data;
sps_sample->size_ = sizeof(sps_data);
stap_hevc->nalus_.push_back(sps_sample);
// Add third NALU (PPS)
SrsNaluSample *pps_sample = new SrsNaluSample();
char pps_data[15];
memset(pps_data, 0x44, sizeof(pps_data)); // PPS NALU type
pps_sample->bytes_ = pps_data;
pps_sample->size_ = sizeof(pps_data);
stap_hevc->nalus_.push_back(pps_sample);
// Add fourth NALU (IDR slice)
SrsNaluSample *idr_sample = new SrsNaluSample();
char idr_data[150];
memset(idr_data, 0x26, sizeof(idr_data)); // IDR NALU type
idr_sample->bytes_ = idr_data;
idr_sample->size_ = sizeof(idr_data);
stap_hevc->nalus_.push_back(idr_sample);
stap_hevc_pkt->set_payload(stap_hevc, SrsRtpPacketPayloadTypeSTAPHevc);
int stap_hevc_size = builder.calculate_packet_payload_size(stap_hevc_pkt.get());
// Expected: (4 + 25) + (4 + 30) + (4 + 15) + (4 + 150) = 29 + 34 + 19 + 154 = 236
EXPECT_EQ(236, stap_hevc_size);
// Test HEVC STAP payload with empty NALUs (should be skipped)
SrsUniquePtr<SrsRtpPacket> stap_hevc_empty_pkt(new SrsRtpPacket());
SrsRtpSTAPPayloadHevc *stap_hevc_empty = new SrsRtpSTAPPayloadHevc();
// Add NALU with zero size (should be skipped)
SrsNaluSample *empty_sample = new SrsNaluSample();
empty_sample->bytes_ = NULL;
empty_sample->size_ = 0;
stap_hevc_empty->nalus_.push_back(empty_sample);
// Add valid NALU
SrsNaluSample *valid_sample = new SrsNaluSample();
char valid_data[40];
memset(valid_data, 0x42, sizeof(valid_data));
valid_sample->bytes_ = valid_data;
valid_sample->size_ = sizeof(valid_data);
stap_hevc_empty->nalus_.push_back(valid_sample);
stap_hevc_empty_pkt->set_payload(stap_hevc_empty, SrsRtpPacketPayloadTypeSTAPHevc);
int stap_hevc_empty_size = builder.calculate_packet_payload_size(stap_hevc_empty_pkt.get());
EXPECT_EQ(44, stap_hevc_empty_size); // Only valid NALU: 4 + 40 = 44
// Test HEVC STAP payload with no NALUs
SrsUniquePtr<SrsRtpPacket> stap_hevc_no_nalus_pkt(new SrsRtpPacket());
SrsRtpSTAPPayloadHevc *stap_hevc_no_nalus = new SrsRtpSTAPPayloadHevc();
stap_hevc_no_nalus_pkt->set_payload(stap_hevc_no_nalus, SrsRtpPacketPayloadTypeSTAPHevc);
int stap_hevc_no_nalus_size = builder.calculate_packet_payload_size(stap_hevc_no_nalus_pkt.get());
EXPECT_EQ(0, stap_hevc_no_nalus_size); // No NALUs, should return 0
}
// Test SrsRtcFrameBuilder::calculate_packet_payload_size with edge cases and fallback
VOID TEST(SrsRtcFrameBuilderTest, CalculatePacketPayloadSizeEdgeCases)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test with unknown payload type (should fall through to default case)
SrsUniquePtr<SrsRtpPacket> unknown_pkt(new SrsRtpPacket());
SrsRtpRawPayload *raw_payload = new SrsRtpRawPayload();
char raw_data[50];
memset(raw_data, 0xFF, sizeof(raw_data));
raw_payload->payload_ = raw_data;
raw_payload->nn_payload_ = sizeof(raw_data);
unknown_pkt->set_payload(raw_payload, SrsRtpPacketPayloadTypeRaw);
int unknown_size = builder.calculate_packet_payload_size(unknown_pkt.get());
// For raw payload, it should return the payload size + 4 bytes length prefix
EXPECT_EQ(54, unknown_size); // 50 + 4 = 54
// Test with packet that has payload but payload is NULL
SrsUniquePtr<SrsRtpPacket> null_payload_pkt(new SrsRtpPacket());
// Set payload type but don't actually set a payload object
null_payload_pkt->payload_type_ = SrsRtpPacketPayloadTypeRaw;
int null_payload_size = builder.calculate_packet_payload_size(null_payload_pkt.get());
EXPECT_EQ(0, null_payload_size); // Should return 0 for NULL payload
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with raw payload
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferRaw)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with raw payload
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(1000);
pkt->header_.set_timestamp(100);
// Create a raw payload with actual data
SrsRtpRawPayload *raw_payload = new SrsRtpRawPayload();
char test_data[64];
memset(test_data, 0xAB, sizeof(test_data)); // Fill with test pattern
raw_payload->payload_ = test_data;
raw_payload->nn_payload_ = sizeof(test_data);
pkt->set_payload(raw_payload, SrsRtpPacketPayloadTypeRaw);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// Verify that data was written to buffer (4 bytes length + 64 bytes payload = 68 bytes)
EXPECT_EQ(68, buffer.pos()); // 4 bytes length prefix + 64 bytes payload
EXPECT_EQ(0, nalu_len); // nalu_len is only set for FU-A payloads, not raw payloads
// Test with NULL packet
SrsBuffer null_buffer(buffer_data, sizeof(buffer_data)); // Create fresh buffer
nalu_len = 0;
builder.write_packet_payload_to_buffer(NULL, null_buffer, nalu_len);
EXPECT_EQ(0, null_buffer.pos());
EXPECT_EQ(0, nalu_len);
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.264 FU-A start fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAStart)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.264 FU-A start fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(1000);
pkt->header_.set_timestamp(100);
// Create FU-A payload for start fragment
SrsRtpFUAPayload2 *fua_payload = new SrsRtpFUAPayload2();
fua_payload->start_ = true;
fua_payload->end_ = false;
fua_payload->nri_ = SrsAvcNaluTypeNonIDR;
fua_payload->nalu_type_ = SrsAvcNaluTypeIDR;
char fua_data[50];
memset(fua_data, 0xCD, sizeof(fua_data)); // Fill with test pattern
fua_payload->payload_ = fua_data;
fua_payload->size_ = sizeof(fua_data);
pkt->set_payload(fua_payload, SrsRtpPacketPayloadTypeFUA2);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For FU-A start fragment:
// - nalu_len should be set to payload size + 1 (for NALU header)
// - Buffer should skip 4 bytes initially (for length prefix to be written later)
// - Buffer should write 1 byte NALU header (nri | nalu_type)
// - Buffer should write payload data
EXPECT_EQ(51, nalu_len); // 50 bytes payload + 1 byte NALU header
EXPECT_EQ(4 + 1 + 50, buffer.pos()); // 4 bytes skipped + 1 byte NALU header + 50 bytes payload
// Verify NALU header was written correctly (nri | nalu_type)
uint8_t expected_nalu_header = fua_payload->nri_ | fua_payload->nalu_type_;
EXPECT_EQ(expected_nalu_header, (uint8_t)buffer_data[4]); // NALU header at position 4
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.264 FU-A middle fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAMiddle)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.264 FU-A middle fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(1001);
pkt->header_.set_timestamp(100);
// Create FU-A payload for middle fragment
SrsRtpFUAPayload2 *fua_payload = new SrsRtpFUAPayload2();
fua_payload->start_ = false;
fua_payload->end_ = false;
fua_payload->nri_ = SrsAvcNaluTypeNonIDR;
fua_payload->nalu_type_ = SrsAvcNaluTypeIDR;
char fua_data[30];
memset(fua_data, 0xEF, sizeof(fua_data)); // Fill with test pattern
fua_payload->payload_ = fua_data;
fua_payload->size_ = sizeof(fua_data);
pkt->set_payload(fua_payload, SrsRtpPacketPayloadTypeFUA2);
// Create buffer for writing and simulate previous nalu_len from start fragment
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 51; // Simulate previous nalu_len from start fragment
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For FU-A middle fragment:
// - nalu_len should be incremented by payload size
// - Buffer should only write payload data (no NALU header)
EXPECT_EQ(51 + 30, nalu_len); // Previous 51 + 30 bytes payload
EXPECT_EQ(30, buffer.pos()); // Only 30 bytes payload written
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.264 FU-A end fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAEnd)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.264 FU-A end fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(1002);
pkt->header_.set_timestamp(100);
// Create FU-A payload for end fragment
SrsRtpFUAPayload2 *fua_payload = new SrsRtpFUAPayload2();
fua_payload->start_ = false;
fua_payload->end_ = true;
fua_payload->nri_ = SrsAvcNaluTypeNonIDR;
fua_payload->nalu_type_ = SrsAvcNaluTypeIDR;
char fua_data[20];
memset(fua_data, 0x12, sizeof(fua_data)); // Fill with test pattern
fua_payload->payload_ = fua_data;
fua_payload->size_ = sizeof(fua_data);
pkt->set_payload(fua_payload, SrsRtpPacketPayloadTypeFUA2);
// Create buffer for writing and simulate previous nalu_len from start+middle fragments
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
// Simulate buffer position after start fragment (4 bytes skipped + 1 NALU header + 50 payload)
// and middle fragment (30 bytes payload)
buffer.skip(4 + 1 + 50 + 30); // Position buffer as if previous fragments were written
int nalu_len = 81; // Simulate previous nalu_len: 51 (start) + 30 (middle)
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For FU-A end fragment:
// - nalu_len should be incremented by payload size
// - Buffer should write payload data
// - Buffer should then write nalu_len back to the beginning (skip back, write length, skip forward)
EXPECT_EQ(81 + 20, nalu_len); // Previous 81 + 20 bytes payload = 101
// Buffer position should be back to where it was after writing length and skipping forward
// After writing payload: position = 4 + 1 + 50 + 30 + 20 = 105
// After writing length back: skip(-(4 + nalu_len)) = skip(-105), write 4 bytes, skip(nalu_len) = skip(101)
// Final position should be 4 + 101 = 105
EXPECT_EQ(4 + 101, buffer.pos());
// Verify that the length was written back to the beginning
uint32_t written_length = 0;
memcpy(&written_length, buffer_data, 4);
written_length = ntohl(written_length); // Convert from network byte order
EXPECT_EQ(101, (int)written_length); // Should match final nalu_len
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 FU-A payloads
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAHevc)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.265 FU-A start fragment
SrsUniquePtr<SrsRtpPacket> hevc_pkt(new SrsRtpPacket());
hevc_pkt->header_.set_payload_type(96);
hevc_pkt->header_.set_ssrc(12345);
hevc_pkt->header_.set_sequence(2000);
hevc_pkt->header_.set_timestamp(200);
// Create H.265 FU-A payload for start fragment
SrsRtpFUAPayloadHevc2 *hevc_fua = new SrsRtpFUAPayloadHevc2();
hevc_fua->start_ = true;
hevc_fua->end_ = false;
hevc_fua->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
char hevc_data[40];
memset(hevc_data, 0x34, sizeof(hevc_data)); // Fill with test pattern
hevc_fua->payload_ = hevc_data;
hevc_fua->size_ = sizeof(hevc_data);
hevc_pkt->set_payload(hevc_fua, SrsRtpPacketPayloadTypeFUAHevc2);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(hevc_pkt.get(), buffer, nalu_len);
// H.265 FU-A has different handling than H.264 FU-A for start fragment
EXPECT_EQ(42, nalu_len); // 40 bytes payload + 2 bytes HEVC NALU header
EXPECT_EQ(4 + 2 + 40, buffer.pos()); // 4 bytes skipped + 2 bytes HEVC NALU header + 40 bytes payload
// Verify HEVC NALU header was written correctly
// First byte: nalu_type << 1 (19 << 1 = 38 = 0x26)
EXPECT_EQ((uint8_t)(hevc_fua->nalu_type_ << 1), (uint8_t)buffer_data[4]); // First byte of HEVC NALU header
// Second byte: 0x01
EXPECT_EQ(0x01, (uint8_t)buffer_data[5]); // Second byte of HEVC NALU header
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with FU-A edge cases
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAEdgeCases)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Test H.264 FU-A with zero-size payload
SrsUniquePtr<SrsRtpPacket> zero_pkt(new SrsRtpPacket());
zero_pkt->header_.set_payload_type(96);
zero_pkt->header_.set_ssrc(12345);
zero_pkt->header_.set_sequence(3000);
zero_pkt->header_.set_timestamp(300);
SrsRtpFUAPayload2 *zero_fua = new SrsRtpFUAPayload2();
zero_fua->start_ = true;
zero_fua->end_ = false;
zero_fua->nri_ = SrsAvcNaluTypeNonIDR;
zero_fua->nalu_type_ = SrsAvcNaluTypeIDR;
zero_fua->payload_ = NULL;
zero_fua->size_ = 0; // Zero size payload
zero_pkt->set_payload(zero_fua, SrsRtpPacketPayloadTypeFUA2);
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test with zero-size payload - should not be processed (size_ > 0 check)
builder.write_packet_payload_to_buffer(zero_pkt.get(), buffer, nalu_len);
EXPECT_EQ(0, nalu_len); // Should remain 0 since size_ is 0
EXPECT_EQ(0, buffer.pos()); // Buffer should not be modified
// Test H.265 FU-A with zero-size payload
SrsUniquePtr<SrsRtpPacket> hevc_zero_pkt(new SrsRtpPacket());
hevc_zero_pkt->header_.set_payload_type(96);
hevc_zero_pkt->header_.set_ssrc(12345);
hevc_zero_pkt->header_.set_sequence(3001);
hevc_zero_pkt->header_.set_timestamp(300);
SrsRtpFUAPayloadHevc2 *hevc_zero_fua = new SrsRtpFUAPayloadHevc2();
hevc_zero_fua->start_ = true;
hevc_zero_fua->end_ = false;
hevc_zero_fua->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR;
hevc_zero_fua->payload_ = NULL;
hevc_zero_fua->size_ = 0; // Zero size payload
hevc_zero_pkt->set_payload(hevc_zero_fua, SrsRtpPacketPayloadTypeFUAHevc2);
SrsBuffer hevc_buffer(buffer_data, sizeof(buffer_data));
nalu_len = 0;
// Test with zero-size HEVC payload - should not be processed
builder.write_packet_payload_to_buffer(hevc_zero_pkt.get(), hevc_buffer, nalu_len);
EXPECT_EQ(0, nalu_len); // Should remain 0 since size_ is 0
EXPECT_EQ(0, hevc_buffer.pos()); // Buffer should not be modified
// Test with NULL payload
SrsUniquePtr<SrsRtpPacket> null_payload_pkt(new SrsRtpPacket());
null_payload_pkt->header_.set_payload_type(96);
null_payload_pkt->header_.set_ssrc(12345);
null_payload_pkt->header_.set_sequence(3002);
null_payload_pkt->header_.set_timestamp(300);
// Don't set any payload (payload will be NULL)
SrsBuffer null_buffer(buffer_data, sizeof(buffer_data));
nalu_len = 0;
// Test with NULL payload - should not be processed
builder.write_packet_payload_to_buffer(null_payload_pkt.get(), null_buffer, nalu_len);
EXPECT_EQ(0, nalu_len); // Should remain 0
EXPECT_EQ(0, null_buffer.pos()); // Buffer should not be modified
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer complete FU-A sequence
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUASequence)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test complete FU-A sequence: start -> middle -> end
// 1. Start fragment
SrsUniquePtr<SrsRtpPacket> start_pkt(new SrsRtpPacket());
start_pkt->header_.set_payload_type(96);
start_pkt->header_.set_ssrc(12345);
start_pkt->header_.set_sequence(4000);
start_pkt->header_.set_timestamp(400);
SrsRtpFUAPayload2 *start_fua = new SrsRtpFUAPayload2();
start_fua->start_ = true;
start_fua->end_ = false;
start_fua->nri_ = SrsAvcNaluTypeNonIDR;
start_fua->nalu_type_ = SrsAvcNaluTypeIDR;
char start_data[25];
memset(start_data, 0xAA, sizeof(start_data));
start_fua->payload_ = start_data;
start_fua->size_ = sizeof(start_data);
start_pkt->set_payload(start_fua, SrsRtpPacketPayloadTypeFUA2);
builder.write_packet_payload_to_buffer(start_pkt.get(), buffer, nalu_len);
EXPECT_EQ(26, nalu_len); // 25 + 1 NALU header
EXPECT_EQ(4 + 1 + 25, buffer.pos()); // 4 skipped + 1 NALU header + 25 payload
// 2. Middle fragment
SrsUniquePtr<SrsRtpPacket> middle_pkt(new SrsRtpPacket());
middle_pkt->header_.set_payload_type(96);
middle_pkt->header_.set_ssrc(12345);
middle_pkt->header_.set_sequence(4001);
middle_pkt->header_.set_timestamp(400);
SrsRtpFUAPayload2 *middle_fua = new SrsRtpFUAPayload2();
middle_fua->start_ = false;
middle_fua->end_ = false;
middle_fua->nri_ = SrsAvcNaluTypeNonIDR;
middle_fua->nalu_type_ = SrsAvcNaluTypeIDR;
char middle_data[15];
memset(middle_data, 0xBB, sizeof(middle_data));
middle_fua->payload_ = middle_data;
middle_fua->size_ = sizeof(middle_data);
middle_pkt->set_payload(middle_fua, SrsRtpPacketPayloadTypeFUA2);
builder.write_packet_payload_to_buffer(middle_pkt.get(), buffer, nalu_len);
EXPECT_EQ(26 + 15, nalu_len); // Previous 26 + 15 = 41
EXPECT_EQ(4 + 1 + 25 + 15, buffer.pos()); // Previous position + 15 payload
// 3. End fragment
SrsUniquePtr<SrsRtpPacket> end_pkt(new SrsRtpPacket());
end_pkt->header_.set_payload_type(96);
end_pkt->header_.set_ssrc(12345);
end_pkt->header_.set_sequence(4002);
end_pkt->header_.set_timestamp(400);
SrsRtpFUAPayload2 *end_fua = new SrsRtpFUAPayload2();
end_fua->start_ = false;
end_fua->end_ = true;
end_fua->nri_ = SrsAvcNaluTypeNonIDR;
end_fua->nalu_type_ = SrsAvcNaluTypeIDR;
char end_data[10];
memset(end_data, 0xCC, sizeof(end_data));
end_fua->payload_ = end_data;
end_fua->size_ = sizeof(end_data);
end_pkt->set_payload(end_fua, SrsRtpPacketPayloadTypeFUA2);
builder.write_packet_payload_to_buffer(end_pkt.get(), buffer, nalu_len);
EXPECT_EQ(41 + 10, nalu_len); // Previous 41 + 10 = 51
EXPECT_EQ(4 + 51, buffer.pos()); // Should be positioned after length + complete NALU
// Verify that the length was written back to the beginning
uint32_t written_length = 0;
memcpy(&written_length, buffer_data, 4);
written_length = ntohl(written_length); // Convert from network byte order
EXPECT_EQ(51, (int)written_length); // Should match final nalu_len
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.264 STAP payload
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferSTAP)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.264 STAP payload
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(5000);
pkt->header_.set_timestamp(500);
// Create STAP payload with multiple NALUs
SrsRtpSTAPPayload *stap_payload = new SrsRtpSTAPPayload();
// Add first NALU (SPS)
SrsNaluSample *sps_sample = new SrsNaluSample();
char sps_data[20];
memset(sps_data, 0x67, sizeof(sps_data)); // SPS NALU type
sps_sample->bytes_ = sps_data;
sps_sample->size_ = sizeof(sps_data);
stap_payload->nalus_.push_back(sps_sample);
// Add second NALU (PPS)
SrsNaluSample *pps_sample = new SrsNaluSample();
char pps_data[10];
memset(pps_data, 0x68, sizeof(pps_data)); // PPS NALU type
pps_sample->bytes_ = pps_data;
pps_sample->size_ = sizeof(pps_data);
stap_payload->nalus_.push_back(pps_sample);
// Add third NALU (IDR slice)
SrsNaluSample *idr_sample = new SrsNaluSample();
char idr_data[50];
memset(idr_data, 0x65, sizeof(idr_data)); // IDR NALU type
idr_sample->bytes_ = idr_data;
idr_sample->size_ = sizeof(idr_data);
stap_payload->nalus_.push_back(idr_sample);
pkt->set_payload(stap_payload, SrsRtpPacketPayloadTypeSTAP);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For STAP payload:
// - Each NALU should be written with 4-byte length prefix + NALU data
// - Expected: (4 + 20) + (4 + 10) + (4 + 50) = 24 + 14 + 54 = 92 bytes
EXPECT_EQ(92, buffer.pos());
EXPECT_EQ(0, nalu_len); // nalu_len is not modified for STAP payloads
// Verify the first NALU length was written correctly
uint32_t first_nalu_length = 0;
memcpy(&first_nalu_length, buffer_data, 4);
first_nalu_length = ntohl(first_nalu_length); // Convert from network byte order
EXPECT_EQ(20, (int)first_nalu_length); // Should match SPS NALU size
// Verify the second NALU length was written correctly
uint32_t second_nalu_length = 0;
memcpy(&second_nalu_length, buffer_data + 24, 4); // Skip first NALU (4 + 20 bytes)
second_nalu_length = ntohl(second_nalu_length);
EXPECT_EQ(10, (int)second_nalu_length); // Should match PPS NALU size
// Verify the third NALU length was written correctly
uint32_t third_nalu_length = 0;
memcpy(&third_nalu_length, buffer_data + 38, 4); // Skip first two NALUs (24 + 14 bytes)
third_nalu_length = ntohl(third_nalu_length);
EXPECT_EQ(50, (int)third_nalu_length); // Should match IDR NALU size
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.264 STAP payload containing empty NALUs
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferSTAPWithEmptyNALUs)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.264 STAP payload
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(5001);
pkt->header_.set_timestamp(500);
// Create STAP payload with mixed empty and valid NALUs
SrsRtpSTAPPayload *stap_payload = new SrsRtpSTAPPayload();
// Add empty NALU (should be skipped)
SrsNaluSample *empty_sample1 = new SrsNaluSample();
empty_sample1->bytes_ = NULL;
empty_sample1->size_ = 0;
stap_payload->nalus_.push_back(empty_sample1);
// Add valid NALU
SrsNaluSample *valid_sample = new SrsNaluSample();
char valid_data[30];
memset(valid_data, 0x67, sizeof(valid_data)); // SPS NALU type
valid_sample->bytes_ = valid_data;
valid_sample->size_ = sizeof(valid_data);
stap_payload->nalus_.push_back(valid_sample);
// Add another empty NALU (should be skipped)
SrsNaluSample *empty_sample2 = new SrsNaluSample();
empty_sample2->bytes_ = NULL;
empty_sample2->size_ = 0;
stap_payload->nalus_.push_back(empty_sample2);
// Add another valid NALU
SrsNaluSample *valid_sample2 = new SrsNaluSample();
char valid_data2[15];
memset(valid_data2, 0x68, sizeof(valid_data2)); // PPS NALU type
valid_sample2->bytes_ = valid_data2;
valid_sample2->size_ = sizeof(valid_data2);
stap_payload->nalus_.push_back(valid_sample2);
pkt->set_payload(stap_payload, SrsRtpPacketPayloadTypeSTAP);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For STAP payload with empty NALUs:
// - Empty NALUs (size_ = 0) should be skipped
// - Only valid NALUs should be written: (4 + 30) + (4 + 15) = 34 + 19 = 53 bytes
EXPECT_EQ(53, buffer.pos());
EXPECT_EQ(0, nalu_len); // nalu_len is not modified for STAP payloads
// Verify the first valid NALU length was written correctly
uint32_t first_nalu_length = 0;
memcpy(&first_nalu_length, buffer_data, 4);
first_nalu_length = ntohl(first_nalu_length); // Convert from network byte order
EXPECT_EQ(30, (int)first_nalu_length); // Should match first valid NALU size
// Verify the second valid NALU length was written correctly
uint32_t second_nalu_length = 0;
memcpy(&second_nalu_length, buffer_data + 34, 4); // Skip first NALU (4 + 30 bytes)
second_nalu_length = ntohl(second_nalu_length);
EXPECT_EQ(15, (int)second_nalu_length); // Should match second valid NALU size
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 FU-A start fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAHevcStart)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.265 FU-A start fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(6000);
pkt->header_.set_timestamp(600);
// Create H.265 FU-A payload for start fragment
SrsRtpFUAPayloadHevc2 *fua_hevc = new SrsRtpFUAPayloadHevc2();
fua_hevc->start_ = true;
fua_hevc->end_ = false;
fua_hevc->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR; // IDR slice (19)
char hevc_data[40];
memset(hevc_data, 0x26, sizeof(hevc_data)); // Fill with test pattern
fua_hevc->payload_ = hevc_data;
fua_hevc->size_ = sizeof(hevc_data);
pkt->set_payload(fua_hevc, SrsRtpPacketPayloadTypeFUAHevc2);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For H.265 FU-A start fragment:
// - nalu_len should be set to payload size + 2 (for HEVC NALU header)
// - Buffer should skip 4 bytes initially (for length prefix to be written later)
// - Buffer should write 2 bytes HEVC NALU header (nalu_type << 1, 0x01)
// - Buffer should write payload data
EXPECT_EQ(42, nalu_len); // 40 bytes payload + 2 bytes HEVC NALU header
EXPECT_EQ(4 + 2 + 40, buffer.pos()); // 4 bytes skipped + 2 bytes HEVC NALU header + 40 bytes payload
// Verify HEVC NALU header was written correctly
// First byte: nalu_type << 1 (19 << 1 = 38 = 0x26)
EXPECT_EQ((uint8_t)(fua_hevc->nalu_type_ << 1), (uint8_t)buffer_data[4]); // First byte of HEVC NALU header
// Second byte: 0x01
EXPECT_EQ(0x01, (uint8_t)buffer_data[5]); // Second byte of HEVC NALU header
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 FU-A middle fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAHevcMiddle)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.265 FU-A middle fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(6001);
pkt->header_.set_timestamp(600);
// Create H.265 FU-A payload for middle fragment
SrsRtpFUAPayloadHevc2 *fua_hevc = new SrsRtpFUAPayloadHevc2();
fua_hevc->start_ = false;
fua_hevc->end_ = false;
fua_hevc->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR; // IDR slice (19)
char hevc_data[30];
memset(hevc_data, 0x27, sizeof(hevc_data)); // Fill with test pattern
fua_hevc->payload_ = hevc_data;
fua_hevc->size_ = sizeof(hevc_data);
pkt->set_payload(fua_hevc, SrsRtpPacketPayloadTypeFUAHevc2);
// Create buffer for writing and simulate previous nalu_len from start fragment
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 42; // Simulate previous nalu_len from start fragment
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For H.265 FU-A middle fragment:
// - nalu_len should be incremented by payload size
// - Buffer should only write payload data (no HEVC NALU header)
EXPECT_EQ(42 + 30, nalu_len); // Previous 42 + 30 bytes payload
EXPECT_EQ(30, buffer.pos()); // Only 30 bytes payload written
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 FU-A end fragment
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferFUAHevcEnd)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.265 FU-A end fragment
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(6002);
pkt->header_.set_timestamp(600);
// Create H.265 FU-A payload for end fragment
SrsRtpFUAPayloadHevc2 *fua_hevc = new SrsRtpFUAPayloadHevc2();
fua_hevc->start_ = false;
fua_hevc->end_ = true;
fua_hevc->nalu_type_ = SrsHevcNaluType_CODED_SLICE_IDR; // IDR slice (19)
char hevc_data[20];
memset(hevc_data, 0x28, sizeof(hevc_data)); // Fill with test pattern
fua_hevc->payload_ = hevc_data;
fua_hevc->size_ = sizeof(hevc_data);
pkt->set_payload(fua_hevc, SrsRtpPacketPayloadTypeFUAHevc2);
// Create buffer for writing and simulate previous nalu_len from start+middle fragments
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
// Simulate buffer position after start fragment (4 bytes skipped + 2 HEVC NALU header + 40 payload)
// and middle fragment (30 bytes payload)
buffer.skip(4 + 2 + 40 + 30); // Position buffer as if previous fragments were written
int nalu_len = 72; // Simulate previous nalu_len: 42 (start) + 30 (middle)
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For H.265 FU-A end fragment:
// - nalu_len should be incremented by payload size
// - Buffer should write payload data
// - Buffer should then write nalu_len back to the beginning (skip back, write length, skip forward)
EXPECT_EQ(72 + 20, nalu_len); // Previous 72 + 20 bytes payload = 92
// Buffer position should be back to where it was after writing length and skipping forward
// After writing payload: position = 4 + 2 + 40 + 30 + 20 = 96
// After writing length back: skip(-(4 + nalu_len)) = skip(-96), write 4 bytes, skip(nalu_len) = skip(92)
// Final position should be 4 + 92 = 96
EXPECT_EQ(4 + 92, buffer.pos());
// Verify that the length was written back to the beginning
uint32_t written_length = 0;
memcpy(&written_length, buffer_data, 4);
written_length = ntohl(written_length); // Convert from network byte order
EXPECT_EQ(92, (int)written_length); // Should match final nalu_len
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 STAP payload
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferSTAPHevc)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.265 STAP payload
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(7000);
pkt->header_.set_timestamp(700);
// Create HEVC STAP payload with multiple NALUs
SrsRtpSTAPPayloadHevc *stap_hevc = new SrsRtpSTAPPayloadHevc();
// Add first NALU (VPS)
SrsNaluSample *vps_sample = new SrsNaluSample();
char vps_data[25];
memset(vps_data, 0x40, sizeof(vps_data)); // VPS NALU type
vps_sample->bytes_ = vps_data;
vps_sample->size_ = sizeof(vps_data);
stap_hevc->nalus_.push_back(vps_sample);
// Add second NALU (SPS)
SrsNaluSample *sps_sample = new SrsNaluSample();
char sps_data[30];
memset(sps_data, 0x42, sizeof(sps_data)); // SPS NALU type
sps_sample->bytes_ = sps_data;
sps_sample->size_ = sizeof(sps_data);
stap_hevc->nalus_.push_back(sps_sample);
// Add third NALU (PPS)
SrsNaluSample *pps_sample = new SrsNaluSample();
char pps_data[15];
memset(pps_data, 0x44, sizeof(pps_data)); // PPS NALU type
pps_sample->bytes_ = pps_data;
pps_sample->size_ = sizeof(pps_data);
stap_hevc->nalus_.push_back(pps_sample);
pkt->set_payload(stap_hevc, SrsRtpPacketPayloadTypeSTAPHevc);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For HEVC STAP payload:
// - Each NALU should be written with 4-byte length prefix + NALU data
// - Expected: (4 + 25) + (4 + 30) + (4 + 15) = 29 + 34 + 19 = 82 bytes
EXPECT_EQ(82, buffer.pos());
EXPECT_EQ(0, nalu_len); // nalu_len is not modified for STAP payloads
// Verify the first NALU length was written correctly
uint32_t first_nalu_length = 0;
memcpy(&first_nalu_length, buffer_data, 4);
first_nalu_length = ntohl(first_nalu_length); // Convert from network byte order
EXPECT_EQ(25, (int)first_nalu_length); // Should match VPS NALU size
// Verify the second NALU length was written correctly
uint32_t second_nalu_length = 0;
memcpy(&second_nalu_length, buffer_data + 29, 4); // Skip first NALU (4 + 25 bytes)
second_nalu_length = ntohl(second_nalu_length);
EXPECT_EQ(30, (int)second_nalu_length); // Should match SPS NALU size
// Verify the third NALU length was written correctly
uint32_t third_nalu_length = 0;
memcpy(&third_nalu_length, buffer_data + 63, 4); // Skip first two NALUs (29 + 34 bytes)
third_nalu_length = ntohl(third_nalu_length);
EXPECT_EQ(15, (int)third_nalu_length); // Should match PPS NALU size
}
// Test SrsRtcFrameBuilder::write_packet_payload_to_buffer with H.265 STAP payload containing empty NALUs
VOID TEST(SrsRtcFrameBuilderTest, WritePacketPayloadToBufferSTAPHevcWithEmptyNALUs)
{
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Create test RTP packet with H.265 STAP payload
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_payload_type(96);
pkt->header_.set_ssrc(12345);
pkt->header_.set_sequence(7001);
pkt->header_.set_timestamp(700);
// Create HEVC STAP payload with mixed empty and valid NALUs
SrsRtpSTAPPayloadHevc *stap_hevc = new SrsRtpSTAPPayloadHevc();
// Add empty NALU (should be skipped)
SrsNaluSample *empty_sample1 = new SrsNaluSample();
empty_sample1->bytes_ = NULL;
empty_sample1->size_ = 0;
stap_hevc->nalus_.push_back(empty_sample1);
// Add valid NALU (VPS)
SrsNaluSample *vps_sample = new SrsNaluSample();
char vps_data[20];
memset(vps_data, 0x40, sizeof(vps_data)); // VPS NALU type
vps_sample->bytes_ = vps_data;
vps_sample->size_ = sizeof(vps_data);
stap_hevc->nalus_.push_back(vps_sample);
// Add another empty NALU (should be skipped)
SrsNaluSample *empty_sample2 = new SrsNaluSample();
empty_sample2->bytes_ = NULL;
empty_sample2->size_ = 0;
stap_hevc->nalus_.push_back(empty_sample2);
// Add valid NALU (SPS)
SrsNaluSample *sps_sample = new SrsNaluSample();
char sps_data[35];
memset(sps_data, 0x42, sizeof(sps_data)); // SPS NALU type
sps_sample->bytes_ = sps_data;
sps_sample->size_ = sizeof(sps_data);
stap_hevc->nalus_.push_back(sps_sample);
// Add final empty NALU (should be skipped)
SrsNaluSample *empty_sample3 = new SrsNaluSample();
empty_sample3->bytes_ = NULL;
empty_sample3->size_ = 0;
stap_hevc->nalus_.push_back(empty_sample3);
pkt->set_payload(stap_hevc, SrsRtpPacketPayloadTypeSTAPHevc);
// Create buffer for writing
char buffer_data[1024];
SrsBuffer buffer(buffer_data, sizeof(buffer_data));
int nalu_len = 0;
// Test write_packet_payload_to_buffer method
builder.write_packet_payload_to_buffer(pkt.get(), buffer, nalu_len);
// For HEVC STAP payload with empty NALUs:
// - Empty NALUs (size_ = 0) should be skipped
// - Only valid NALUs should be written: (4 + 20) + (4 + 35) = 24 + 39 = 63 bytes
EXPECT_EQ(63, buffer.pos());
EXPECT_EQ(0, nalu_len); // nalu_len is not modified for STAP payloads
// Verify the first valid NALU length was written correctly
uint32_t first_nalu_length = 0;
memcpy(&first_nalu_length, buffer_data, 4);
first_nalu_length = ntohl(first_nalu_length); // Convert from network byte order
EXPECT_EQ(20, (int)first_nalu_length); // Should match VPS NALU size
// Verify the second valid NALU length was written correctly
uint32_t second_nalu_length = 0;
memcpy(&second_nalu_length, buffer_data + 24, 4); // Skip first NALU (4 + 20 bytes)
second_nalu_length = ntohl(second_nalu_length);
EXPECT_EQ(35, (int)second_nalu_length); // Should match SPS NALU size
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - basic case
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluBasic)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create empty RTP packets (no payload) to simulate empty NALU scenario
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Create next frame packets that should be processed after empty frame is skipped
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(102, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(103, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
// Test packet_video_rtmp with empty frame range (100-101)
// This should trigger the empty NALU handling logic and process next frame (102-103)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that a frame was generated (from the next frame, not the empty one)
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
// Verify the frame is a video frame
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - no next frame available
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluNoNextFrame)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create empty RTP packets (no payload) to simulate empty NALU scenario
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Do NOT store any next frame packets - this tests the case where detect_next_frame
// returns got_frame = false
// Test packet_video_rtmp with empty frame range (100-101)
// This should trigger the empty NALU handling logic but find no next frame
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that no frame was generated since there was no next frame available
EXPECT_EQ(0, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ == NULL);
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - recursive call with next frame
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluRecursiveCall)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create empty RTP packets (no payload) for first frame
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Create valid next frame packets with actual payload
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(102, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(103, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt3(create_test_rtp_packet(104, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
builder.video_cache_->store_packet(next_pkt3->copy());
// Test packet_video_rtmp with empty frame range (100-101)
// This should:
// 1. Detect empty NALU (nb_payload == 0)
// 2. Call detect_next_frame(102, ...)
// 3. Find next frame (102-104)
// 4. Recursively call packet_video_rtmp(102, 104)
// 5. Process the valid frame
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that a frame was generated from the recursive call
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - multiple empty frames
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluMultipleEmptyFrames)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create multiple empty frames in sequence
// First empty frame (100-101)
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// Second empty frame (102-103)
SrsUniquePtr<SrsRtpPacket> empty_pkt3(new SrsRtpPacket());
empty_pkt3->header_.set_sequence(102);
empty_pkt3->header_.set_timestamp(2000);
empty_pkt3->header_.set_ssrc(12345);
empty_pkt3->header_.set_payload_type(96);
SrsUniquePtr<SrsRtpPacket> empty_pkt4(new SrsRtpPacket());
empty_pkt4->header_.set_sequence(103);
empty_pkt4->header_.set_timestamp(2000);
empty_pkt4->header_.set_ssrc(12345);
empty_pkt4->header_.set_payload_type(96);
// Valid frame (104-105)
SrsUniquePtr<SrsRtpPacket> valid_pkt1(create_test_rtp_packet(104, 3000, 12345, false));
SrsUniquePtr<SrsRtpPacket> valid_pkt2(create_test_rtp_packet(105, 3000, 12345, true)); // marker bit
// Store all packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
builder.video_cache_->store_packet(empty_pkt3->copy());
builder.video_cache_->store_packet(empty_pkt4->copy());
builder.video_cache_->store_packet(valid_pkt1->copy());
builder.video_cache_->store_packet(valid_pkt2->copy());
// Test packet_video_rtmp with first empty frame range (100-101)
// This should skip multiple empty frames and eventually process the valid frame (104-105)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that a frame was generated (should be the valid frame after skipping empty ones)
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - zero-size payloads
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluZeroSizePayloads)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create RTP packets with zero-size raw payloads
SrsUniquePtr<SrsRtpPacket> zero_pkt1(new SrsRtpPacket());
zero_pkt1->header_.set_sequence(100);
zero_pkt1->header_.set_timestamp(1000);
zero_pkt1->header_.set_ssrc(12345);
zero_pkt1->header_.set_payload_type(96);
// Create raw payload with zero size
SrsRtpRawPayload *raw_payload1 = new SrsRtpRawPayload();
raw_payload1->payload_ = NULL;
raw_payload1->nn_payload_ = 0; // Zero size payload
zero_pkt1->set_payload(raw_payload1, SrsRtpPacketPayloadTypeRaw);
SrsUniquePtr<SrsRtpPacket> zero_pkt2(new SrsRtpPacket());
zero_pkt2->header_.set_sequence(101);
zero_pkt2->header_.set_timestamp(1000);
zero_pkt2->header_.set_ssrc(12345);
zero_pkt2->header_.set_payload_type(96);
// Create raw payload with zero size
SrsRtpRawPayload *raw_payload2 = new SrsRtpRawPayload();
raw_payload2->payload_ = NULL;
raw_payload2->nn_payload_ = 0; // Zero size payload
zero_pkt2->set_payload(raw_payload2, SrsRtpPacketPayloadTypeRaw);
// Store zero-size payload packets in video cache
builder.video_cache_->store_packet(zero_pkt1->copy());
builder.video_cache_->store_packet(zero_pkt2->copy());
// Create valid next frame packets
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(102, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(103, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
// Test packet_video_rtmp with zero-size payload frame range (100-101)
// This should trigger the empty NALU handling logic and process next frame (102-103)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that a frame was generated (from the next frame, not the zero-size one)
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - H.265 codec
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluHevc)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.265 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdHEVC));
// Create empty RTP packets (no payload) to simulate empty NALU scenario
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Create next frame packets that should be processed after empty frame is skipped
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(102, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(103, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
// Test packet_video_rtmp with empty frame range (100-101) for H.265
// This should trigger the empty NALU handling logic and process next frame (102-103)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that a frame was generated (from the next frame, not the empty one)
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
// Verify the frame is a video frame
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - sequence number wrap-around
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluSequenceWrapAround)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create empty RTP packets near sequence number wrap-around (65535 -> 0)
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(65535); // Max uint16_t
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(0); // Wrap-around to 0
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// No payload set - this will result in 0 payload size
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Create next frame packets after wrap-around
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(1, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(2, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
// Test packet_video_rtmp with empty frame range (65535-0)
// This should trigger the empty NALU handling logic and process next frame (1-2)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(65535, 0));
// Verify that a frame was generated (from the next frame, not the empty one)
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsRtcFrameBuilder::packet_video_rtmp with empty NALU handling - verify frame processing
VOID TEST(SrsRtcFrameBuilderTest, PacketVideoRtmpEmptyNaluFrameProcessing)
{
srs_error_t err;
MockRtcFrameTarget target;
SrsRtcFrameBuilder builder(_srs_app_factory, &target);
// Initialize builder with H.264 codec
SrsUniquePtr<MockRtcRequest> req(new MockRtcRequest());
HELPER_EXPECT_SUCCESS(builder.initialize(req.get(), SrsAudioCodecIdAAC, SrsVideoCodecIdAVC));
// Create empty RTP packets (no payload) to simulate empty NALU scenario
SrsUniquePtr<SrsRtpPacket> empty_pkt1(new SrsRtpPacket());
empty_pkt1->header_.set_sequence(100);
empty_pkt1->header_.set_timestamp(1000);
empty_pkt1->header_.set_ssrc(12345);
empty_pkt1->header_.set_payload_type(96);
SrsUniquePtr<SrsRtpPacket> empty_pkt2(new SrsRtpPacket());
empty_pkt2->header_.set_sequence(101);
empty_pkt2->header_.set_timestamp(1000);
empty_pkt2->header_.set_ssrc(12345);
empty_pkt2->header_.set_payload_type(96);
// Store empty packets in video cache
builder.video_cache_->store_packet(empty_pkt1->copy());
builder.video_cache_->store_packet(empty_pkt2->copy());
// Create next frame packets that should be processed after empty frame is skipped
SrsUniquePtr<SrsRtpPacket> next_pkt1(create_test_rtp_packet(102, 2000, 12345, false));
SrsUniquePtr<SrsRtpPacket> next_pkt2(create_test_rtp_packet(103, 2000, 12345, true)); // marker bit
// Store next frame packets in video cache
builder.video_cache_->store_packet(next_pkt1->copy());
builder.video_cache_->store_packet(next_pkt2->copy());
// Test packet_video_rtmp with empty frame range (100-101)
// This should trigger the empty NALU handling logic and process next frame (102-103)
HELPER_EXPECT_SUCCESS(builder.packet_video_rtmp(100, 101));
// Verify that the empty NALU handling worked correctly:
// 1. The empty frame (100-101) was skipped
// 2. The next frame (102-103) was processed instead
// 3. A frame was generated and sent to the target
EXPECT_EQ(1, target.on_frame_count_);
EXPECT_TRUE(target.last_frame_ != NULL);
EXPECT_TRUE(target.last_frame_->is_video());
}
// Test SrsCodecPayload::generate_media_payload_type
VOID TEST(SrsCodecPayloadTest, GenerateMediaPayloadType)
{
SrsUniquePtr<SrsCodecPayload> payload(create_test_codec_payload(96, "H264", 90000));
SrsMediaPayloadType media_type = payload->generate_media_payload_type();
EXPECT_EQ(96, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
}
// Test SrsVideoPayload::generate_media_payload_type with empty H264 parameters
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeEmptyParams)
{
SrsVideoPayload payload(102, "H264", 90000);
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with level_asymmetry_allow only
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeLevelAsymmetryOnly)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.level_asymmetry_allow_ = "1";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-asymmetry-allowed=1", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with packetization_mode only
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypePacketizationModeOnly)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.packetization_mode_ = "1";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("packetization-mode=1", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with profile_level_id only
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeProfileLevelIdOnly)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.profile_level_id_ = "42e01f";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("profile-level-id=42e01f", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with level_asymmetry_allow and packetization_mode
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeLevelAsymmetryAndPacketization)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.level_asymmetry_allow_ = "1";
payload.h264_param_.packetization_mode_ = "1";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-asymmetry-allowed=1;packetization-mode=1", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with level_asymmetry_allow and profile_level_id
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeLevelAsymmetryAndProfileLevelId)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.level_asymmetry_allow_ = "1";
payload.h264_param_.profile_level_id_ = "42e01f";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-asymmetry-allowed=1;profile-level-id=42e01f", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with packetization_mode and profile_level_id
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypePacketizationAndProfileLevelId)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.packetization_mode_ = "1";
payload.h264_param_.profile_level_id_ = "42e01f";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("packetization-mode=1;profile-level-id=42e01f", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with all H264 parameters
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeAllH264Params)
{
SrsVideoPayload payload(102, "H264", 90000);
payload.h264_param_.level_asymmetry_allow_ = "1";
payload.h264_param_.packetization_mode_ = "1";
payload.h264_param_.profile_level_id_ = "42e01f";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(102, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", media_type.format_specific_param_.c_str());
}
// Test SrsVideoPayload::generate_media_payload_type with different parameter values
VOID TEST(SrsVideoPayloadTest, GenerateMediaPayloadTypeDifferentValues)
{
SrsVideoPayload payload(96, "H264", 90000);
payload.h264_param_.level_asymmetry_allow_ = "0";
payload.h264_param_.packetization_mode_ = "0";
payload.h264_param_.profile_level_id_ = "640028";
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(96, media_type.payload_type_);
EXPECT_STREQ("H264", media_type.encoding_name_.c_str());
EXPECT_EQ(90000, media_type.clock_rate_);
EXPECT_STREQ("level-asymmetry-allowed=0;packetization-mode=0;profile-level-id=640028", media_type.format_specific_param_.c_str());
}
// Test SrsAudioPayload::generate_media_payload_type
VOID TEST(SrsAudioPayloadTest, GenerateMediaPayloadType)
{
SrsAudioPayload payload(111, "opus", 48000, 2);
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(111, media_type.payload_type_);
EXPECT_STREQ("opus", media_type.encoding_name_.c_str());
EXPECT_EQ(48000, media_type.clock_rate_);
}
// Test SrsAudioPayload::set_opus_param_desc
VOID TEST(SrsAudioPayloadTest, SetOpusParamDesc)
{
srs_error_t err;
SrsAudioPayload payload(111, "opus", 48000, 2);
// Test valid opus parameters
HELPER_EXPECT_SUCCESS(payload.set_opus_param_desc("minptime=10;useinbandfec=1;stereo=1;usedtx=1"));
EXPECT_EQ(10, payload.opus_param_.minptime_);
EXPECT_TRUE(payload.opus_param_.use_inband_fec_);
EXPECT_TRUE(payload.opus_param_.stereo_);
EXPECT_TRUE(payload.opus_param_.usedtx_);
// Test partial parameters
SrsAudioPayload payload2(111, "opus", 48000, 2);
HELPER_EXPECT_SUCCESS(payload2.set_opus_param_desc("minptime=20"));
EXPECT_EQ(20, payload2.opus_param_.minptime_);
EXPECT_FALSE(payload2.opus_param_.use_inband_fec_);
EXPECT_FALSE(payload2.opus_param_.stereo_);
EXPECT_FALSE(payload2.opus_param_.usedtx_);
}
// Test SrsAudioPayload::generate_media_payload_type with Opus parameters - individual parameters
VOID TEST(SrsAudioPayloadTest, GenerateMediaPayloadTypeOpusIndividualParams)
{
// Test minptime only
SrsAudioPayload payload1(111, "opus", 48000, 2);
payload1.opus_param_.minptime_ = 10;
SrsMediaPayloadType media_type1 = payload1.generate_media_payload_type();
EXPECT_EQ(111, media_type1.payload_type_);
EXPECT_STREQ("opus", media_type1.encoding_name_.c_str());
EXPECT_EQ(48000, media_type1.clock_rate_);
EXPECT_STREQ("2", media_type1.encoding_param_.c_str());
EXPECT_STREQ("minptime=10", media_type1.format_specific_param_.c_str());
// Test use_inband_fec only
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "useinbandfec=1" instead of ";useinbandfec=1"
SrsAudioPayload payload2(111, "opus", 48000, 2);
payload2.opus_param_.use_inband_fec_ = true;
SrsMediaPayloadType media_type2 = payload2.generate_media_payload_type();
EXPECT_STREQ(";useinbandfec=1", media_type2.format_specific_param_.c_str());
// Test stereo only
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "stereo=1" instead of ";stereo=1"
SrsAudioPayload payload3(111, "opus", 48000, 2);
payload3.opus_param_.stereo_ = true;
SrsMediaPayloadType media_type3 = payload3.generate_media_payload_type();
EXPECT_STREQ(";stereo=1", media_type3.format_specific_param_.c_str());
// Test usedtx only
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "usedtx=1" instead of ";usedtx=1"
SrsAudioPayload payload4(111, "opus", 48000, 2);
payload4.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type4 = payload4.generate_media_payload_type();
EXPECT_STREQ(";usedtx=1", media_type4.format_specific_param_.c_str());
}
// Test SrsAudioPayload::generate_media_payload_type with Opus parameters - combinations starting with minptime
VOID TEST(SrsAudioPayloadTest, GenerateMediaPayloadTypeOpusMinptimeCombinations)
{
// Test minptime + use_inband_fec
SrsAudioPayload payload1(111, "opus", 48000, 2);
payload1.opus_param_.minptime_ = 20;
payload1.opus_param_.use_inband_fec_ = true;
SrsMediaPayloadType media_type1 = payload1.generate_media_payload_type();
EXPECT_STREQ("minptime=20;useinbandfec=1", media_type1.format_specific_param_.c_str());
// Test minptime + stereo
SrsAudioPayload payload2(111, "opus", 48000, 2);
payload2.opus_param_.minptime_ = 15;
payload2.opus_param_.stereo_ = true;
SrsMediaPayloadType media_type2 = payload2.generate_media_payload_type();
EXPECT_STREQ("minptime=15;stereo=1", media_type2.format_specific_param_.c_str());
// Test minptime + usedtx
SrsAudioPayload payload3(111, "opus", 48000, 2);
payload3.opus_param_.minptime_ = 5;
payload3.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type3 = payload3.generate_media_payload_type();
EXPECT_STREQ("minptime=5;usedtx=1", media_type3.format_specific_param_.c_str());
// Test minptime + use_inband_fec + stereo
SrsAudioPayload payload4(111, "opus", 48000, 2);
payload4.opus_param_.minptime_ = 25;
payload4.opus_param_.use_inband_fec_ = true;
payload4.opus_param_.stereo_ = true;
SrsMediaPayloadType media_type4 = payload4.generate_media_payload_type();
EXPECT_STREQ("minptime=25;useinbandfec=1;stereo=1", media_type4.format_specific_param_.c_str());
// Test minptime + use_inband_fec + usedtx
SrsAudioPayload payload5(111, "opus", 48000, 2);
payload5.opus_param_.minptime_ = 30;
payload5.opus_param_.use_inband_fec_ = true;
payload5.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type5 = payload5.generate_media_payload_type();
EXPECT_STREQ("minptime=30;useinbandfec=1;usedtx=1", media_type5.format_specific_param_.c_str());
// Test minptime + stereo + usedtx
SrsAudioPayload payload6(111, "opus", 48000, 2);
payload6.opus_param_.minptime_ = 40;
payload6.opus_param_.stereo_ = true;
payload6.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type6 = payload6.generate_media_payload_type();
EXPECT_STREQ("minptime=40;stereo=1;usedtx=1", media_type6.format_specific_param_.c_str());
// Test all parameters
SrsAudioPayload payload7(111, "opus", 48000, 2);
payload7.opus_param_.minptime_ = 50;
payload7.opus_param_.use_inband_fec_ = true;
payload7.opus_param_.stereo_ = true;
payload7.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type7 = payload7.generate_media_payload_type();
EXPECT_STREQ("minptime=50;useinbandfec=1;stereo=1;usedtx=1", media_type7.format_specific_param_.c_str());
}
// Test SrsAudioPayload::generate_media_payload_type with Opus parameters - combinations without minptime
VOID TEST(SrsAudioPayloadTest, GenerateMediaPayloadTypeOpusWithoutMinptimeCombinations)
{
// Test use_inband_fec + stereo
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "useinbandfec=1;stereo=1" instead of ";useinbandfec=1;stereo=1"
SrsAudioPayload payload1(111, "opus", 48000, 2);
payload1.opus_param_.use_inband_fec_ = true;
payload1.opus_param_.stereo_ = true;
SrsMediaPayloadType media_type1 = payload1.generate_media_payload_type();
EXPECT_STREQ(";useinbandfec=1;stereo=1", media_type1.format_specific_param_.c_str());
// Test use_inband_fec + usedtx
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "useinbandfec=1;usedtx=1" instead of ";useinbandfec=1;usedtx=1"
SrsAudioPayload payload2(111, "opus", 48000, 2);
payload2.opus_param_.use_inband_fec_ = true;
payload2.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type2 = payload2.generate_media_payload_type();
EXPECT_STREQ(";useinbandfec=1;usedtx=1", media_type2.format_specific_param_.c_str());
// Test stereo + usedtx
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "stereo=1;usedtx=1" instead of ";stereo=1;usedtx=1"
SrsAudioPayload payload3(111, "opus", 48000, 2);
payload3.opus_param_.stereo_ = true;
payload3.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type3 = payload3.generate_media_payload_type();
EXPECT_STREQ(";stereo=1;usedtx=1", media_type3.format_specific_param_.c_str());
// Test use_inband_fec + stereo + usedtx
// NOTE: Current implementation has a bug - it adds semicolon even when no preceding content
// TODO: Should be "useinbandfec=1;stereo=1;usedtx=1" instead of ";useinbandfec=1;stereo=1;usedtx=1"
SrsAudioPayload payload4(111, "opus", 48000, 2);
payload4.opus_param_.use_inband_fec_ = true;
payload4.opus_param_.stereo_ = true;
payload4.opus_param_.usedtx_ = true;
SrsMediaPayloadType media_type4 = payload4.generate_media_payload_type();
EXPECT_STREQ(";useinbandfec=1;stereo=1;usedtx=1", media_type4.format_specific_param_.c_str());
}
// Test SrsAudioPayload::generate_media_payload_type with no Opus parameters (empty string)
VOID TEST(SrsAudioPayloadTest, GenerateMediaPayloadTypeOpusNoParams)
{
SrsAudioPayload payload(111, "opus", 48000, 2);
// All opus_param_ fields remain at default values (false/0)
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(111, media_type.payload_type_);
EXPECT_STREQ("opus", media_type.encoding_name_.c_str());
EXPECT_EQ(48000, media_type.clock_rate_);
EXPECT_STREQ("2", media_type.encoding_param_.c_str());
EXPECT_STREQ("", media_type.format_specific_param_.c_str()); // Should be empty string
}
// Test SrsRedPayload
VOID TEST(SrsRedPayloadTest, BasicFunctionality)
{
SrsRedPayload payload(63, "red", 48000, 2);
EXPECT_EQ(63, payload.pt_);
EXPECT_STREQ("red", payload.name_.c_str());
EXPECT_EQ(48000, payload.sample_);
EXPECT_EQ(2, payload.channel_);
// Test copy
SrsUniquePtr<SrsRedPayload> copied(payload.copy());
EXPECT_EQ(63, copied->pt_);
EXPECT_STREQ("red", copied->name_.c_str());
EXPECT_EQ(48000, copied->sample_);
EXPECT_EQ(2, copied->channel_);
// Test generate_media_payload_type
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(63, media_type.payload_type_);
EXPECT_STREQ("red", media_type.encoding_name_.c_str());
EXPECT_EQ(48000, media_type.clock_rate_);
}
// Test SrsRtxPayloadDes
VOID TEST(SrsRtxPayloadDesTest, BasicFunctionality)
{
SrsRtxPayloadDes payload(96, 97);
EXPECT_EQ(96, payload.pt_);
EXPECT_EQ(97, payload.apt_);
// Test copy
SrsUniquePtr<SrsRtxPayloadDes> copied(payload.copy());
EXPECT_EQ(96, copied->pt_);
EXPECT_EQ(97, copied->apt_);
// Test generate_media_payload_type
SrsMediaPayloadType media_type = payload.generate_media_payload_type();
EXPECT_EQ(96, media_type.payload_type_);
EXPECT_STREQ("rtx", media_type.encoding_name_.c_str());
}
// Test SrsRtcTrackDescription::add_rtp_extension_desc
VOID TEST(SrsRtcTrackDescriptionTest, AddRtpExtensionDesc)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
// Add RTP extension
desc->add_rtp_extension_desc(1, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
desc->add_rtp_extension_desc(2, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time");
// Verify extensions were added
EXPECT_EQ(2, desc->extmaps_.size());
EXPECT_STREQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", desc->extmaps_[1].c_str());
EXPECT_STREQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", desc->extmaps_[2].c_str());
}
// Test SrsRtcTrackDescription::del_rtp_extension_desc
VOID TEST(SrsRtcTrackDescriptionTest, DelRtpExtensionDesc)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
// Add extensions first
desc->add_rtp_extension_desc(1, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
desc->add_rtp_extension_desc(2, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time");
EXPECT_EQ(2, desc->extmaps_.size());
// Delete one extension
desc->del_rtp_extension_desc("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
EXPECT_EQ(1, desc->extmaps_.size());
EXPECT_STREQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", desc->extmaps_[2].c_str());
// Delete non-existent extension (should not crash)
desc->del_rtp_extension_desc("non-existent-extension");
EXPECT_EQ(1, desc->extmaps_.size());
}
// Test SrsRtcTrackDescription::set_direction
VOID TEST(SrsRtcTrackDescriptionTest, SetDirection)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
desc->set_direction("sendonly");
EXPECT_STREQ("sendonly", desc->direction_.c_str());
desc->set_direction("recvonly");
EXPECT_STREQ("recvonly", desc->direction_.c_str());
desc->set_direction("sendrecv");
EXPECT_STREQ("sendrecv", desc->direction_.c_str());
desc->set_direction("inactive");
EXPECT_STREQ("inactive", desc->direction_.c_str());
}
// Test SrsRtcTrackDescription::set_codec_payload
VOID TEST(SrsRtcTrackDescriptionTest, SetCodecPayload)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
SrsCodecPayload *payload = create_test_codec_payload(96, "H264", 90000);
desc->set_codec_payload(payload); // Track description takes ownership
EXPECT_TRUE(desc->media_ != NULL);
EXPECT_EQ(96, desc->media_->pt_);
EXPECT_STREQ("H264", desc->media_->name_.c_str());
EXPECT_EQ(90000, desc->media_->sample_);
// Payload ownership transferred to track description, will be cleaned up by desc destructor
}
// Test SrsRtcTrackDescription::create_auxiliary_payload
VOID TEST(SrsRtcTrackDescriptionTest, CreateAuxiliaryPayload)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
// Test RED payload type (method only processes first payload)
std::vector<SrsMediaPayloadType> red_payload_types;
SrsMediaPayloadType red_type(63);
red_type.encoding_name_ = "red";
red_type.clock_rate_ = 90000;
red_type.encoding_param_ = "2"; // Channel parameter for RED
red_payload_types.push_back(red_type);
desc->create_auxiliary_payload(red_payload_types);
// Verify RED payload was created
EXPECT_TRUE(desc->red_ != NULL);
EXPECT_EQ(63, desc->red_->pt_);
EXPECT_STREQ("red", desc->red_->name_.c_str());
// Test RTX payload type separately
std::vector<SrsMediaPayloadType> rtx_payload_types;
SrsMediaPayloadType rtx_type(96);
rtx_type.encoding_name_ = "rtx";
rtx_type.clock_rate_ = 90000;
rtx_type.encoding_param_ = "97"; // APT parameter for RTX
rtx_payload_types.push_back(rtx_type);
desc->create_auxiliary_payload(rtx_payload_types);
// Verify RTX payload was created
EXPECT_TRUE(desc->rtx_ != NULL);
EXPECT_EQ(96, desc->rtx_->pt_);
EXPECT_STREQ("rtx", desc->rtx_->name_.c_str());
}
// Test SrsRtcTrackDescription::set_rtx_ssrc
VOID TEST(SrsRtcTrackDescriptionTest, SetRtxSsrc)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
desc->set_rtx_ssrc(67890);
EXPECT_EQ(67890, desc->rtx_ssrc_);
}
// Test SrsRtcTrackDescription::set_fec_ssrc
VOID TEST(SrsRtcTrackDescriptionTest, SetFecSsrc)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
desc->set_fec_ssrc(54321);
EXPECT_EQ(54321, desc->fec_ssrc_);
}
// Test SrsRtcTrackDescription::set_mid
VOID TEST(SrsRtcTrackDescriptionTest, SetMid)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
desc->set_mid("video-mid");
EXPECT_STREQ("video-mid", desc->mid_.c_str());
}
// Test SrsRtcTrackDescription::get_rtp_extension_id
VOID TEST(SrsRtcTrackDescriptionTest, GetRtpExtensionId)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
// Add extensions
desc->add_rtp_extension_desc(1, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
desc->add_rtp_extension_desc(2, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time");
// Test getting extension IDs
EXPECT_EQ(1, desc->get_rtp_extension_id("urn:ietf:params:rtp-hdrext:ssrc-audio-level"));
EXPECT_EQ(2, desc->get_rtp_extension_id("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"));
// Test non-existent extension
EXPECT_EQ(0, desc->get_rtp_extension_id("non-existent-extension"));
}
// Test SrsRtcSourceDescription::find_track_description_by_ssrc
VOID TEST(SrsRtcSourceDescriptionTest, FindTrackDescriptionBySsrc)
{
SrsRtcSourceDescription source_desc;
// Create audio track
source_desc.audio_track_desc_ = create_test_track_description("audio", 11111);
// Create video tracks
source_desc.video_track_descs_.push_back(create_test_track_description("video", 22222));
source_desc.video_track_descs_.push_back(create_test_track_description("video", 33333));
// Test finding audio track
SrsRtcTrackDescription *found_audio = source_desc.find_track_description_by_ssrc(11111);
EXPECT_TRUE(found_audio != NULL);
EXPECT_EQ(11111, found_audio->ssrc_);
EXPECT_STREQ("audio", found_audio->type_.c_str());
// Test finding video tracks
SrsRtcTrackDescription *found_video1 = source_desc.find_track_description_by_ssrc(22222);
EXPECT_TRUE(found_video1 != NULL);
EXPECT_EQ(22222, found_video1->ssrc_);
EXPECT_STREQ("video", found_video1->type_.c_str());
SrsRtcTrackDescription *found_video2 = source_desc.find_track_description_by_ssrc(33333);
EXPECT_TRUE(found_video2 != NULL);
EXPECT_EQ(33333, found_video2->ssrc_);
EXPECT_STREQ("video", found_video2->type_.c_str());
// Test finding non-existent SSRC
SrsRtcTrackDescription *not_found = source_desc.find_track_description_by_ssrc(99999);
EXPECT_TRUE(not_found == NULL);
}
// Note: The following tests are commented out because they require a proper MockRtcConnection
// that inherits from SrsRtcConnection, which is complex to implement properly.
// These tests would need a full mock implementation of SrsRtcConnection.
/*
// Test SrsRtcRecvTrack::get_ssrc
VOID TEST(SrsRtcRecvTrackTest, GetSsrc)
{
// This test requires a proper SrsRtcConnection mock
// MockRtcConnection session;
// SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("audio", 12345));
// SrsRtcAudioRecvTrack track(&session, desc.get());
// EXPECT_EQ(12345, track.get_ssrc());
}
*/
// Helper function to create track description with video codec
SrsRtcTrackDescription *create_video_track_description(std::string codec_name, uint32_t ssrc)
{
SrsRtcTrackDescription *desc = new SrsRtcTrackDescription();
desc->type_ = "video";
desc->ssrc_ = ssrc;
desc->id_ = "test-video-track";
desc->is_active_ = true;
desc->direction_ = "sendrecv";
desc->mid_ = "0";
// Create video payload with specified codec
SrsVideoPayload *video_payload = new SrsVideoPayload(96, codec_name, 90000);
desc->set_codec_payload(video_payload);
return desc;
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with empty buffer
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadEmptyBuffer)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create empty buffer
char buffer_data[1024];
SrsBuffer buffer(buffer_data, 0); // Empty buffer
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test with empty buffer - should return early without setting payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload == NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeUnknown, ppt);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.264 raw NALU
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadH264RawNALU)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.264 SPS NALU (type 7)
char buffer_data[1024];
buffer_data[0] = 0x67; // SPS NALU type (0x60 | 0x07)
buffer_data[1] = 0x42;
buffer_data[2] = 0x00;
buffer_data[3] = 0x1e;
SrsBuffer buffer(buffer_data, 4);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.264 raw NALU - should create raw payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
EXPECT_EQ(7, pkt->nalu_type_); // Should set NALU type to SPS (7)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.264 STAP-A payload
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadH264STAP)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.264 STAP-A NALU (type 24)
char buffer_data[1024];
buffer_data[0] = kStapA; // STAP-A NALU type (24)
buffer_data[1] = 0x00;
buffer_data[2] = 0x10; // First NALU size (16 bytes)
buffer_data[3] = 0x67; // SPS NALU
// Fill remaining bytes with dummy data
for (int i = 4; i < 20; i++) {
buffer_data[i] = 0x42;
}
SrsBuffer buffer(buffer_data, 20);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.264 STAP-A - should create STAP payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeSTAP, ppt);
EXPECT_EQ(kStapA, pkt->nalu_type_); // Should set NALU type to STAP-A (24)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.264 FU-A payload
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadH264FUA)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.264 FU-A NALU (type 28)
char buffer_data[1024];
buffer_data[0] = kFuA; // FU-A NALU type (28)
buffer_data[1] = 0x85; // FU header: start=1, end=0, type=5 (IDR)
buffer_data[2] = 0x88; // First byte of IDR slice
// Fill remaining bytes with dummy data
for (int i = 3; i < 50; i++) {
buffer_data[i] = 0x99;
}
SrsBuffer buffer(buffer_data, 50);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.264 FU-A - should create FUA payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeFUA2, ppt);
EXPECT_EQ(kFuA, pkt->nalu_type_); // Should set NALU type to FU-A (28)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.265 raw NALU
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadHEVCRawNALU)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H265", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.265 VPS NALU (type 32)
char buffer_data[1024];
buffer_data[0] = 0x40; // VPS NALU type (32 << 1 = 64 = 0x40)
buffer_data[1] = 0x01; // Second byte of HEVC NALU header
buffer_data[2] = 0x01; // VPS data
buffer_data[3] = 0x60;
SrsBuffer buffer(buffer_data, 4);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.265 raw NALU - should create raw payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
EXPECT_EQ(32, pkt->nalu_type_); // Should set NALU type to VPS (32)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.265 STAP payload
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadHEVCSTAP)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H265", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.265 STAP NALU (type 48)
char buffer_data[1024];
buffer_data[0] = kStapHevc << 1; // STAP HEVC NALU type (48 << 1 = 96 = 0x60)
buffer_data[1] = 0x01; // Second byte of HEVC NALU header
buffer_data[2] = 0x00;
buffer_data[3] = 0x10; // First NALU size (16 bytes)
buffer_data[4] = 0x40; // VPS NALU
buffer_data[5] = 0x01;
// Fill remaining bytes with dummy data
for (int i = 6; i < 22; i++) {
buffer_data[i] = 0x42;
}
SrsBuffer buffer(buffer_data, 22);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.265 STAP - should create STAP HEVC payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeSTAPHevc, ppt);
EXPECT_EQ(kStapHevc, pkt->nalu_type_); // Should set NALU type to STAP HEVC (48)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.265 FU-A payload
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadHEVCFUA)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H265", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.265 FU-A NALU (type 49)
char buffer_data[1024];
buffer_data[0] = kFuHevc << 1; // FU HEVC NALU type (49 << 1 = 98 = 0x62)
buffer_data[1] = 0x01; // Second byte of HEVC NALU header
buffer_data[2] = 0x93; // FU header: start=1, end=0, type=19 (IDR)
buffer_data[3] = 0x88; // First byte of IDR slice
// Fill remaining bytes with dummy data
for (int i = 4; i < 50; i++) {
buffer_data[i] = 0x99;
}
SrsBuffer buffer(buffer_data, 50);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.265 FU-A - should create FUA HEVC payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeFUAHevc2, ppt);
EXPECT_EQ(kFuHevc, pkt->nalu_type_); // Should set NALU type to FU HEVC (49)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with unknown codec
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadUnknownCodec)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("VP8", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with some data
char buffer_data[1024];
buffer_data[0] = 0x90; // Some random byte
buffer_data[1] = 0x42;
SrsBuffer buffer(buffer_data, 2);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test unknown codec - should set payload to NULL and type to Unknown
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload == NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeUnknown, ppt);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with no media codec
// Note: This test exposes a potential bug in the implementation where media_ is not checked for NULL
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadNoMediaCodec)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("video", 12345));
// Free the existing media payload and set to NULL to test this edge case
srs_freep(desc->media_);
desc->media_ = NULL;
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.264 data
char buffer_data[1024];
buffer_data[0] = 0x67; // SPS NALU type
buffer_data[1] = 0x42;
SrsBuffer buffer(buffer_data, 2);
// Since we can't test the actual crash case, we'll just verify the setup
EXPECT_TRUE(desc->media_ == NULL);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.264 IDR NALU
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadH264IDR)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.264 IDR NALU (type 5)
char buffer_data[1024];
buffer_data[0] = 0x65; // IDR NALU type (0x60 | 0x05)
buffer_data[1] = 0x88;
buffer_data[2] = 0x84;
buffer_data[3] = 0x00;
SrsBuffer buffer(buffer_data, 4);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.264 IDR NALU - should create raw payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
EXPECT_EQ(5, pkt->nalu_type_); // Should set NALU type to IDR (5)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with H.265 IDR NALU
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadHEVCIDR)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H265", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with H.265 IDR NALU (type 19)
char buffer_data[1024];
buffer_data[0] = 0x26; // IDR NALU type (19 << 1 = 38 = 0x26)
buffer_data[1] = 0x01; // Second byte of HEVC NALU header
buffer_data[2] = 0x88;
buffer_data[3] = 0x84;
SrsBuffer buffer(buffer_data, 4);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test H.265 IDR NALU - should create raw payload
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
EXPECT_EQ(19, pkt->nalu_type_); // Should set NALU type to IDR (19)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::on_before_decode_payload with single byte buffer
VOID TEST(SrsRtcVideoRecvTrackTest, OnBeforeDecodePayloadSingleByte)
{
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> desc(create_video_track_description("H264", 12345));
SrsRtcVideoRecvTrack track(&mock_receiver, desc.get(), false);
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Create buffer with single byte
char buffer_data[1024];
buffer_data[0] = 0x67; // SPS NALU type
SrsBuffer buffer(buffer_data, 1);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
// Test single byte buffer - should still work
track.on_before_decode_payload(pkt.get(), &buffer, &payload, &ppt);
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
EXPECT_EQ(7, pkt->nalu_type_); // Should set NALU type to SPS (7)
// Cleanup
srs_freep(payload);
}
// Test SrsRtcVideoRecvTrack::check_send_nacks (basic functionality)
VOID TEST(SrsRtcVideoRecvTrackTest, CheckSendNacksBasic)
{
// This test verifies the basic structure without requiring full RTC connection
// The actual check_send_nacks implementation calls do_check_send_nacks
uint32_t timeout_nacks = 0;
// Test that timeout_nacks can be modified (simulating the internal logic)
timeout_nacks = 5;
EXPECT_EQ(5, timeout_nacks);
// Test basic NACK timeout logic
srs_utime_t current_time = 1000000; // 1 second in microseconds
srs_utime_t nack_timeout = 100000; // 100ms in microseconds
bool should_send_nack = (current_time > nack_timeout);
EXPECT_TRUE(should_send_nack); // Current time should be > 100ms
}
// Test SrsRtcSendTrack::has_ssrc
VOID TEST(SrsRtcSendTrackTest, HasSsrc)
{
SrsUniquePtr<SrsRtcTrackDescription> desc(create_test_track_description("audio", 12345));
// Test has_ssrc logic (simulating the implementation)
bool has_primary_ssrc = (desc->ssrc_ == 12345);
bool has_rtx_ssrc = (desc->rtx_ssrc_ == 12345);
bool has_fec_ssrc = (desc->fec_ssrc_ == 12345);
EXPECT_TRUE(has_primary_ssrc);
EXPECT_FALSE(has_rtx_ssrc); // RTX SSRC not set
EXPECT_FALSE(has_fec_ssrc); // FEC SSRC not set
// Test with RTX SSRC set
desc->set_rtx_ssrc(12345);
has_rtx_ssrc = (desc->rtx_ssrc_ == 12345);
EXPECT_TRUE(has_rtx_ssrc);
}
// Test SrsRtcSendTrack::rebuild_packet
VOID TEST(SrsRtcSendTrackTest, RebuildPacket)
{
// Create test RTP packet
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
// Test rebuild_packet logic (simulating jitter correction)
uint16_t original_seq = pkt->header_.get_sequence();
uint32_t original_ts = pkt->header_.get_timestamp();
// Simulate jitter correction
uint16_t corrected_seq = original_seq + 10; // Simulate sequence jitter
uint32_t corrected_ts = original_ts + 160; // Simulate timestamp jitter
pkt->header_.set_sequence(corrected_seq);
pkt->header_.set_timestamp(corrected_ts);
EXPECT_EQ(corrected_seq, pkt->header_.get_sequence());
EXPECT_EQ(corrected_ts, pkt->header_.get_timestamp());
EXPECT_NE(original_seq, pkt->header_.get_sequence());
EXPECT_NE(original_ts, pkt->header_.get_timestamp());
}
// Test SrsRtcSendTrack::on_nack
VOID TEST(SrsRtcSendTrackTest, OnNack)
{
// Create test RTP packet
SrsRtpPacket *pkt = create_test_rtp_packet(100, 1000, 12345);
SrsRtpPacket **ppkt = &pkt;
// Test on_nack logic (simulating packet retrieval and copy behavior)
bool nack_no_copy = false;
if (!nack_no_copy && *ppkt) {
// Should copy the packet
SrsRtpPacket *copied_pkt = (*ppkt)->copy();
EXPECT_TRUE(copied_pkt != NULL);
EXPECT_EQ((*ppkt)->header_.get_sequence(), copied_pkt->header_.get_sequence());
EXPECT_EQ((*ppkt)->header_.get_timestamp(), copied_pkt->header_.get_timestamp());
EXPECT_EQ((*ppkt)->header_.get_ssrc(), copied_pkt->header_.get_ssrc());
srs_freep(copied_pkt);
}
// Test nack_no_copy behavior
nack_no_copy = true;
if (nack_no_copy && *ppkt) {
// Should set packet to NULL to avoid copy
SrsRtpPacket *original_pkt = *ppkt;
*ppkt = NULL;
EXPECT_TRUE(*ppkt == NULL);
// Restore for cleanup
*ppkt = original_pkt;
}
srs_freep(pkt);
}
// Test SrsRtcSendTrack::on_recv_nack
VOID TEST(SrsRtcSendTrackTest, OnRecvNack)
{
// Test on_recv_nack logic with lost sequence numbers
std::vector<uint16_t> lost_seqs;
lost_seqs.push_back(100);
lost_seqs.push_back(102);
lost_seqs.push_back(105);
// Simulate processing lost sequence numbers
for (size_t i = 0; i < lost_seqs.size(); ++i) {
uint16_t seq = lost_seqs[i];
// Test sequence number validation
EXPECT_GT(seq, 0);
EXPECT_LT(seq, 65536); // Valid RTP sequence number range
// Simulate packet retrieval (would normally fetch from ring buffer)
bool packet_found = (seq == 100 || seq == 102); // Simulate some packets found
if (seq == 100 || seq == 102) {
EXPECT_TRUE(packet_found);
} else {
EXPECT_FALSE(packet_found);
}
}
EXPECT_EQ(3, lost_seqs.size());
}
// Test SrsRtcAudioSendTrack::on_rtp
VOID TEST(SrsRtcAudioSendTrackTest, OnRtp)
{
// Create test RTP packet for audio
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(100, 1000, 12345));
pkt->header_.set_payload_type(111); // Opus payload type
// Test basic RTP packet processing logic
uint16_t original_seq = pkt->header_.get_sequence();
uint32_t original_ts = pkt->header_.get_timestamp();
uint32_t original_ssrc = pkt->header_.get_ssrc();
// Verify packet properties
EXPECT_EQ(100, original_seq);
EXPECT_EQ(1000, original_ts);
EXPECT_EQ(12345, original_ssrc);
EXPECT_EQ(111, pkt->header_.get_payload_type());
// Test audio-specific processing (simulating what on_rtp would do)
// Audio packets typically have smaller payloads and different timing
bool is_audio_packet = (pkt->header_.get_payload_type() == 111);
EXPECT_TRUE(is_audio_packet);
// Simulate jitter buffer processing for audio
srs_utime_t audio_timestamp = pkt->header_.get_timestamp() * 1000 / 48; // Convert to microseconds for 48kHz
EXPECT_GT(audio_timestamp, 0);
}
// Test SrsRtcAudioSendTrack::on_rtcp
VOID TEST(SrsRtcAudioSendTrackTest, OnRtcp)
{
// Create test RTCP packet (simulated as RTP packet for simplicity)
SrsUniquePtr<SrsRtpPacket> rtcp_pkt(create_test_rtp_packet(0, 0, 12345));
// Test RTCP packet processing logic
uint32_t ssrc = rtcp_pkt->header_.get_ssrc();
EXPECT_EQ(12345, ssrc);
// Simulate RTCP processing (would normally handle SR, RR, NACK, etc.)
bool is_valid_rtcp = (ssrc != 0);
EXPECT_TRUE(is_valid_rtcp);
// Test RTCP feedback processing
std::vector<uint16_t> nack_seqs;
nack_seqs.push_back(98);
nack_seqs.push_back(99);
// Simulate NACK processing
for (size_t i = 0; i < nack_seqs.size(); ++i) {
uint16_t seq = nack_seqs[i];
bool should_retransmit = (seq < 100); // Simulate retransmission logic
EXPECT_TRUE(should_retransmit);
}
}
// Test SrsRtcVideoSendTrack::on_rtp
VOID TEST(SrsRtcVideoSendTrackTest, OnRtp)
{
// Create test RTP packet for video
SrsUniquePtr<SrsRtpPacket> pkt(create_test_rtp_packet(200, 90000, 54321));
pkt->header_.set_payload_type(102); // H.264 payload type
pkt->header_.set_marker(true); // End of frame marker
// Test basic RTP packet processing logic
uint16_t original_seq = pkt->header_.get_sequence();
uint32_t original_ts = pkt->header_.get_timestamp();
uint32_t original_ssrc = pkt->header_.get_ssrc();
bool marker = pkt->header_.get_marker();
// Verify packet properties
EXPECT_EQ(200, original_seq);
EXPECT_EQ(90000, original_ts);
EXPECT_EQ(54321, original_ssrc);
EXPECT_EQ(102, pkt->header_.get_payload_type());
EXPECT_TRUE(marker);
// Test video-specific processing (simulating what on_rtp would do)
bool is_video_packet = (pkt->header_.get_payload_type() == 102);
EXPECT_TRUE(is_video_packet);
// Simulate frame boundary detection
bool is_frame_end = pkt->header_.get_marker();
EXPECT_TRUE(is_frame_end);
// Simulate video timestamp processing (90kHz clock)
srs_utime_t video_timestamp = pkt->header_.get_timestamp() * 1000000LL / 90000; // Convert to microseconds
EXPECT_GT(video_timestamp, 0);
}
// Test SrsRtcVideoSendTrack::on_rtcp
VOID TEST(SrsRtcVideoSendTrackTest, OnRtcp)
{
// Create test RTCP packet (simulated as RTP packet for simplicity)
SrsUniquePtr<SrsRtpPacket> rtcp_pkt(create_test_rtp_packet(0, 0, 54321));
// Test RTCP packet processing logic
uint32_t ssrc = rtcp_pkt->header_.get_ssrc();
EXPECT_EQ(54321, ssrc);
// Simulate video-specific RTCP processing
bool is_valid_rtcp = (ssrc != 0);
EXPECT_TRUE(is_valid_rtcp);
// Test PLI (Picture Loss Indication) processing
bool pli_received = true; // Simulate PLI reception
if (pli_received) {
// Should trigger keyframe request
bool should_request_keyframe = true;
EXPECT_TRUE(should_request_keyframe);
}
// Test NACK processing for video
std::vector<uint16_t> video_nack_seqs;
video_nack_seqs.push_back(198);
video_nack_seqs.push_back(199);
video_nack_seqs.push_back(201);
// Simulate video NACK processing (more complex than audio)
for (size_t i = 0; i < video_nack_seqs.size(); ++i) {
uint16_t seq = video_nack_seqs[i];
bool is_in_range = (seq >= 198 && seq <= 202);
EXPECT_TRUE(is_in_range);
// Simulate packet availability check
bool packet_available = (seq != 199); // Simulate missing packet 199
if (seq == 199) {
EXPECT_FALSE(packet_available);
} else {
EXPECT_TRUE(packet_available);
}
}
}
// Test comprehensive payload type generation
VOID TEST(SrsCodecPayloadTest, ComprehensivePayloadTypes)
{
// Test various codec payload types
struct CodecTest {
uint8_t pt;
std::string name;
int sample_rate;
std::string expected_type;
};
CodecTest tests[] = {
{96, "H264", 90000, "video"},
{97, "H265", 90000, "video"},
{98, "VP8", 90000, "video"},
{99, "VP9", 90000, "video"},
{111, "opus", 48000, "audio"},
{0, "PCMU", 8000, "audio"},
{8, "PCMA", 8000, "audio"},
{9, "G722", 8000, "audio"}};
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) {
SrsUniquePtr<SrsCodecPayload> payload(create_test_codec_payload(tests[i].pt, tests[i].name, tests[i].sample_rate));
SrsMediaPayloadType media_type = payload->generate_media_payload_type();
EXPECT_EQ(tests[i].pt, media_type.payload_type_);
EXPECT_STREQ(tests[i].name.c_str(), media_type.encoding_name_.c_str());
EXPECT_EQ(tests[i].sample_rate, media_type.clock_rate_);
// Test codec ID detection
bool is_video = (tests[i].name == "H264" || tests[i].name == "H265" ||
tests[i].name == "VP8" || tests[i].name == "VP9");
if (is_video) {
int8_t video_codec = payload->codec(true);
EXPECT_GE(video_codec, 0); // Should return valid video codec ID
} else {
int8_t audio_codec = payload->codec(false);
EXPECT_GE(audio_codec, 0); // Should return valid audio codec ID
}
}
}
// Test SrsRtcAudioRecvTrack::on_before_decode_payload method
VOID TEST(SrsRtcAudioRecvTrackTest, OnBeforeDecodePayload)
{
// Create a mock RTC connection and track description for testing
SrsRtcTrackDescription *track_desc = create_test_track_description("audio", 12345);
// Create the audio receive track
MockRtcPacketReceiver mock_receiver;
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc, false);
// Test case 1: Empty buffer - should return early without setting payload
{
SrsRtpPacket pkt;
char empty_data[0];
SrsBuffer empty_buf(empty_data, 0);
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
audio_track.on_before_decode_payload(&pkt, &empty_buf, &payload, &ppt);
// Should not modify payload or payload type for empty buffer
EXPECT_TRUE(payload == NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeUnknown, ppt);
}
// Test case 2: Non-empty buffer - should create SrsRtpRawPayload
{
SrsRtpPacket pkt;
char test_data[64];
memset(test_data, 0xAB, sizeof(test_data)); // Fill with test pattern
SrsBuffer buf(test_data, sizeof(test_data));
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
audio_track.on_before_decode_payload(&pkt, &buf, &payload, &ppt);
// Should create SrsRtpRawPayload and set payload type to Raw
EXPECT_TRUE(payload != NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeRaw, ppt);
// Verify it's actually a SrsRtpRawPayload by trying to cast
SrsRtpRawPayload *raw_payload = dynamic_cast<SrsRtpRawPayload *>(payload);
EXPECT_TRUE(raw_payload != NULL);
// Clean up the created payload
srs_freep(payload);
}
// Test case 3: Buffer with data but at end position - should return early
{
SrsRtpPacket pkt;
char test_data[32];
memset(test_data, 0xCD, sizeof(test_data));
SrsBuffer buf(test_data, sizeof(test_data));
buf.skip(sizeof(test_data)); // Move to end, making buffer empty
ISrsRtpPayloader *payload = NULL;
SrsRtpPacketPayloadType ppt = SrsRtpPacketPayloadTypeUnknown;
audio_track.on_before_decode_payload(&pkt, &buf, &payload, &ppt);
// Should not modify payload or payload type for empty buffer
EXPECT_TRUE(payload == NULL);
EXPECT_EQ(SrsRtpPacketPayloadTypeUnknown, ppt);
}
// Clean up
srs_freep(track_desc);
}
// Test SrsRtcAudioRecvTrack::check_send_nacks basic functionality
VOID TEST(SrsRtcAudioRecvTrackTest, CheckSendNacksWithMock)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("audio", 12345));
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc.get(), false);
// Test case 1: Basic check_send_nacks call - should execute successfully
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
// Test case 2: Multiple calls should also succeed
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(12345, track_desc->ssrc_);
}
// Test SrsRtcVideoRecvTrack::check_send_nacks basic functionality
VOID TEST(SrsRtcVideoRecvTrackTest, CheckSendNacksWithMock)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("video", 54321));
SrsRtcVideoRecvTrack video_track(&mock_receiver, track_desc.get(), false);
// Test case 1: Basic check_send_nacks call - should execute successfully
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
// Test case 2: Multiple calls should also succeed
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(54321, track_desc->ssrc_);
}
// Test SrsRtcRecvTrack::do_check_send_nacks functionality
VOID TEST(SrsRtcRecvTrackTest, DoCheckSendNacksBasic)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("audio", 98765));
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc.get(), false);
// Test case 1: do_check_send_nacks should execute successfully
uint32_t timeout_nacks = 999;
HELPER_EXPECT_SUCCESS(audio_track.do_check_send_nacks(timeout_nacks));
// Test case 2: Multiple calls should also succeed
timeout_nacks = 888;
HELPER_EXPECT_SUCCESS(audio_track.do_check_send_nacks(timeout_nacks));
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(98765, track_desc->ssrc_);
}
// Test SrsRtcAudioRecvTrack::check_send_nacks with multiple calls
VOID TEST(SrsRtcAudioRecvTrackTest, CheckSendNacksMultipleCalls)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("audio", 11111));
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc.get(), false);
// Test multiple consecutive calls - all should succeed
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(11111, track_desc->ssrc_);
}
// Test SrsRtcVideoRecvTrack::check_send_nacks with different scenarios
VOID TEST(SrsRtcVideoRecvTrackTest, CheckSendNacksTimeoutScenarios)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("video", 22222));
SrsRtcVideoRecvTrack video_track(&mock_receiver, track_desc.get(), false);
// Test multiple calls - all should succeed
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(22222, track_desc->ssrc_);
}
// Test SrsRtcRecvTrack::do_check_send_nacks with different SSRC values
VOID TEST(SrsRtcRecvTrackTest, DoCheckSendNacksDifferentSSRC)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
// Test with different SSRC values
uint32_t test_ssrcs[] = {0, 1, 0xFFFFFFFF, 0x12345678, 0xABCDEF00};
int num_tests = sizeof(test_ssrcs) / sizeof(test_ssrcs[0]);
for (int i = 0; i < num_tests; i++) {
uint32_t test_ssrc = test_ssrcs[i];
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("video", test_ssrc));
SrsRtcVideoRecvTrack video_track(&mock_receiver, track_desc.get(), false);
uint32_t timeout_nacks = 999;
HELPER_EXPECT_SUCCESS(video_track.do_check_send_nacks(timeout_nacks));
// Verify the track has proper SSRC
EXPECT_EQ(test_ssrc, track_desc->ssrc_);
}
}
// Test SrsRtcAudioRecvTrack::check_send_nacks with edge case values
VOID TEST(SrsRtcAudioRecvTrackTest, CheckSendNacksEdgeCases)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("audio", 33333));
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc.get(), false);
// Test multiple calls with different scenarios - all should succeed
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(audio_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(33333, track_desc->ssrc_);
}
// Test SrsRtcVideoRecvTrack::check_send_nacks with edge case values
VOID TEST(SrsRtcVideoRecvTrackTest, CheckSendNacksEdgeCases)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc(create_test_track_description("video", 44444));
SrsRtcVideoRecvTrack video_track(&mock_receiver, track_desc.get(), false);
// Test multiple calls with different scenarios - all should succeed
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
HELPER_EXPECT_SUCCESS(video_track.check_send_nacks());
// Verify the track has proper initialization
EXPECT_TRUE(track_desc.get() != NULL);
EXPECT_EQ(44444, track_desc->ssrc_);
}
// Test that both audio and video tracks work correctly
VOID TEST(SrsRtcRecvTrackTest, NackReceiverParameterPassing)
{
srs_error_t err;
MockRtcPacketReceiver mock_receiver;
SrsUniquePtr<SrsRtcTrackDescription> track_desc1(create_test_track_description("audio", 55555));
SrsUniquePtr<SrsRtcTrackDescription> track_desc2(create_test_track_description("video", 66666));
SrsRtcAudioRecvTrack audio_track(&mock_receiver, track_desc1.get(), false);
SrsRtcVideoRecvTrack video_track(&mock_receiver, track_desc2.get(), false);
// Test audio track functionality
uint32_t timeout_nacks = 0;
HELPER_EXPECT_SUCCESS(audio_track.do_check_send_nacks(timeout_nacks));
// Test video track functionality
timeout_nacks = 0;
HELPER_EXPECT_SUCCESS(video_track.do_check_send_nacks(timeout_nacks));
// Verify both tracks have proper initialization
EXPECT_TRUE(track_desc1.get() != NULL);
EXPECT_EQ(55555, track_desc1->ssrc_);
EXPECT_TRUE(track_desc2.get() != NULL);
EXPECT_EQ(66666, track_desc2->ssrc_);
}