srs/trunk/src/utest/srs_utest_app12.cpp
2025-10-07 10:10:58 -04:00

3545 lines
133 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_app12.hpp>
using namespace std;
#include <srs_app_rtc_source.hpp>
#include <srs_app_rtsp_source.hpp>
#include <srs_app_srt_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_ts.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_utest_app2.hpp>
#include <srs_utest_app3.hpp>
#include <srs_utest_app5.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_config.hpp>
#include <srs_utest_coworkers.hpp>
// Mock frame target implementation
MockSrtFrameTarget::MockSrtFrameTarget()
{
on_frame_count_ = 0;
last_frame_ = NULL;
frame_error_ = srs_success;
}
MockSrtFrameTarget::~MockSrtFrameTarget()
{
srs_freep(last_frame_);
srs_freep(frame_error_);
}
srs_error_t MockSrtFrameTarget::on_frame(SrsMediaPacket *frame)
{
on_frame_count_++;
// Store a copy of the frame for verification
srs_freep(last_frame_);
if (frame) {
last_frame_ = frame->copy();
}
return srs_error_copy(frame_error_);
}
void MockSrtFrameTarget::reset()
{
on_frame_count_ = 0;
srs_freep(last_frame_);
srs_freep(frame_error_);
}
void MockSrtFrameTarget::set_frame_error(srs_error_t err)
{
srs_freep(frame_error_);
frame_error_ = srs_error_copy(err);
}
// Test SrsSrtPacket wrap and copy functionality
// This test covers the major use scenario: wrapping data and copying packets
VOID TEST(SrsSrtPacketTest, WrapDataAndCopy)
{
// Create an SRT packet
SrsUniquePtr<SrsSrtPacket> pkt(new SrsSrtPacket());
// Test wrap(char *data, int size) - wraps raw data into packet
const char *test_data = "Hello SRT";
int data_size = strlen(test_data);
char *wrapped_buf = pkt->wrap((char *)test_data, data_size);
// Verify the wrapped data
EXPECT_TRUE(wrapped_buf != NULL);
EXPECT_EQ(data_size, pkt->size());
EXPECT_EQ(0, memcmp(wrapped_buf, test_data, data_size));
// Test copy() - creates a copy of the packet
SrsUniquePtr<SrsSrtPacket> copied_pkt(pkt->copy());
// Verify the copied packet has the same data
EXPECT_TRUE(copied_pkt.get() != NULL);
EXPECT_EQ(pkt->size(), copied_pkt->size());
EXPECT_EQ(0, memcmp(pkt->data(), copied_pkt->data(), pkt->size()));
// Test wrap(SrsMediaPacket *msg) - wraps a media packet (RTMP to SRT scenario)
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
const char *media_data = "Media Packet Data";
int media_size = strlen(media_data);
char *media_buf = new char[media_size];
memcpy(media_buf, media_data, media_size);
msg->wrap(media_buf, media_size);
msg->timestamp_ = 12345;
msg->message_type_ = SrsFrameTypeVideo;
// Wrap the media packet into SRT packet
SrsUniquePtr<SrsSrtPacket> pkt2(new SrsSrtPacket());
char *wrapped_msg_buf = pkt2->wrap(msg.get());
// Verify the wrapped message
EXPECT_TRUE(wrapped_msg_buf != NULL);
EXPECT_EQ(media_size, pkt2->size());
EXPECT_EQ(0, memcmp(wrapped_msg_buf, media_data, media_size));
// Copy the packet with wrapped message
SrsUniquePtr<SrsSrtPacket> copied_pkt2(pkt2->copy());
// Verify the copied packet
EXPECT_TRUE(copied_pkt2.get() != NULL);
EXPECT_EQ(pkt2->size(), copied_pkt2->size());
EXPECT_EQ(0, memcmp(pkt2->data(), copied_pkt2->data(), pkt2->size()));
}
// Test SrsSrtConsumer update_source_id and enqueue functionality
// This test covers the major use scenario: updating source ID flag and enqueueing packets
VOID TEST(SrsSrtConsumerTest, UpdateSourceIdAndEnqueue)
{
srs_error_t err;
// Create a mock SRT source
MockSrtSource mock_source;
// Create an SRT consumer with the mock source
SrsUniquePtr<SrsSrtConsumer> consumer(new SrsSrtConsumer(&mock_source));
// Test update_source_id() - should set the flag
consumer->update_source_id();
// Test enqueue() - add packets to the queue
// Create first SRT packet (consumer takes ownership)
SrsSrtPacket *pkt1 = new SrsSrtPacket();
const char *data1 = "Test SRT Packet 1";
pkt1->wrap((char *)data1, strlen(data1));
// Enqueue first packet (consumer takes ownership)
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1));
// Create second SRT packet (consumer takes ownership)
SrsSrtPacket *pkt2 = new SrsSrtPacket();
const char *data2 = "Test SRT Packet 2";
pkt2->wrap((char *)data2, strlen(data2));
// Enqueue second packet (consumer takes ownership)
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2));
// Dump packets to verify they were enqueued
SrsSrtPacket *dumped_pkt1 = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt1));
EXPECT_TRUE(dumped_pkt1 != NULL);
EXPECT_EQ(strlen(data1), (size_t)dumped_pkt1->size());
EXPECT_EQ(0, memcmp(dumped_pkt1->data(), data1, strlen(data1)));
srs_freep(dumped_pkt1);
SrsSrtPacket *dumped_pkt2 = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt2));
EXPECT_TRUE(dumped_pkt2 != NULL);
EXPECT_EQ(strlen(data2), (size_t)dumped_pkt2->size());
EXPECT_EQ(0, memcmp(dumped_pkt2->data(), data2, strlen(data2)));
srs_freep(dumped_pkt2);
// Verify queue is now empty
SrsSrtPacket *dumped_pkt3 = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt3));
EXPECT_TRUE(dumped_pkt3 == NULL);
}
// Test SrsSrtConsumer dump_packet functionality
// This test covers the major use scenario: dumping packets from queue with source ID update
VOID TEST(SrsSrtConsumerTest, DumpPacket)
{
srs_error_t err;
// Create a mock SRT source
MockSrtSource mock_source;
// Create an SRT consumer with the mock source
SrsUniquePtr<SrsSrtConsumer> consumer(new SrsSrtConsumer(&mock_source));
// Enqueue a packet first
SrsSrtPacket *pkt = new SrsSrtPacket();
const char *data = "Test SRT Data";
pkt->wrap((char *)data, strlen(data));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt));
// Trigger source ID update
consumer->update_source_id();
// Dump packet - this should update source_id flag and return the packet
SrsSrtPacket *dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
// Verify the packet was dumped correctly
EXPECT_TRUE(dumped_pkt != NULL);
EXPECT_EQ(strlen(data), (size_t)dumped_pkt->size());
EXPECT_EQ(0, memcmp(dumped_pkt->data(), data, strlen(data)));
srs_freep(dumped_pkt);
// Dump again from empty queue - should return NULL
SrsSrtPacket *empty_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&empty_pkt));
EXPECT_TRUE(empty_pkt == NULL);
}
// Test SrsSrtConsumer wait functionality
// This test covers the major use scenario: waiting for messages and being signaled when enough messages arrive
VOID TEST(SrsSrtConsumerTest, WaitForMessages)
{
srs_error_t err;
// Create a mock SRT source
MockSrtSource mock_source;
// Create an SRT consumer with the mock source
SrsUniquePtr<SrsSrtConsumer> consumer(new SrsSrtConsumer(&mock_source));
// Scenario 1: Queue already has enough messages - wait() should return immediately
// Enqueue 3 packets first
for (int i = 0; i < 3; i++) {
SrsSrtPacket *pkt = new SrsSrtPacket();
char data[32];
snprintf(data, sizeof(data), "Packet %d", i);
pkt->wrap(data, strlen(data));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt));
}
// Wait for 2 messages - should return immediately since queue has 3 packets (3 > 2)
srs_utime_t start_time = srs_time_now_realtime();
consumer->wait(2, 1 * SRS_UTIME_MILLISECONDS);
srs_utime_t elapsed = srs_time_now_realtime() - start_time;
// Should return immediately (elapsed time should be very small, less than 10ms)
EXPECT_LT(elapsed, 1 * SRS_UTIME_MILLISECONDS);
// Clean up the queue
for (int i = 0; i < 3; i++) {
SrsSrtPacket *pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&pkt));
srs_freep(pkt);
}
// Scenario 2: Queue doesn't have enough messages - wait() should timeout
// Enqueue only 1 packet
SrsSrtPacket *pkt1 = new SrsSrtPacket();
const char *data1 = "Single Packet";
pkt1->wrap((char *)data1, strlen(data1));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1));
// Wait for 2 messages with 50ms timeout - should timeout since queue only has 1 packet (1 <= 2)
start_time = srs_time_now_realtime();
consumer->wait(2, 10 * SRS_UTIME_MILLISECONDS);
elapsed = srs_time_now_realtime() - start_time;
// Should wait for approximately the timeout duration (allow 20ms tolerance due to system scheduling)
EXPECT_GE(elapsed, 0.1 * SRS_UTIME_MILLISECONDS);
EXPECT_LT(elapsed, 50 * SRS_UTIME_MILLISECONDS);
// Clean up
SrsSrtPacket *pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&pkt));
srs_freep(pkt);
}
// Test SrsSrtFrameBuilder::on_ts_message functionality
// This test covers the major use scenario: processing H.264 video TS message
VOID TEST(SrsSrtFrameBuilderTest, OnTsMessageH264Video)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS channel for H.264 video
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyVideo;
channel->stream_ = SrsTsStreamVideoH264;
// Create a TS message with H.264 video data (no packet needed for this test)
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdVideoCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase
msg->pts_ = 90000;
// Create simple H.264 NAL unit data (IDR frame with SPS/PPS)
// Format: [4-byte length][NAL unit data]
// SPS NAL (0x67)
uint8_t sps_data[] = {0x67, 0x42, 0x00, 0x1e, 0x8d, 0x8d, 0x40, 0x50};
// PPS NAL (0x68)
uint8_t pps_data[] = {0x68, 0xce, 0x3c, 0x80};
// IDR NAL (0x65)
uint8_t idr_data[] = {0x65, 0x88, 0x84, 0x00, 0x10};
// Build AnnexB format: start_code + NAL
int total_size = 4 + sizeof(sps_data) + 4 + sizeof(pps_data) + 4 + sizeof(idr_data);
char *payload = new char[total_size];
SrsBuffer stream(payload, total_size);
// Write SPS with start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)sps_data, sizeof(sps_data));
// Write PPS with start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)pps_data, sizeof(pps_data));
// Write IDR with start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)idr_data, sizeof(idr_data));
// Wrap payload into message using SrsSimpleStream
msg->payload_ = new SrsSimpleStream();
msg->payload_->append(payload, total_size);
// Call on_ts_message - this is the method under test
HELPER_EXPECT_SUCCESS(builder->on_ts_message(msg.get()));
// Verify that frame was delivered to target
// Should have at least 1 frame (sequence header + video frame)
EXPECT_GT(mock_target.on_frame_count_, 0);
}
// Test SrsSrtFrameBuilder::on_ts_video_avc functionality
// This test covers the major use scenario: demuxing H.264 annexb data with SPS/PPS/IDR frames
VOID TEST(SrsSrtFrameBuilderTest, OnTsVideoAvc)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS message with H.264 video data
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyVideo;
channel->stream_ = SrsTsStreamVideoH264;
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdVideoCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase
msg->pts_ = 90000;
// Create H.264 annexb format data with SPS, PPS, and IDR frame
// SPS NAL (0x67 = type 7)
uint8_t sps_data[] = {0x67, 0x42, 0x00, 0x1e, 0x8d, 0x8d, 0x40, 0x50};
// PPS NAL (0x68 = type 8)
uint8_t pps_data[] = {0x68, 0xce, 0x3c, 0x80};
// IDR NAL (0x65 = type 5)
uint8_t idr_data[] = {0x65, 0x88, 0x84, 0x00, 0x10};
// Build annexb format: start_code (0x00000001) + NAL unit
int total_size = 4 + sizeof(sps_data) + 4 + sizeof(pps_data) + 4 + sizeof(idr_data);
char *payload = new char[total_size];
SrsBuffer stream(payload, total_size);
// Write SPS with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)sps_data, sizeof(sps_data));
// Write PPS with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)pps_data, sizeof(pps_data));
// Write IDR with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)idr_data, sizeof(idr_data));
// Create SrsBuffer for on_ts_video_avc to read from
SrsBuffer avs(payload, total_size);
// Call on_ts_video_avc - this is the method under test
HELPER_EXPECT_SUCCESS(builder->on_ts_video_avc(msg.get(), &avs));
// Verify that frames were delivered to target
// Should have 2 frames: sequence header (from SPS/PPS change) + video frame (IDR)
EXPECT_EQ(2, mock_target.on_frame_count_);
// Verify the last frame is not NULL
EXPECT_TRUE(mock_target.last_frame_ != NULL);
// Verify the last frame is a video frame
if (mock_target.last_frame_) {
EXPECT_EQ(SrsFrameTypeVideo, mock_target.last_frame_->message_type_);
}
// Clean up payload
delete[] payload;
}
// Test SrsSrtFrameBuilder::on_ts_video_hevc functionality
// This test covers the major use scenario: demuxing HEVC annexb data with VPS/SPS/PPS/IDR frames
VOID TEST(SrsSrtFrameBuilderTest, OnTsVideoHevc)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS message with HEVC video data
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyVideo;
channel->stream_ = SrsTsStreamVideoHEVC;
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdVideoCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase
msg->pts_ = 90000;
// Create HEVC annexb format data with VPS, SPS, PPS, and IDR frame
// VPS NAL (0x40 = type 32)
uint8_t vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0x95, 0x98, 0x09};
// SPS NAL (0x42 = type 33)
uint8_t sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x40, 0x40, 0x00, 0x00, 0xfa, 0x40, 0x00, 0x17, 0x70, 0x02};
// PPS NAL (0x44 = type 34)
uint8_t pps_data[] = {0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40};
// IDR NAL (0x26 = type 19, IDR_W_RADL)
uint8_t idr_data[] = {0x26, 0x01, 0xaf, 0x06, 0xb8, 0x63, 0xef, 0x3a, 0x7f, 0x3c, 0x00, 0x01, 0x00, 0x80};
// Build annexb format: start_code (0x00000001) + NAL unit
int total_size = 4 + sizeof(vps_data) + 4 + sizeof(sps_data) + 4 + sizeof(pps_data) + 4 + sizeof(idr_data);
char *payload = new char[total_size];
SrsBuffer stream(payload, total_size);
// Write VPS with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)vps_data, sizeof(vps_data));
// Write SPS with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)sps_data, sizeof(sps_data));
// Write PPS with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)pps_data, sizeof(pps_data));
// Write IDR with 4-byte start code
stream.write_4bytes(0x00000001);
stream.write_bytes((char *)idr_data, sizeof(idr_data));
// Create SrsBuffer for on_ts_video_hevc to read from
SrsBuffer avs(payload, total_size);
// Call on_ts_video_hevc - this is the method under test
HELPER_EXPECT_SUCCESS(builder->on_ts_video_hevc(msg.get(), &avs));
// Verify that frames were delivered to target
// Should have 2 frames: sequence header (from VPS/SPS/PPS change) + video frame (IDR)
EXPECT_EQ(2, mock_target.on_frame_count_);
// Verify the last frame is not NULL
EXPECT_TRUE(mock_target.last_frame_ != NULL);
// Verify the last frame is a video frame
if (mock_target.last_frame_) {
EXPECT_EQ(SrsFrameTypeVideo, mock_target.last_frame_->message_type_);
}
// Clean up payload
delete[] payload;
}
// Test SrsSrtFrameBuilder::check_sps_pps_change functionality
// This test covers the major use scenario: generating video sequence header when SPS/PPS change
VOID TEST(SrsSrtFrameBuilderTest, CheckSpsPpsChange)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a TsMessage with valid timestamp
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage());
msg->dts_ = 90000; // 1 second in 90kHz timebase (will be converted to 1000ms)
msg->pts_ = 90000;
// Set up SPS and PPS data in the builder
// Use simple but valid SPS/PPS data
uint8_t sps_data[] = {0x67, 0x42, 0x00, 0x1e, 0x8d, 0x8d, 0x40, 0x50};
uint8_t pps_data[] = {0x68, 0xce, 0x3c, 0x80};
// Access private members to set up the test scenario
builder->sps_ = std::string((char *)sps_data, sizeof(sps_data));
builder->pps_ = std::string((char *)pps_data, sizeof(pps_data));
builder->sps_pps_change_ = true; // Simulate SPS/PPS change detected
// Call check_sps_pps_change - this should generate and send a sequence header frame
HELPER_EXPECT_SUCCESS(builder->check_sps_pps_change(msg.get()));
// Verify that a frame was delivered to target (the sequence header)
EXPECT_EQ(1, mock_target.on_frame_count_);
// Verify the frame is not NULL
EXPECT_TRUE(mock_target.last_frame_ != NULL);
// Verify the frame is a video frame
if (mock_target.last_frame_) {
EXPECT_EQ(SrsFrameTypeVideo, mock_target.last_frame_->message_type_);
// Verify timestamp was converted correctly (90000 / 90 = 1000ms)
EXPECT_EQ(1000, (int)mock_target.last_frame_->timestamp_);
}
// Verify that sps_pps_change_ flag was reset to false
EXPECT_FALSE(builder->sps_pps_change_);
}
// Test SrsSrtFrameBuilder::on_h264_frame functionality
// This test covers the major use scenario: converting H.264 NAL units to RTMP video frame
VOID TEST(SrsSrtFrameBuilderTest, OnH264Frame)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS message with H.264 video timing information
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage());
msg->dts_ = 90000; // 1 second in 90kHz timebase (will be converted to 1000ms)
msg->pts_ = 99000; // 1.1 seconds in 90kHz timebase (will be converted to 1100ms, CTS=100ms)
// Create H.264 NAL units for an IDR frame
// IDR NAL (0x65 = type 5, keyframe)
uint8_t idr_nal[] = {0x65, 0x88, 0x84, 0x00, 0x10, 0x20, 0x30, 0x40};
// Non-IDR NAL (0x41 = type 1, inter frame)
uint8_t non_idr_nal[] = {0x41, 0x9a, 0x12, 0x34};
// Build ipb_frames vector with NAL units
vector<pair<char *, int> > ipb_frames;
ipb_frames.push_back(make_pair((char *)idr_nal, sizeof(idr_nal)));
ipb_frames.push_back(make_pair((char *)non_idr_nal, sizeof(non_idr_nal)));
// Call on_h264_frame - should convert TS message to RTMP video frame
HELPER_EXPECT_SUCCESS(builder->on_h264_frame(msg.get(), ipb_frames));
// Verify that on_frame was called once
EXPECT_EQ(1, mock_target.on_frame_count_);
// Verify the frame is not NULL
EXPECT_TRUE(mock_target.last_frame_ != NULL);
if (mock_target.last_frame_) {
// Verify the frame is a video frame
EXPECT_EQ(SrsFrameTypeVideo, mock_target.last_frame_->message_type_);
// Verify timestamp was converted correctly (90000 / 90 = 1000ms)
EXPECT_EQ(1000, (int)mock_target.last_frame_->timestamp_);
// Verify the payload structure
// Expected structure: 5-byte video tag header + (4-byte length + NAL data) for each NAL
// 5 + (4 + 8) + (4 + 4) = 5 + 12 + 8 = 25 bytes
int expected_size = 5 + (4 + sizeof(idr_nal)) + (4 + sizeof(non_idr_nal));
EXPECT_EQ(expected_size, mock_target.last_frame_->size());
// Verify the video tag header
SrsBuffer payload(mock_target.last_frame_->payload(), mock_target.last_frame_->size());
// First byte: 0x17 for keyframe (type=1, codec=7 for AVC)
uint8_t frame_type_codec = payload.read_1bytes();
EXPECT_EQ(0x17, frame_type_codec);
// Second byte: 0x01 for AVC NALU
uint8_t avc_packet_type = payload.read_1bytes();
EXPECT_EQ(0x01, avc_packet_type);
// Next 3 bytes: composition time (CTS = PTS - DTS = 1100 - 1000 = 100ms)
int32_t cts = payload.read_3bytes();
EXPECT_EQ(100, cts);
// Verify first NAL unit (IDR)
int32_t nal1_size = payload.read_4bytes();
EXPECT_EQ((int)sizeof(idr_nal), nal1_size);
char nal1_data[sizeof(idr_nal)];
payload.read_bytes(nal1_data, sizeof(idr_nal));
EXPECT_EQ(0, memcmp(nal1_data, idr_nal, sizeof(idr_nal)));
// Verify second NAL unit (non-IDR)
int32_t nal2_size = payload.read_4bytes();
EXPECT_EQ((int)sizeof(non_idr_nal), nal2_size);
char nal2_data[sizeof(non_idr_nal)];
payload.read_bytes(nal2_data, sizeof(non_idr_nal));
EXPECT_EQ(0, memcmp(nal2_data, non_idr_nal, sizeof(non_idr_nal)));
}
}
// Test SrsSrtFrameBuilder check_vps_sps_pps_change functionality
// This test covers the major use scenario: generating HEVC sequence header when VPS/SPS/PPS change
VOID TEST(SrsSrtFrameBuilderTest, CheckVpsSppsPpsChange)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock TsMessage with valid DTS/PTS (in 90kHz timebase)
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage());
msg->dts_ = 90000; // 1 second in 90kHz
msg->pts_ = 90000; // 1 second in 90kHz
// Valid HEVC VPS/SPS/PPS data (same as used in OnTsVideoHevc test)
// VPS NAL (0x40 = type 32)
uint8_t vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0x95, 0x98, 0x09};
std::string vps((char *)vps_data, sizeof(vps_data));
// SPS NAL (0x42 = type 33)
uint8_t sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x40, 0x40, 0x00, 0x00, 0xfa, 0x40, 0x00, 0x17, 0x70, 0x02};
std::string sps((char *)sps_data, sizeof(sps_data));
// PPS NAL (0x44 = type 34)
uint8_t pps_data[] = {0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40};
std::string pps((char *)pps_data, sizeof(pps_data));
// Set the HEVC VPS/SPS/PPS in the builder (accessing private members via test macro)
builder->vps_sps_pps_change_ = true;
builder->hevc_vps_ = vps;
builder->hevc_sps_ = sps;
builder->hevc_pps_.clear();
builder->hevc_pps_.push_back(pps);
// Call check_vps_sps_pps_change - should generate sequence header and call on_frame
HELPER_EXPECT_SUCCESS(builder->check_vps_sps_pps_change(msg.get()));
// Verify that on_frame was called once
EXPECT_EQ(1, mock_target.on_frame_count_);
// Verify that vps_sps_pps_change_ flag was reset to false
EXPECT_FALSE(builder->vps_sps_pps_change_);
// Verify the frame was generated correctly
EXPECT_TRUE(mock_target.last_frame_ != NULL);
if (mock_target.last_frame_) {
// Verify it's a video frame
EXPECT_EQ(SrsFrameTypeVideo, mock_target.last_frame_->message_type_);
// Verify timestamp conversion from 90kHz to milliseconds (90000 / 90 = 1000ms)
EXPECT_EQ(1000u, (uint32_t)mock_target.last_frame_->timestamp_);
}
// Test scenario 2: vps_sps_pps_change_ is false - should return immediately without calling on_frame
mock_target.reset();
builder->vps_sps_pps_change_ = false;
HELPER_EXPECT_SUCCESS(builder->check_vps_sps_pps_change(msg.get()));
// Verify on_frame was NOT called
EXPECT_EQ(0, mock_target.on_frame_count_);
// Test scenario 3: vps_sps_pps_change_ is true but VPS is empty - should return without calling on_frame
mock_target.reset();
builder->vps_sps_pps_change_ = true;
builder->hevc_vps_ = ""; // Empty VPS
HELPER_EXPECT_SUCCESS(builder->check_vps_sps_pps_change(msg.get()));
// Verify on_frame was NOT called
EXPECT_EQ(0, mock_target.on_frame_count_);
}
// Test SrsSrtFrameBuilder::on_hevc_frame functionality
// This test covers the major use scenario: processing HEVC video frame with multiple NALUs including IDR frame
VOID TEST(SrsSrtFrameBuilderTest, OnHevcFrameWithIDR)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS channel for HEVC video
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyVideo;
channel->stream_ = SrsTsStreamVideoHEVC;
// Create a TS message with HEVC video data
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdVideoCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase (will be converted to 1000ms in FLV)
msg->pts_ = 90000;
// Create HEVC NAL units for testing
// VPS NAL (type 32, 0x40 in first byte: (32 << 1) = 0x40)
uint8_t vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60};
// SPS NAL (type 33, 0x42 in first byte: (33 << 1) = 0x42)
uint8_t sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03};
// PPS NAL (type 34, 0x44 in first byte: (34 << 1) = 0x44)
uint8_t pps_data[] = {0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89};
// IDR NAL (type 19, 0x26 in first byte: (19 << 1) = 0x26) - this is an IRAP frame
uint8_t idr_data[] = {0x26, 0x01, 0xaf, 0x08, 0x40, 0x00, 0x00, 0x10};
// Build ipb_frames vector with NAL units
std::vector<std::pair<char *, int> > ipb_frames;
ipb_frames.push_back(std::make_pair((char *)vps_data, sizeof(vps_data)));
ipb_frames.push_back(std::make_pair((char *)sps_data, sizeof(sps_data)));
ipb_frames.push_back(std::make_pair((char *)pps_data, sizeof(pps_data)));
ipb_frames.push_back(std::make_pair((char *)idr_data, sizeof(idr_data)));
// Call on_hevc_frame
HELPER_EXPECT_SUCCESS(builder->on_hevc_frame(msg.get(), ipb_frames));
// Verify the frame was delivered to the target
EXPECT_EQ(1, mock_target.on_frame_count_);
EXPECT_TRUE(mock_target.last_frame_ != NULL);
// Verify the frame properties
SrsMediaPacket *frame = mock_target.last_frame_;
EXPECT_TRUE(frame->payload() != NULL);
EXPECT_GT(frame->size(), 0);
// Expected frame size: 5 bytes header + (4 + vps_size) + (4 + sps_size) + (4 + pps_size) + (4 + idr_size)
int expected_size = 5 + (4 + sizeof(vps_data)) + (4 + sizeof(sps_data)) + (4 + sizeof(pps_data)) + (4 + sizeof(idr_data));
EXPECT_EQ(expected_size, frame->size());
// Verify the timestamp (90000 / 90 = 1000ms)
EXPECT_EQ(1000, frame->timestamp_);
// Verify the message type is video
EXPECT_EQ(SrsFrameTypeVideo, frame->message_type_);
// Verify the enhanced RTMP header format
SrsUniquePtr<SrsBuffer> buffer(new SrsBuffer(frame->payload(), frame->size()));
// Read and verify the 5-byte video tag header
uint8_t header_byte = buffer->read_1bytes();
// Check SRS_FLV_IS_EX_HEADER bit is set (0x80)
EXPECT_TRUE((header_byte & 0x80) != 0);
// Check frame type is keyframe (1 << 4 = 0x10, shifted to bits 4-6)
uint8_t frame_type = (header_byte >> 4) & 0x07;
EXPECT_EQ(SrsVideoAvcFrameTypeKeyFrame, frame_type);
// Check packet type is CodedFramesX (3, in bits 0-3)
uint8_t packet_type = header_byte & 0x0f;
EXPECT_EQ(SrsVideoHEVCFrameTraitPacketTypeCodedFramesX, packet_type);
// Verify HEVC fourcc 'hvc1'
uint32_t fourcc = buffer->read_4bytes();
EXPECT_EQ(0x68766331, fourcc); // 'h' 'v' 'c' '1'
// Verify NAL units are written correctly with 4-byte length prefix
// VPS
uint32_t vps_length = buffer->read_4bytes();
EXPECT_EQ(sizeof(vps_data), vps_length);
EXPECT_EQ(0, memcmp(buffer->data() + buffer->pos(), vps_data, sizeof(vps_data)));
buffer->skip(sizeof(vps_data));
// SPS
uint32_t sps_length = buffer->read_4bytes();
EXPECT_EQ(sizeof(sps_data), sps_length);
EXPECT_EQ(0, memcmp(buffer->data() + buffer->pos(), sps_data, sizeof(sps_data)));
buffer->skip(sizeof(sps_data));
// PPS
uint32_t pps_length = buffer->read_4bytes();
EXPECT_EQ(sizeof(pps_data), pps_length);
EXPECT_EQ(0, memcmp(buffer->data() + buffer->pos(), pps_data, sizeof(pps_data)));
buffer->skip(sizeof(pps_data));
// IDR
uint32_t idr_length = buffer->read_4bytes();
EXPECT_EQ(sizeof(idr_data), idr_length);
EXPECT_EQ(0, memcmp(buffer->data() + buffer->pos(), idr_data, sizeof(idr_data)));
}
// Test SrsSrtFrameBuilder::on_ts_audio functionality
// This test covers the major use scenario: processing AAC audio TS message with ADTS format
VOID TEST(SrsSrtFrameBuilderTest, OnTsAudioAAC)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Create a TS channel for AAC audio
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyAudio;
channel->stream_ = SrsTsStreamAudioAAC;
// Create ADTS AAC frame data first (before creating SrsTsMessage)
// ADTS header format (7 bytes for protection_absent=1):
// Based on working example from srs_utest_avc.cpp
// Frame format: 0xff 0xf9 0x50 0x80 0x01 0x3f 0xfc [payload]
// - syncword: 0xfff (12 bits)
// - ID: 1 (MPEG-2 AAC)
// - protection_absent: 1
// - profile: 01 (AAC-LC)
// - sampling_frequency_index: 0100 (44.1kHz, index 4)
// - channel_configuration: 010 (stereo)
// - frame_length: 10 bytes (7 header + 3 payload)
// Build ADTS frame: 44.1kHz, stereo, AAC-LC, 10 bytes total (7 header + 3 payload)
// frame_length = 10 = 0b0000000001010 (13 bits)
// Bit layout: bits[12-11]=00, bits[10-3]=00000001, bits[2-0]=010
uint8_t adts_frame[] = {
0xff, 0xf9, // syncword(0xfff) + ID(1) + layer(0) + protection_absent(1)
0x50, // profile(01=AAC-LC) + sampling_frequency_index(0100=44.1kHz) + private_bit(0) + channel_config high bit(0)
0x80, // channel_config low(10=stereo) + original_copy(0) + home(0) + copyright bits(00) + frame_length bits[12-11](00)
0x01, // frame_length bits[10-3] (00000001)
0x5f, // frame_length bits[2-0](010) + adts_buffer_fullness high 5 bits(11111)
0xfc, // adts_buffer_fullness low 6 bits(111111) + number_of_raw_data_blocks(00)
0xaa, 0xbb, 0xcc // 3 bytes AAC raw data payload
};
int payload_size = sizeof(adts_frame);
char *payload = new char[payload_size];
memcpy(payload, adts_frame, payload_size);
// Create a TS message with AAC audio data
// Set up the payload in SrsSimpleStream
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdAudioCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase
msg->pts_ = 90000;
// Append payload to the message's payload stream
msg->payload_->append(payload, payload_size);
// Call on_ts_message to process the AAC audio data (which internally calls on_ts_audio)
HELPER_EXPECT_SUCCESS(builder->on_ts_message(msg.get()));
// Verify that frames were sent to target
// Should have 2 frames: 1 audio sequence header + 1 audio frame
EXPECT_EQ(2, mock_target.on_frame_count_);
EXPECT_TRUE(mock_target.last_frame_ != NULL);
EXPECT_EQ(SrsFrameTypeAudio, mock_target.last_frame_->message_type_);
// Verify the timestamp conversion from TS timebase (90kHz) to FLV timebase (1kHz)
// pts = 90000 / 90 = 1000ms
EXPECT_EQ(1000, (int)mock_target.last_frame_->timestamp_);
srs_freepa(payload);
}
// Test SrsSrtFrameBuilder::check_audio_sh_change functionality
// This test covers the major use scenario: dispatching audio sequence header when audio config changes
VOID TEST(SrsSrtFrameBuilderTest, CheckAudioShChange)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder with mock target
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock request for initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Set up audio sequence header change scenario
// Simulate that audio_sh_ has been populated and audio_sh_change_ flag is set
// This happens when audio specific config changes during stream processing
builder->audio_sh_change_ = true;
// Create a sample AAC audio specific config (2 bytes)
// Format: profile(5 bits) + sampling_frequency_index(4 bits) + channel_configuration(4 bits) + other(3 bits)
// AAC-LC (profile=2), 44.1kHz (index=4), stereo (channels=2)
// Binary: 00010 0100 0010 000 = 0x1210
uint8_t asc_data[] = {0x12, 0x10};
builder->audio_sh_.assign((char *)asc_data, sizeof(asc_data));
// Create a TS channel for AAC audio
SrsUniquePtr<SrsTsChannel> channel(new SrsTsChannel());
channel->apply_ = SrsTsPidApplyAudio;
channel->stream_ = SrsTsStreamAudioAAC;
// Create a TS message (the actual message content doesn't matter for this test)
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage(channel.get(), NULL));
msg->sid_ = SrsTsPESStreamIdAudioCommon;
msg->dts_ = 90000; // 1 second in 90kHz timebase
msg->pts_ = 90000;
uint32_t pts = 1000; // 1000ms in FLV timebase
// Call check_audio_sh_change to dispatch the audio sequence header
HELPER_EXPECT_SUCCESS(builder->check_audio_sh_change(msg.get(), pts));
// Verify that audio sequence header frame was dispatched
EXPECT_EQ(1, mock_target.on_frame_count_);
EXPECT_TRUE(mock_target.last_frame_ != NULL);
EXPECT_EQ(SrsFrameTypeAudio, mock_target.last_frame_->message_type_);
EXPECT_EQ(pts, mock_target.last_frame_->timestamp_);
// Verify the audio sequence header format
SrsUniquePtr<SrsBuffer> buffer(new SrsBuffer(mock_target.last_frame_->payload(), mock_target.last_frame_->size()));
// First byte: AAC codec flag
// Format: codec(4 bits) + sample_rate(2 bits) + sample_bits(1 bit) + channels(1 bit)
// AAC(10) + 44.1kHz(3) + 16bit(1) + stereo(1) = 0xAF
uint8_t aac_flag = buffer->read_1bytes();
EXPECT_EQ(0xAF, aac_flag);
// Second byte: AAC packet type (0 = sequence header)
uint8_t packet_type = buffer->read_1bytes();
EXPECT_EQ(0, packet_type);
// Remaining bytes: audio specific config
EXPECT_EQ(sizeof(asc_data), (size_t)buffer->left());
EXPECT_EQ(0, memcmp(buffer->data() + buffer->pos(), asc_data, sizeof(asc_data)));
// Verify that audio_sh_change_ flag was reset to false
EXPECT_FALSE(builder->audio_sh_change_);
// Test that calling check_audio_sh_change again does nothing (flag is false)
mock_target.reset();
HELPER_EXPECT_SUCCESS(builder->check_audio_sh_change(msg.get(), pts));
EXPECT_EQ(0, mock_target.on_frame_count_);
}
// Test SrsSrtFrameBuilder::on_aac_frame - converts AAC frame from TS to RTMP format
// This test covers the major use scenario: converting AAC audio data to RTMP format
VOID TEST(SrsSrtFrameBuilderTest, OnAacFrame)
{
srs_error_t err;
// Create mock frame target
MockSrtFrameTarget mock_target;
// Create SrsSrtFrameBuilder
SrsUniquePtr<SrsSrtFrameBuilder> builder(new SrsSrtFrameBuilder(&mock_target));
// Create a mock SrsTsMessage (only used for context, not directly accessed in on_aac_frame)
SrsUniquePtr<SrsTsMessage> msg(new SrsTsMessage());
// Create test AAC frame data (simulating raw AAC data)
const char *aac_data = "AAC_FRAME_DATA_TEST";
int data_size = strlen(aac_data);
char *frame_data = new char[data_size];
memcpy(frame_data, aac_data, data_size);
// Set PTS (presentation timestamp) - convert from ms to 90kHz timebase for TS
uint32_t pts = 1000; // 1000ms in RTMP timebase
// Call on_aac_frame to convert AAC frame to RTMP format
HELPER_EXPECT_SUCCESS(builder->on_aac_frame(msg.get(), pts, frame_data, data_size));
// Verify that frame was sent to target
EXPECT_EQ(1, mock_target.on_frame_count_);
EXPECT_TRUE(mock_target.last_frame_ != NULL);
// Verify the frame properties
EXPECT_EQ(pts, mock_target.last_frame_->timestamp_);
EXPECT_EQ(SrsFrameTypeAudio, mock_target.last_frame_->message_type_);
// Verify the payload size: original data + 2 bytes FLV audio tag header
int expected_size = data_size + 2;
EXPECT_EQ(expected_size, mock_target.last_frame_->size());
// Verify the audio tag header (first 2 bytes)
char *payload = mock_target.last_frame_->payload();
EXPECT_TRUE(payload != NULL);
// First byte: audio flag = (codec << 4) | (sample_rate << 2) | (sample_bits << 1) | channels
// Expected: (10 << 4) | (3 << 2) | (1 << 1) | 1 = 0xAF
uint8_t expected_flag = (SrsAudioCodecIdAAC << 4) | (SrsAudioSampleRate44100 << 2) | (SrsAudioSampleBits16bit << 1) | SrsAudioChannelsStereo;
EXPECT_EQ(expected_flag, (uint8_t)payload[0]);
// Second byte: AAC packet type = 1 (AAC raw frame data)
EXPECT_EQ(1, (uint8_t)payload[1]);
// Verify the actual AAC data follows the 2-byte header
EXPECT_EQ(0, memcmp(payload + 2, aac_data, data_size));
srs_freepa(frame_data);
}
// Test SrsSrtSource::stream_is_dead and on_source_id_changed
// This test covers the major use scenario: stream lifecycle management and source ID changes
VOID TEST(SrsSrtSourceTest, StreamLifecycleAndSourceIdChange)
{
srs_error_t err;
// Create a mock request for SRT source initialization
MockRtcAsyncCallRequest mock_req("test.vhost", "live", "stream1");
// Create SrsSrtSource
SrsUniquePtr<SrsSrtSource> source(new SrsSrtSource());
// Initialize the source
HELPER_EXPECT_SUCCESS(source->initialize(&mock_req));
// Test 1: stream_is_dead() when can_publish is false (stream is publishing)
// Simulate on_publish() which sets can_publish_ to false
HELPER_EXPECT_SUCCESS(source->on_publish());
EXPECT_FALSE(source->stream_is_dead()); // Should return false when publishing
// Test 2: stream_is_dead() when can_publish is true but has consumers
source->on_unpublish(); // Sets can_publish_ back to true
EXPECT_TRUE(source->can_publish());
// Create a consumer
ISrsSrtConsumer *consumer = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer));
EXPECT_TRUE(consumer != NULL);
// Should return false when has consumers
EXPECT_FALSE(source->stream_is_dead());
// Test 3: stream_is_dead() when can_publish is true, no consumers, but within cleanup delay
// Destroy the consumer to trigger stream_die_at_ update
srs_freep(consumer);
// Should return false immediately after consumer destruction (within cleanup delay)
EXPECT_FALSE(source->stream_is_dead());
// Test 4: stream_is_dead() returns true after cleanup delay
// Manually set stream_die_at_ to simulate time passing beyond cleanup delay
// SRS_SRT_SOURCE_CLEANUP is 3 seconds
source->stream_die_at_ = srs_time_now_cached() - (4 * SRS_UTIME_SECONDS);
// Should return true after cleanup delay
EXPECT_TRUE(source->stream_is_dead());
// Test 5: on_source_id_changed() updates source ID and notifies consumers
// Create a new source for testing source ID changes
SrsUniquePtr<SrsSrtSource> source2(new SrsSrtSource());
HELPER_EXPECT_SUCCESS(source2->initialize(&mock_req));
// Create a new context ID
SrsContextId new_id;
new_id.set_value("test-source-id-123");
// Change source ID
HELPER_EXPECT_SUCCESS(source2->on_source_id_changed(new_id));
// Verify source ID was updated
EXPECT_EQ(0, source2->source_id().compare(new_id));
// Verify pre_source_id was set to the first ID
EXPECT_EQ(0, source2->pre_source_id().compare(new_id));
// Test 6: on_source_id_changed() with same ID should do nothing
SrsContextId same_id;
same_id.set_value("test-source-id-123");
HELPER_EXPECT_SUCCESS(source2->on_source_id_changed(same_id));
// Source ID should remain unchanged
EXPECT_EQ(0, source2->source_id().compare(new_id));
// Test 7: on_source_id_changed() notifies consumers
// Create consumers for the source
ISrsSrtConsumer *consumer1 = NULL;
ISrsSrtConsumer *consumer2 = NULL;
HELPER_EXPECT_SUCCESS(source2->create_consumer(consumer1));
HELPER_EXPECT_SUCCESS(source2->create_consumer(consumer2));
// Change source ID again
SrsContextId another_id;
another_id.set_value("test-source-id-456");
HELPER_EXPECT_SUCCESS(source2->on_source_id_changed(another_id));
// Verify source ID was updated
EXPECT_EQ(0, source2->source_id().compare(another_id));
// Verify pre_source_id remains the first ID (not updated on subsequent changes)
EXPECT_EQ(0, source2->pre_source_id().compare(new_id));
// Verify consumers were notified (should_update_source_id_ flag set)
SrsSrtConsumer *consumer1_impl = dynamic_cast<SrsSrtConsumer *>(consumer1);
SrsSrtConsumer *consumer2_impl = dynamic_cast<SrsSrtConsumer *>(consumer2);
EXPECT_TRUE(consumer1_impl != NULL);
EXPECT_TRUE(consumer2_impl != NULL);
EXPECT_TRUE(consumer1_impl->should_update_source_id_);
EXPECT_TRUE(consumer2_impl->should_update_source_id_);
// Cleanup consumers
srs_freep(consumer1);
srs_freep(consumer2);
}
// Test SrsSrtSource consumer management lifecycle
// This test covers the major use scenario: creating consumers, managing them, and destroying them
VOID TEST(SrsSrtSourceTest, ConsumerManagementLifecycle)
{
srs_error_t err;
// Create a mock request
MockSrsRequest mock_req("__defaultVhost__", "live", "test_stream");
// Create and initialize SRT source
SrsUniquePtr<SrsSrtSource> source(new SrsSrtSource());
HELPER_EXPECT_SUCCESS(source->initialize(&mock_req));
// Test 1: source_id() and pre_source_id() - should return empty initially
SrsContextId initial_source_id = source->source_id();
SrsContextId initial_pre_source_id = source->pre_source_id();
EXPECT_TRUE(initial_source_id.empty());
EXPECT_TRUE(initial_pre_source_id.empty());
// Test 2: create_consumer() - creates consumer and adds to list
ISrsSrtConsumer *consumer1 = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer1));
EXPECT_TRUE(consumer1 != NULL);
// Verify stream_die_at_ is reset to 0 when consumer is created
EXPECT_EQ(0, source->stream_die_at_);
// Test 3: consumer_dumps() - should succeed (just prints trace)
HELPER_EXPECT_SUCCESS(source->consumer_dumps(consumer1));
// Test 4: Create another consumer
ISrsSrtConsumer *consumer2 = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer2));
EXPECT_TRUE(consumer2 != NULL);
// Verify both consumers are in the list
EXPECT_EQ(2, (int)source->consumers_.size());
// Test 5: update_auth() - updates authentication info
MockSrsRequest auth_req("__defaultVhost__", "live", "test_stream");
auth_req.pageUrl_ = "http://example.com/page";
auth_req.swfUrl_ = "http://example.com/swf";
source->update_auth(&auth_req);
// Verify auth was updated in the internal request
EXPECT_STREQ(auth_req.pageUrl_.c_str(), source->req_->pageUrl_.c_str());
EXPECT_STREQ(auth_req.swfUrl_.c_str(), source->req_->swfUrl_.c_str());
// Test 6: set_bridge() - sets bridge and frees old one
// Note: We don't create a real bridge here as it would require complex setup
// Just verify the method can be called safely with NULL
source->set_bridge(NULL);
EXPECT_TRUE(source->srt_bridge_ == NULL);
// Test 7: on_consumer_destroy() - removes consumer from list
source->on_consumer_destroy(consumer1);
// Verify consumer1 was removed
EXPECT_EQ(1, (int)source->consumers_.size());
// Verify stream_die_at_ is NOT set yet (still has one consumer)
EXPECT_EQ(0, source->stream_die_at_);
// Test 8: on_consumer_destroy() - removes last consumer and sets stream_die_at_
source->on_consumer_destroy(consumer2);
// Verify consumer2 was removed
EXPECT_EQ(0, (int)source->consumers_.size());
// Verify stream_die_at_ is set when last consumer is destroyed (and can_publish_ is true)
EXPECT_TRUE(source->stream_die_at_ > 0);
// Test 9: on_consumer_destroy() with non-existent consumer - should not crash
ISrsSrtConsumer *fake_consumer = (ISrsSrtConsumer *)0x12345678;
source->on_consumer_destroy(fake_consumer);
// Should still have 0 consumers
EXPECT_EQ(0, (int)source->consumers_.size());
// Cleanup consumers
srs_freep(consumer1);
srs_freep(consumer2);
}
// Mock statistic implementation
MockSrtStatistic::MockSrtStatistic()
{
on_stream_publish_count_ = 0;
on_stream_close_count_ = 0;
last_publisher_id_ = "";
last_publish_req_ = NULL;
last_close_req_ = NULL;
}
MockSrtStatistic::~MockSrtStatistic()
{
}
void MockSrtStatistic::on_disconnect(std::string id, srs_error_t err)
{
}
srs_error_t MockSrtStatistic::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type)
{
return srs_success;
}
srs_error_t MockSrtStatistic::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height)
{
return srs_success;
}
srs_error_t MockSrtStatistic::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object)
{
return srs_success;
}
void MockSrtStatistic::on_stream_publish(ISrsRequest *req, std::string publisher_id)
{
on_stream_publish_count_++;
last_publish_req_ = req;
last_publisher_id_ = publisher_id;
}
void MockSrtStatistic::on_stream_close(ISrsRequest *req)
{
on_stream_close_count_++;
last_close_req_ = req;
}
void MockSrtStatistic::kbps_add_delta(std::string id, ISrsKbpsDelta *delta)
{
}
void MockSrtStatistic::kbps_sample()
{
}
srs_error_t MockSrtStatistic::on_video_frames(ISrsRequest *req, int nb_frames)
{
return srs_success;
}
std::string MockSrtStatistic::server_id()
{
return "mock_server_id";
}
std::string MockSrtStatistic::service_id()
{
return "mock_service_id";
}
std::string MockSrtStatistic::service_pid()
{
return "mock_pid";
}
SrsStatisticVhost *MockSrtStatistic::find_vhost_by_id(std::string vid)
{
return NULL;
}
SrsStatisticStream *MockSrtStatistic::find_stream(std::string sid)
{
return NULL;
}
SrsStatisticClient *MockSrtStatistic::find_client(std::string client_id)
{
return NULL;
}
srs_error_t MockSrtStatistic::dumps_vhosts(SrsJsonArray *arr)
{
return srs_success;
}
srs_error_t MockSrtStatistic::dumps_streams(SrsJsonArray *arr, int start, int count)
{
return srs_success;
}
srs_error_t MockSrtStatistic::dumps_clients(SrsJsonArray *arr, int start, int count)
{
return srs_success;
}
srs_error_t MockSrtStatistic::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs)
{
send_bytes = 0;
recv_bytes = 0;
nstreams = 0;
nclients = 0;
total_nclients = 0;
nerrs = 0;
return srs_success;
}
void MockSrtStatistic::reset()
{
on_stream_publish_count_ = 0;
on_stream_close_count_ = 0;
last_publisher_id_ = "";
last_publish_req_ = NULL;
last_close_req_ = NULL;
}
// Mock SRT bridge implementation
MockSrtBridge::MockSrtBridge()
{
on_publish_count_ = 0;
on_unpublish_count_ = 0;
on_packet_count_ = 0;
on_publish_error_ = srs_success;
on_packet_error_ = srs_success;
}
MockSrtBridge::~MockSrtBridge()
{
srs_freep(on_publish_error_);
srs_freep(on_packet_error_);
}
srs_error_t MockSrtBridge::initialize(ISrsRequest *r)
{
return srs_success;
}
srs_error_t MockSrtBridge::on_publish()
{
on_publish_count_++;
return srs_error_copy(on_publish_error_);
}
void MockSrtBridge::on_unpublish()
{
on_unpublish_count_++;
}
srs_error_t MockSrtBridge::on_packet(SrsSrtPacket *packet)
{
on_packet_count_++;
return srs_error_copy(on_packet_error_);
}
void MockSrtBridge::set_on_publish_error(srs_error_t err)
{
srs_freep(on_publish_error_);
on_publish_error_ = srs_error_copy(err);
}
void MockSrtBridge::set_on_packet_error(srs_error_t err)
{
srs_freep(on_packet_error_);
on_packet_error_ = srs_error_copy(err);
}
void MockSrtBridge::reset()
{
on_publish_count_ = 0;
on_unpublish_count_ = 0;
on_packet_count_ = 0;
srs_freep(on_publish_error_);
on_publish_error_ = srs_success;
srs_freep(on_packet_error_);
on_packet_error_ = srs_success;
}
// Mock SRT consumer implementation
MockSrtConsumer::MockSrtConsumer()
{
enqueue_count_ = 0;
enqueue_error_ = srs_success;
}
MockSrtConsumer::~MockSrtConsumer()
{
srs_freep(enqueue_error_);
for (int i = 0; i < (int)packets_.size(); i++) {
srs_freep(packets_[i]);
}
packets_.clear();
}
srs_error_t MockSrtConsumer::enqueue(SrsSrtPacket *packet)
{
enqueue_count_++;
if (enqueue_error_ != srs_success) {
srs_freep(packet);
return srs_error_copy(enqueue_error_);
}
packets_.push_back(packet);
return srs_success;
}
srs_error_t MockSrtConsumer::dump_packet(SrsSrtPacket **ppkt)
{
return srs_success;
}
void MockSrtConsumer::wait(int nb_msgs, srs_utime_t timeout)
{
}
void MockSrtConsumer::set_enqueue_error(srs_error_t err)
{
srs_freep(enqueue_error_);
enqueue_error_ = srs_error_copy(err);
}
void MockSrtConsumer::reset()
{
enqueue_count_ = 0;
srs_freep(enqueue_error_);
enqueue_error_ = srs_success;
for (int i = 0; i < (int)packets_.size(); i++) {
srs_freep(packets_[i]);
}
packets_.clear();
}
// Mock RTSP source implementation
MockRtspSource::MockRtspSource()
{
on_consumer_destroy_count_ = 0;
}
MockRtspSource::~MockRtspSource()
{
}
void MockRtspSource::on_consumer_destroy(SrsRtspConsumer *consumer)
{
on_consumer_destroy_count_++;
}
void MockRtspSource::reset()
{
on_consumer_destroy_count_ = 0;
}
// Test SrsSrtSource publish/unpublish lifecycle
// This test covers the major use scenario: publishing a stream, then unpublishing it
VOID TEST(SrsSrtSourceTest, PublishUnpublishLifecycle)
{
srs_error_t err;
// Create a mock request
MockSrsRequest req("test.vhost", "live", "livestream");
// Create SRT source and initialize
SrsUniquePtr<SrsSrtSource> source(new SrsSrtSource());
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Replace global stat with mock
MockSrtStatistic mock_stat;
ISrsStatistic *old_stat = source->stat_;
source->stat_ = &mock_stat;
// Test 1: can_publish() should return true initially
EXPECT_TRUE(source->can_publish());
// Test 2: on_publish() - should set can_publish_ to false and call stat->on_stream_publish
HELPER_EXPECT_SUCCESS(source->on_publish());
// Verify can_publish_ is now false
EXPECT_FALSE(source->can_publish());
// Verify stat->on_stream_publish was called
EXPECT_EQ(1, mock_stat.on_stream_publish_count_);
EXPECT_TRUE(mock_stat.last_publish_req_ != NULL);
EXPECT_FALSE(mock_stat.last_publisher_id_.empty());
// Test 3: on_unpublish() - should restore can_publish_ to true and call stat->on_stream_close
source->on_unpublish();
// Verify can_publish_ is now true
EXPECT_TRUE(source->can_publish());
// Verify stat->on_stream_close was called
EXPECT_EQ(1, mock_stat.on_stream_close_count_);
EXPECT_TRUE(mock_stat.last_close_req_ != NULL);
// Verify stream_die_at_ is set (no consumers)
EXPECT_TRUE(source->stream_die_at_ > 0);
// Test 4: on_unpublish() when already unpublished - should be ignored
srs_utime_t old_die_at = source->stream_die_at_;
int old_close_count = mock_stat.on_stream_close_count_;
source->on_unpublish();
// Verify nothing changed
EXPECT_EQ(old_close_count, mock_stat.on_stream_close_count_);
EXPECT_EQ(old_die_at, source->stream_die_at_);
// Restore global stat
source->stat_ = old_stat;
}
// Test SrsSrtSource publish/unpublish with bridge
// This test covers the scenario with a bridge that needs to be notified
VOID TEST(SrsSrtSourceTest, PublishUnpublishWithBridge)
{
srs_error_t err;
// Create a mock request
MockSrsRequest req("test.vhost", "live", "livestream");
// Create SRT source and initialize
SrsUniquePtr<SrsSrtSource> source(new SrsSrtSource());
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Replace global stat with mock
MockSrtStatistic mock_stat;
ISrsStatistic *old_stat = source->stat_;
source->stat_ = &mock_stat;
// Create and set mock bridge
MockSrtBridge *mock_bridge = new MockSrtBridge();
source->set_bridge(mock_bridge);
// Test 1: on_publish() with bridge - should call bridge->on_publish()
HELPER_EXPECT_SUCCESS(source->on_publish());
// Verify bridge->on_publish was called
EXPECT_EQ(1, mock_bridge->on_publish_count_);
// Test 2: on_unpublish() with bridge - should call bridge->on_unpublish() and free bridge
// Note: The bridge will be freed by on_unpublish(), so we can't check its state afterwards
source->on_unpublish();
// Verify bridge was freed (we can't check mock_bridge->on_unpublish_count_ because it's freed)
EXPECT_TRUE(source->srt_bridge_ == NULL);
// Restore global stat
source->stat_ = old_stat;
}
// Test SrsSrtSource on_packet distribution to consumers and bridge
// This test covers the major use scenario: distributing packets to multiple consumers and bridge
VOID TEST(SrsSrtSourceTest, OnPacketDistribution)
{
srs_error_t err;
// Create a mock request
MockSrsRequest req("test.vhost", "live", "livestream");
// Create SRT source and initialize
SrsUniquePtr<SrsSrtSource> source(new SrsSrtSource());
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create mock consumers
MockSrtConsumer *consumer1 = new MockSrtConsumer();
MockSrtConsumer *consumer2 = new MockSrtConsumer();
// Add consumers to source
source->consumers_.push_back(consumer1);
source->consumers_.push_back(consumer2);
// Create and set mock bridge
MockSrtBridge *mock_bridge = new MockSrtBridge();
source->set_bridge(mock_bridge);
// Create a test packet
SrsUniquePtr<SrsSrtPacket> packet(new SrsSrtPacket());
const char *test_data = "Test SRT Packet Data";
packet->wrap((char *)test_data, strlen(test_data));
// Test: on_packet should distribute to all consumers and bridge
HELPER_EXPECT_SUCCESS(source->on_packet(packet.get()));
// Verify both consumers received the packet
EXPECT_EQ(1, consumer1->enqueue_count_);
EXPECT_EQ(1, consumer2->enqueue_count_);
EXPECT_EQ(1, (int)consumer1->packets_.size());
EXPECT_EQ(1, (int)consumer2->packets_.size());
// Verify packet data in consumers
EXPECT_EQ(strlen(test_data), (size_t)consumer1->packets_[0]->size());
EXPECT_EQ(0, memcmp(consumer1->packets_[0]->data(), test_data, strlen(test_data)));
EXPECT_EQ(strlen(test_data), (size_t)consumer2->packets_[0]->size());
EXPECT_EQ(0, memcmp(consumer2->packets_[0]->data(), test_data, strlen(test_data)));
// Verify bridge received the packet
EXPECT_EQ(1, mock_bridge->on_packet_count_);
// Cleanup: Remove consumers from source before they are freed
source->consumers_.clear();
srs_freep(consumer1);
srs_freep(consumer2);
// Note: mock_bridge will be freed by source destructor
}
// Test SrsRtspConsumer enqueue and update_source_id
// This test covers the major use scenario: enqueueing RTP packets and signaling waiting threads
VOID TEST(SrsRtspConsumerTest, EnqueueAndUpdateSourceId)
{
srs_error_t err;
// Create a mock RTSP source on heap
MockRtspSource *mock_source = new MockRtspSource();
// Create RTSP consumer - use raw pointer to avoid destructor issues with mock
SrsRtspConsumer *consumer = new SrsRtspConsumer((SrsRtspSource *)mock_source);
// Test 1: update_source_id() - should set should_update_source_id_ flag
consumer->update_source_id();
EXPECT_TRUE(consumer->should_update_source_id_);
// Test 2: enqueue() without waiting - should add packet to queue
SrsRtpPacket *pkt1 = create_test_rtp_packet(100, 1000, 12345);
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1));
EXPECT_EQ(1, (int)consumer->queue_.size());
// Test 3: enqueue() multiple packets - should accumulate in queue
SrsRtpPacket *pkt2 = create_test_rtp_packet(101, 1000, 12345);
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2));
EXPECT_EQ(2, (int)consumer->queue_.size());
// Test 4: enqueue() with waiting thread - should signal when queue size exceeds minimum
consumer->mw_waiting_ = true;
consumer->mw_min_msgs_ = 1; // Signal when queue has more than 1 message
SrsRtpPacket *pkt3 = create_test_rtp_packet(102, 1000, 12345);
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3));
EXPECT_EQ(3, (int)consumer->queue_.size());
// After signaling, mw_waiting_ should be set to false
EXPECT_FALSE(consumer->mw_waiting_);
// Test 5: dump_packet() - should retrieve packets from queue
SrsRtpPacket *dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt != NULL);
EXPECT_EQ(100, dumped_pkt->header_.get_sequence());
EXPECT_EQ(2, (int)consumer->queue_.size()); // Queue should have 2 packets left
// Free the dumped packet (it was removed from queue)
srs_freep(dumped_pkt);
// Manual cleanup to avoid calling destructor with invalid mock source cast
// Note: The packets in the queue are still owned by the consumer and will be freed
// when we manually clean up. We need to free them before freeing the consumer struct.
for (int i = 0; i < (int)consumer->queue_.size(); i++) {
srs_freep(consumer->queue_[i]);
}
consumer->queue_.clear();
// Destroy condition variable
srs_cond_destroy(consumer->mw_wait_);
// Free consumer memory without calling destructor (to avoid mock source issues)
free(consumer);
// Clean up mock source
srs_freep(mock_source);
}
// Test SrsRtspConsumer dump_packet and wait
// This test covers the major use scenario: waiting for packets and dumping them from queue
VOID TEST(SrsRtspConsumerTest, DumpPacketAndWait)
{
srs_error_t err;
// Create a mock RTSP source on heap
MockRtspSource *mock_source = new MockRtspSource();
// Create RTSP consumer - use raw pointer to avoid destructor issues with mock
SrsRtspConsumer *consumer = new SrsRtspConsumer((SrsRtspSource *)mock_source);
// Test 1: dump_packet() on empty queue - should return NULL
SrsRtpPacket *dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt == NULL);
// Test 2: Enqueue packets and dump them
SrsRtpPacket *pkt1 = create_test_rtp_packet(100, 1000, 12345);
SrsRtpPacket *pkt2 = create_test_rtp_packet(101, 2000, 12345);
SrsRtpPacket *pkt3 = create_test_rtp_packet(102, 3000, 12345);
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt1));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt2));
HELPER_EXPECT_SUCCESS(consumer->enqueue(pkt3));
EXPECT_EQ(3, (int)consumer->queue_.size());
// Test 3: wait() when queue size is already above threshold - should return immediately
consumer->wait(1); // Wait for more than 1 message
EXPECT_FALSE(consumer->mw_waiting_); // Should not be waiting since queue has 3 packets
// Test 4: dump_packet() - should retrieve first packet (FIFO order)
dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt != NULL);
EXPECT_EQ(100, dumped_pkt->header_.get_sequence());
EXPECT_EQ(1000, dumped_pkt->header_.get_timestamp());
EXPECT_EQ(2, (int)consumer->queue_.size()); // Queue should have 2 packets left
srs_freep(dumped_pkt);
// Test 5: dump_packet() again - should retrieve second packet
dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt != NULL);
EXPECT_EQ(101, dumped_pkt->header_.get_sequence());
EXPECT_EQ(2000, dumped_pkt->header_.get_timestamp());
EXPECT_EQ(1, (int)consumer->queue_.size()); // Queue should have 1 packet left
srs_freep(dumped_pkt);
// Test 6: dump_packet() third time - should retrieve last packet
dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt != NULL);
EXPECT_EQ(102, dumped_pkt->header_.get_sequence());
EXPECT_EQ(3000, dumped_pkt->header_.get_timestamp());
EXPECT_EQ(0, (int)consumer->queue_.size()); // Queue should be empty
srs_freep(dumped_pkt);
// Test 7: dump_packet() on empty queue again - should return NULL
dumped_pkt = NULL;
HELPER_EXPECT_SUCCESS(consumer->dump_packet(&dumped_pkt));
EXPECT_TRUE(dumped_pkt == NULL);
// Manual cleanup to avoid calling destructor with invalid mock source cast
for (int i = 0; i < (int)consumer->queue_.size(); i++) {
srs_freep(consumer->queue_[i]);
}
consumer->queue_.clear();
// Destroy condition variable
srs_cond_destroy(consumer->mw_wait_);
// Free consumer memory without calling destructor (to avoid mock source issues)
free(consumer);
// Clean up mock source
srs_freep(mock_source);
}
// Test SrsRtspConsumer::on_stream_change() - covers the major use scenario
// This test verifies that when a stream change event occurs, the consumer
// properly forwards the event to its registered handler callback
VOID TEST(SrsRtspConsumerTest, OnStreamChangeWithHandler)
{
// Create a mock RTSP source (cast to SrsRtspSource* for constructor)
MockRtspSource *mock_source = new MockRtspSource();
SrsRtspSource *source_ptr = (SrsRtspSource *)mock_source;
// Create RTSP consumer with mock source
SrsRtspConsumer *consumer = new SrsRtspConsumer(source_ptr);
// Create mock handler to receive stream change events
MockRtcSourceChangeCallback mock_handler;
EXPECT_EQ(0, mock_handler.stream_change_count_);
// Set the handler on the consumer
consumer->set_handler(&mock_handler);
// Create a mock stream description
SrsRtcSourceDescription desc;
desc.id_ = "test-stream-id";
// Test: Call on_stream_change() - should forward to handler
consumer->on_stream_change(&desc);
// Verify: Handler should have been called once
EXPECT_EQ(1, mock_handler.stream_change_count_);
EXPECT_EQ(&desc, mock_handler.last_stream_desc_);
EXPECT_EQ("test-stream-id", mock_handler.last_stream_desc_->id_);
// Test: Call on_stream_change() again - should forward again
SrsRtcSourceDescription desc2;
desc2.id_ = "another-stream-id";
consumer->on_stream_change(&desc2);
// Verify: Handler should have been called twice
EXPECT_EQ(2, mock_handler.stream_change_count_);
EXPECT_EQ(&desc2, mock_handler.last_stream_desc_);
EXPECT_EQ("another-stream-id", mock_handler.last_stream_desc_->id_);
// Manual cleanup to avoid calling destructor with invalid mock source cast
for (int i = 0; i < (int)consumer->queue_.size(); i++) {
srs_freep(consumer->queue_[i]);
}
consumer->queue_.clear();
// Destroy condition variable
srs_cond_destroy(consumer->mw_wait_);
// Free consumer memory without calling destructor (to avoid mock source issues)
free(consumer);
// Clean up mock source
srs_freep(mock_source);
}
// Test SrsRtspSourceManager::notify() - covers the major use scenario
// This test verifies that the notify method properly cleans up dead sources from the pool
VOID TEST(SrsRtspSourceManagerTest, NotifyCleanupDeadSources)
{
srs_error_t err;
// Create RTSP source manager
SrsUniquePtr<SrsRtspSourceManager> manager(new SrsRtspSourceManager());
HELPER_EXPECT_SUCCESS(manager->initialize());
// Create mock requests for source creation
MockSrsRequest req1("localhost", "live", "stream1");
MockSrsRequest req2("localhost", "live", "stream2");
MockSrsRequest req3("localhost", "live", "stream3");
// Create three sources in the pool
SrsSharedPtr<SrsRtspSource> source1;
SrsSharedPtr<SrsRtspSource> source2;
SrsSharedPtr<SrsRtspSource> source3;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req1, source1));
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req2, source2));
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req3, source3));
EXPECT_TRUE(source1.get() != NULL);
EXPECT_TRUE(source2.get() != NULL);
EXPECT_TRUE(source3.get() != NULL);
// Simulate sources being published and then unpublished to set stream_die_at_
// This makes them "alive" initially (within cleanup delay)
source1->is_created_ = true;
source2->is_created_ = true;
source3->is_created_ = true;
HELPER_EXPECT_SUCCESS(source1->on_publish());
HELPER_EXPECT_SUCCESS(source2->on_publish());
HELPER_EXPECT_SUCCESS(source3->on_publish());
source1->on_unpublish(); // Sets stream_die_at_ to current time
source2->on_unpublish();
source3->on_unpublish();
// Verify all three sources are in the pool
EXPECT_EQ(3, (int)manager->pool_.size());
// Test 1: notify() when all sources are alive (within cleanup delay) - should not remove any sources
EXPECT_FALSE(source1->stream_is_dead());
EXPECT_FALSE(source2->stream_is_dead());
EXPECT_FALSE(source3->stream_is_dead());
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
EXPECT_EQ(3, (int)manager->pool_.size());
// Test 2: Make source1 dead by setting stream_die_at_ to past time
// Set stream_die_at_ to 4 seconds ago (beyond SRS_RTSP_SOURCE_CLEANUP of 3 seconds)
source1->stream_die_at_ = srs_time_now_cached() - (4 * SRS_UTIME_SECONDS);
// Verify source1 is now dead, but source2 and source3 are still alive
EXPECT_TRUE(source1->stream_is_dead());
EXPECT_FALSE(source2->stream_is_dead());
EXPECT_FALSE(source3->stream_is_dead());
// Call notify() - should remove source1 from pool
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
EXPECT_EQ(2, (int)manager->pool_.size());
// Verify source1 is removed, but source2 and source3 remain
SrsSharedPtr<SrsRtspSource> fetched1 = manager->fetch(&req1);
SrsSharedPtr<SrsRtspSource> fetched2 = manager->fetch(&req2);
SrsSharedPtr<SrsRtspSource> fetched3 = manager->fetch(&req3);
EXPECT_TRUE(fetched1.get() == NULL); // source1 removed
EXPECT_TRUE(fetched2.get() != NULL); // source2 still exists
EXPECT_TRUE(fetched3.get() != NULL); // source3 still exists
// Test 3: Make source2 and source3 dead
source2->stream_die_at_ = srs_time_now_cached() - (4 * SRS_UTIME_SECONDS);
source3->stream_die_at_ = srs_time_now_cached() - (4 * SRS_UTIME_SECONDS);
EXPECT_TRUE(source2->stream_is_dead());
EXPECT_TRUE(source3->stream_is_dead());
// Call notify() - should remove both source2 and source3
HELPER_EXPECT_SUCCESS(manager->notify(0, 0, 0));
EXPECT_EQ(0, (int)manager->pool_.size());
// Verify all sources are removed
fetched2 = manager->fetch(&req2);
fetched3 = manager->fetch(&req3);
EXPECT_TRUE(fetched2.get() == NULL);
EXPECT_TRUE(fetched3.get() == NULL);
}
// Test SrsRtspSourceManager::fetch_or_create - covers the major use scenario:
// 1. Creating a new source on first fetch
// 2. Fetching existing source on subsequent calls
// 3. Verifying update_auth is called for existing sources
VOID TEST(SrsRtspSourceManagerTest, FetchOrCreateMajorScenario)
{
srs_error_t err;
// Create manager
SrsUniquePtr<SrsRtspSourceManager> manager(new SrsRtspSourceManager());
HELPER_EXPECT_SUCCESS(manager->initialize());
// Create request for stream
MockSrsRequest req1("test.vhost", "live", "stream1");
// First fetch_or_create - should create new source
SrsSharedPtr<SrsRtspSource> source1;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req1, source1));
EXPECT_TRUE(source1.get() != NULL);
EXPECT_EQ(1, (int)manager->pool_.size());
// Second fetch_or_create with same stream URL - should return existing source
MockSrsRequest req2("test.vhost", "live", "stream1");
SrsSharedPtr<SrsRtspSource> source2;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req2, source2));
EXPECT_TRUE(source2.get() != NULL);
EXPECT_EQ(1, (int)manager->pool_.size());
// Verify it's the same source object
EXPECT_TRUE(source1.get() == source2.get());
// Third fetch_or_create with different stream URL - should create new source
MockSrsRequest req3("test.vhost", "live", "stream2");
SrsSharedPtr<SrsRtspSource> source3;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req3, source3));
EXPECT_TRUE(source3.get() != NULL);
EXPECT_EQ(2, (int)manager->pool_.size());
// Verify it's a different source object
EXPECT_TRUE(source1.get() != source3.get());
}
// Test SrsRtspSourceManager::fetch method
// This test covers the major use scenario:
// 1. Fetching existing source from pool returns the source
// 2. Fetching non-existent source returns NULL shared pointer
VOID TEST(SrsRtspSourceManagerTest, FetchMajorScenario)
{
srs_error_t err;
// Create manager
SrsUniquePtr<SrsRtspSourceManager> manager(new SrsRtspSourceManager());
HELPER_EXPECT_SUCCESS(manager->initialize());
// Create request for stream
MockSrsRequest req1("test.vhost", "live", "stream1");
// First, create a source using fetch_or_create
SrsSharedPtr<SrsRtspSource> source1;
HELPER_EXPECT_SUCCESS(manager->fetch_or_create(&req1, source1));
EXPECT_TRUE(source1.get() != NULL);
// Test fetch() - should return existing source
MockSrsRequest req2("test.vhost", "live", "stream1");
SrsSharedPtr<SrsRtspSource> fetched_source = manager->fetch(&req2);
EXPECT_TRUE(fetched_source.get() != NULL);
EXPECT_TRUE(source1.get() == fetched_source.get());
// Test fetch() with non-existent stream - should return NULL shared pointer
MockSrsRequest req3("test.vhost", "live", "nonexistent");
SrsSharedPtr<SrsRtspSource> null_source = manager->fetch(&req3);
EXPECT_TRUE(null_source.get() == NULL);
}
// Test SrsRtspSource consumer creation - covers the major use scenario:
// 1. Getting source_id and pre_source_id
// 2. Creating a consumer
// 3. Dumping consumer state
// 4. Verifying consumer is added to source's consumer list
VOID TEST(SrsRtspSourceTest, CreateConsumerMajorScenario)
{
srs_error_t err;
// Create RTSP source
SrsUniquePtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Test source_id() and pre_source_id() - should return valid context IDs
SrsContextId source_id = source->source_id();
SrsContextId pre_source_id = source->pre_source_id();
EXPECT_TRUE(source_id.compare(pre_source_id) != 0 || source_id.empty());
// Create consumer - major use case
SrsRtspConsumer *consumer = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer));
EXPECT_TRUE(consumer != NULL);
// Verify consumer is added to source's consumer list
EXPECT_EQ(1, (int)source->consumers_.size());
EXPECT_EQ(consumer, source->consumers_[0]);
// Verify stream_die_at is reset when consumer is created
EXPECT_EQ(0, (int)source->stream_die_at_);
// Call consumer_dumps to complete consumer setup
HELPER_EXPECT_SUCCESS(source->consumer_dumps(consumer, true, true, true));
// Cleanup - consumer will be destroyed and removed from source
srs_freep(consumer);
}
// Test SrsRtspSource stream lifecycle - covers the major use scenario:
// 1. can_publish() returns true before stream is created
// 2. set_stream_created() marks stream as created
// 3. can_publish() returns false after stream is created
// 4. on_consumer_destroy() removes consumer from list
// 5. on_consumer_destroy() sets stream_die_at when no consumers and not created
VOID TEST(SrsRtspSourceTest, StreamLifecycleMajorScenario)
{
srs_error_t err;
// Create RTSP source
SrsUniquePtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Initially, stream is not created, so can_publish() should return true
EXPECT_TRUE(source->can_publish());
EXPECT_FALSE(source->is_created_);
// Create two consumers
SrsRtspConsumer *consumer1 = NULL;
SrsRtspConsumer *consumer2 = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer1));
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer2));
EXPECT_TRUE(consumer1 != NULL);
EXPECT_TRUE(consumer2 != NULL);
EXPECT_EQ(2, (int)source->consumers_.size());
// Set stream as created (simulates SDP negotiation complete)
source->set_stream_created();
EXPECT_TRUE(source->is_created_);
// After stream is created, can_publish() should return false
EXPECT_FALSE(source->can_publish());
// Destroy first consumer - should remove it from list
source->on_consumer_destroy(consumer1);
EXPECT_EQ(1, (int)source->consumers_.size());
EXPECT_EQ(consumer2, source->consumers_[0]);
// stream_die_at should NOT be set because stream is created
EXPECT_EQ(0, (int)source->stream_die_at_);
// Destroy second consumer - should remove it from list
source->on_consumer_destroy(consumer2);
EXPECT_EQ(0, (int)source->consumers_.size());
// stream_die_at should still NOT be set because stream is created
EXPECT_EQ(0, (int)source->stream_die_at_);
// Reset stream state to simulate unpublish scenario
source->is_created_ = false;
source->stream_die_at_ = 0;
// Create a new consumer in unpublished state
SrsRtspConsumer *consumer3 = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer3));
EXPECT_TRUE(consumer3 != NULL);
EXPECT_EQ(1, (int)source->consumers_.size());
// Destroy consumer when stream is not created - should set stream_die_at
source->on_consumer_destroy(consumer3);
EXPECT_EQ(0, (int)source->consumers_.size());
// stream_die_at should be set because stream is not created and no consumers
EXPECT_TRUE(source->stream_die_at_ > 0);
// Cleanup
srs_freep(consumer1);
srs_freep(consumer2);
srs_freep(consumer3);
}
// Test SrsRtspSource on_rtp, audio_desc, video_desc - covers the major use scenario:
// 1. on_rtp() distributes RTP packets to all consumers
// 2. set_audio_desc() and audio_desc() manage audio track description
// 3. set_video_desc() and video_desc() manage video track description
VOID TEST(SrsRtspSourceTest, OnRtpAndTrackDescriptorsMajorScenario)
{
srs_error_t err;
// Create RTSP source
SrsUniquePtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create real consumers (they need to be real SrsRtspConsumer objects)
SrsRtspConsumer *consumer1 = NULL;
SrsRtspConsumer *consumer2 = NULL;
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer1));
HELPER_EXPECT_SUCCESS(source->create_consumer(consumer2));
EXPECT_TRUE(consumer1 != NULL);
EXPECT_TRUE(consumer2 != NULL);
// Create a test RTP packet
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
char test_data[100];
memset(test_data, 0xAB, sizeof(test_data));
pkt->wrap(test_data, sizeof(test_data));
pkt->header_.set_sequence(12345);
pkt->header_.set_timestamp(67890);
pkt->header_.set_ssrc(11111);
// Test on_rtp() - should distribute packet to all consumers
HELPER_EXPECT_SUCCESS(source->on_rtp(pkt.get()));
// Verify both consumers received the packet by checking their queues
SrsRtpPacket *pkt_out1 = NULL;
SrsRtpPacket *pkt_out2 = NULL;
HELPER_EXPECT_SUCCESS(consumer1->dump_packet(&pkt_out1));
HELPER_EXPECT_SUCCESS(consumer2->dump_packet(&pkt_out2));
EXPECT_TRUE(pkt_out1 != NULL);
EXPECT_TRUE(pkt_out2 != NULL);
// Verify packet data is copied correctly
EXPECT_EQ(pkt->header_.get_sequence(), pkt_out1->header_.get_sequence());
EXPECT_EQ(pkt->header_.get_timestamp(), pkt_out1->header_.get_timestamp());
EXPECT_EQ(pkt->header_.get_ssrc(), pkt_out1->header_.get_ssrc());
// Cleanup packets
srs_freep(pkt_out1);
srs_freep(pkt_out2);
// Test audio descriptor management
SrsUniquePtr<SrsRtcTrackDescription> audio_desc(new SrsRtcTrackDescription());
audio_desc->type_ = "audio";
audio_desc->ssrc_ = 22222;
audio_desc->id_ = "audio-track-1";
audio_desc->is_active_ = true;
// Set audio descriptor
source->set_audio_desc(audio_desc.get());
// Verify audio descriptor is set and copied
SrsRtcTrackDescription *retrieved_audio = source->audio_desc();
EXPECT_TRUE(retrieved_audio != NULL);
EXPECT_EQ("audio", retrieved_audio->type_);
EXPECT_EQ(22222u, retrieved_audio->ssrc_);
EXPECT_EQ("audio-track-1", retrieved_audio->id_);
EXPECT_TRUE(retrieved_audio->is_active_);
// Test video descriptor management
SrsUniquePtr<SrsRtcTrackDescription> video_desc(new SrsRtcTrackDescription());
video_desc->type_ = "video";
video_desc->ssrc_ = 33333;
video_desc->id_ = "video-track-1";
video_desc->is_active_ = true;
// Set video descriptor
source->set_video_desc(video_desc.get());
// Verify video descriptor is set and copied
SrsRtcTrackDescription *retrieved_video = source->video_desc();
EXPECT_TRUE(retrieved_video != NULL);
EXPECT_EQ("video", retrieved_video->type_);
EXPECT_EQ(33333u, retrieved_video->ssrc_);
EXPECT_EQ("video-track-1", retrieved_video->id_);
EXPECT_TRUE(retrieved_video->is_active_);
// Send another packet to verify continued operation
SrsUniquePtr<SrsRtpPacket> pkt2(new SrsRtpPacket());
pkt2->wrap(test_data, sizeof(test_data));
pkt2->header_.set_sequence(12346);
HELPER_EXPECT_SUCCESS(source->on_rtp(pkt2.get()));
// Verify consumers received second packet
SrsRtpPacket *pkt_out3 = NULL;
SrsRtpPacket *pkt_out4 = NULL;
HELPER_EXPECT_SUCCESS(consumer1->dump_packet(&pkt_out3));
HELPER_EXPECT_SUCCESS(consumer2->dump_packet(&pkt_out4));
EXPECT_TRUE(pkt_out3 != NULL);
EXPECT_TRUE(pkt_out4 != NULL);
EXPECT_EQ(12346, pkt_out3->header_.get_sequence());
// Cleanup
srs_freep(pkt_out3);
srs_freep(pkt_out4);
srs_freep(consumer1);
srs_freep(consumer2);
}
// Test SrsRtspRtpBuilder::initialize_audio_track - covers the major use scenario:
// 1. Initialize audio track with AAC codec
// 2. Verify audio track description is created with correct parameters
// 3. Verify AAC config hex is set from format's aac_extra_data
// 4. Verify audio description is set to source
VOID TEST(SrsRtspRtpBuilderTest, InitializeAudioTrackAAC)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup audio format with AAC codec
// Simulate AAC sequence header with sample rate 44100Hz (index 3) and stereo (2 channels)
// Note: acodec_ is created lazily in on_audio(), so we need to create it manually for testing
builder->format_->acodec_ = new SrsAudioCodecConfig();
builder->format_->acodec_->id_ = SrsAudioCodecIdAAC;
builder->format_->acodec_->sound_rate_ = SrsAudioSampleRate44100; // Index 3 = 44100Hz
builder->format_->acodec_->sound_type_ = SrsAudioChannelsStereo;
builder->format_->acodec_->aac_channels_ = 2;
// Create AAC AudioSpecificConfig: AAC-LC, 44100Hz, stereo
// Format: 5 bits object type (2=AAC-LC) + 4 bits sample rate index (4=44100) + 4 bits channel config (2=stereo)
// Binary: 00010 0100 0010 = 0x1208 (but we use standard AAC config)
// Standard AAC-LC 44.1kHz stereo config: 0x1210
char aac_config[] = {0x12, 0x10};
builder->format_->acodec_->aac_extra_data_.assign(aac_config, aac_config + sizeof(aac_config));
// Call initialize_audio_track with AAC codec
HELPER_EXPECT_SUCCESS(builder->initialize_audio_track(SrsAudioCodecIdAAC));
// Verify audio track description was set to source
SrsRtcTrackDescription *audio_desc = source->audio_desc();
EXPECT_TRUE(audio_desc != NULL);
// Verify track description properties
EXPECT_EQ("audio", audio_desc->type_);
EXPECT_TRUE(!audio_desc->id_.empty());
EXPECT_TRUE(audio_desc->id_.find("audio-") == 0); // Should start with "audio-"
EXPECT_EQ("recvonly", audio_desc->direction_);
// Verify SSRC was generated
EXPECT_TRUE(audio_desc->ssrc_ != 0);
EXPECT_EQ(audio_desc->ssrc_, builder->audio_ssrc_);
// Verify media payload
EXPECT_TRUE(audio_desc->media_ != NULL);
EXPECT_EQ("audio", audio_desc->media_->type_);
EXPECT_EQ(kAudioPayloadType, audio_desc->media_->pt_);
EXPECT_EQ("MPEG4-GENERIC", audio_desc->media_->name_); // Should use MPEG4-GENERIC for RTSP
EXPECT_EQ(44100, audio_desc->media_->sample_); // Should match srs_flv_srates[3]
// Verify audio payload specific properties
SrsAudioPayload *audio_payload = dynamic_cast<SrsAudioPayload *>(audio_desc->media_);
EXPECT_TRUE(audio_payload != NULL);
EXPECT_EQ(2, audio_payload->channel_); // Stereo
// Verify AAC config hex is set
EXPECT_TRUE(!audio_payload->aac_config_hex_.empty());
EXPECT_EQ("1210", audio_payload->aac_config_hex_); // Hex encoding of {0x12, 0x10}
// Verify builder's audio parameters
EXPECT_EQ(kAudioPayloadType, builder->audio_payload_type_);
EXPECT_EQ(44100, builder->audio_sample_rate_);
}
// Test SrsRtspRtpBuilder::initialize_video_track with H.264 codec
// This test covers the major use scenario: initializing video track with H.264 codec
VOID TEST(SrsRtspRtpBuilderTest, InitializeVideoTrackH264)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup video format with H.264 codec
// Note: vcodec_ is created lazily in on_video(), so we need to create it manually for testing
builder->format_->vcodec_ = new SrsVideoCodecConfig();
builder->format_->vcodec_->id_ = SrsVideoCodecIdAVC;
builder->format_->vcodec_->avc_profile_ = SrsAvcProfileBaseline;
builder->format_->vcodec_->avc_level_ = SrsAvcLevel_3;
builder->format_->vcodec_->width_ = 1920;
builder->format_->vcodec_->height_ = 1080;
// Create video parsed packet
if (!builder->format_->video_) {
builder->format_->video_ = new SrsParsedVideoPacket();
}
builder->format_->video_->frame_type_ = SrsVideoAvcFrameTypeKeyFrame;
builder->format_->video_->avc_packet_type_ = SrsVideoAvcFrameTraitSequenceHeader;
// Manually set up the meta cache vformat_ to avoid complex SPS/PPS parsing
// The initialize_video_track method only needs vsh_format() to return a valid format
if (!builder->meta_->vformat_) {
builder->meta_->vformat_ = new SrsRtmpFormat();
}
// Create a copy of vcodec_ to avoid double-free issue
builder->meta_->vformat_->vcodec_ = new SrsVideoCodecConfig();
builder->meta_->vformat_->vcodec_->id_ = builder->format_->vcodec_->id_;
builder->meta_->vformat_->vcodec_->avc_profile_ = builder->format_->vcodec_->avc_profile_;
builder->meta_->vformat_->vcodec_->avc_level_ = builder->format_->vcodec_->avc_level_;
builder->meta_->vformat_->vcodec_->width_ = builder->format_->vcodec_->width_;
builder->meta_->vformat_->vcodec_->height_ = builder->format_->vcodec_->height_;
// Call initialize_video_track with H.264 codec
HELPER_EXPECT_SUCCESS(builder->initialize_video_track(SrsVideoCodecIdAVC));
// Verify video track description was set to source
SrsRtcTrackDescription *video_desc = source->video_desc();
EXPECT_TRUE(video_desc != NULL);
// Verify track description properties
EXPECT_EQ("video", video_desc->type_);
EXPECT_TRUE(!video_desc->id_.empty());
EXPECT_TRUE(video_desc->id_.find("video-H264-") == 0); // Should start with "video-H264-"
EXPECT_EQ("recvonly", video_desc->direction_);
// Verify SSRC was generated
EXPECT_TRUE(video_desc->ssrc_ != 0);
// Verify media payload
EXPECT_TRUE(video_desc->media_ != NULL);
EXPECT_EQ("video", video_desc->media_->type_);
EXPECT_EQ(kVideoPayloadType, video_desc->media_->pt_);
EXPECT_EQ("H264", video_desc->media_->name_);
EXPECT_EQ(90000, video_desc->media_->sample_); // kVideoSamplerate = 90000
// Verify video payload specific properties
SrsVideoPayload *video_payload = dynamic_cast<SrsVideoPayload *>(video_desc->media_);
EXPECT_TRUE(video_payload != NULL);
// Verify H.264 parameters are set correctly
EXPECT_EQ("42e01f", video_payload->h264_param_.profile_level_id_);
EXPECT_EQ("1", video_payload->h264_param_.packetization_mode_);
EXPECT_EQ("1", video_payload->h264_param_.level_asymmetry_allow_);
}
// Test SrsRtspRtpBuilder initialize, on_publish, and on_unpublish lifecycle
// This test covers the major use scenario: initializing the builder, publishing, and unpublishing
VOID TEST(SrsRtspRtpBuilderTest, InitializePublishUnpublishLifecycle)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsRtspSource *source = new SrsRtspSource();
SrsSharedPtr<SrsRtspSource> shared_source(source);
// Create mock request
MockSrsRequest mock_req("test.vhost", "live", "livestream");
// Initialize source
HELPER_EXPECT_SUCCESS(source->initialize(&mock_req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, shared_source));
// Create mock config with try_annexb_first setting
MockAppConfig mock_config;
// Inject mock config
builder->config_ = &mock_config;
// Test 1: initialize() - should set up format and config
HELPER_EXPECT_SUCCESS(builder->initialize(&mock_req));
// Verify request was stored
EXPECT_TRUE(builder->req_ == &mock_req);
// Verify format was initialized
EXPECT_TRUE(builder->format_ != NULL);
// Verify try_annexb_first was set from config
EXPECT_TRUE(builder->format_->try_annexb_first_ == true);
// Test 2: on_publish() - should clear metadata cache
// First, manually set up some metadata to verify it gets cleared
// Create a dummy metadata packet
SrsUniquePtr<SrsMediaPacket> meta_packet(new SrsMediaPacket());
meta_packet->timestamp_ = 1000;
meta_packet->message_type_ = SrsFrameTypeScript;
char *meta_data = new char[10];
for (int i = 0; i < 10; i++) {
meta_data[i] = 0xAA;
}
meta_packet->wrap(meta_data, 10);
// Manually set metadata in cache (simulating previous publish)
builder->meta_->meta_ = meta_packet->copy();
// Verify metadata exists before on_publish
EXPECT_TRUE(builder->meta_->data() != NULL);
// Call on_publish
HELPER_EXPECT_SUCCESS(builder->on_publish());
// Verify metadata was cleared
EXPECT_TRUE(builder->meta_->data() == NULL);
// Test 3: on_unpublish() - should update previous sequence headers
// Set up video and audio sequence headers
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->timestamp_ = 0;
video_sh->message_type_ = SrsFrameTypeVideo;
char *video_data = new char[20];
for (int i = 0; i < 20; i++) {
video_data[i] = 0xBB;
}
video_sh->wrap(video_data, 20);
SrsUniquePtr<SrsMediaPacket> audio_sh(new SrsMediaPacket());
audio_sh->timestamp_ = 0;
audio_sh->message_type_ = SrsFrameTypeAudio;
char *audio_data = new char[15];
for (int i = 0; i < 15; i++) {
audio_data[i] = 0xCC;
}
audio_sh->wrap(audio_data, 15);
// Set sequence headers in cache
builder->meta_->video_ = video_sh->copy();
builder->meta_->audio_ = audio_sh->copy();
// Verify sequence headers exist
EXPECT_TRUE(builder->meta_->vsh() != NULL);
EXPECT_TRUE(builder->meta_->ash() != NULL);
// Verify previous sequence headers are NULL before on_unpublish
EXPECT_TRUE(builder->meta_->previous_vsh() == NULL);
EXPECT_TRUE(builder->meta_->previous_ash() == NULL);
// Call on_unpublish
builder->on_unpublish();
// Verify previous sequence headers were updated (copied from current)
EXPECT_TRUE(builder->meta_->previous_vsh() != NULL);
EXPECT_TRUE(builder->meta_->previous_ash() != NULL);
// Verify previous sequence headers have correct data
EXPECT_EQ(20, builder->meta_->previous_vsh()->size());
EXPECT_EQ(15, builder->meta_->previous_ash()->size());
EXPECT_EQ(0, memcmp(builder->meta_->previous_vsh()->payload(), video_data, 20));
EXPECT_EQ(0, memcmp(builder->meta_->previous_ash()->payload(), audio_data, 15));
// Restore global config
builder->config_ = _srs_config;
}
// Test SrsRtspRtpBuilder::on_frame and on_audio - covers the major use scenario:
// 1. Process AAC sequence header to initialize audio track
// 2. Process AAC raw data frame to generate RTP packet
// 3. Verify RTP packet is sent to target
VOID TEST(SrsRtspRtpBuilderTest, OnFrameAndOnAudioAAC)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Step 1: Create and process AAC sequence header to initialize audio track
SrsUniquePtr<SrsMediaPacket> aac_seq_header(new SrsMediaPacket());
aac_seq_header->message_type_ = SrsFrameTypeAudio;
aac_seq_header->timestamp_ = 0;
// Create AAC sequence header data
// Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][AudioSpecificConfig]
char *seq_data = new char[4];
seq_data[0] = 0xAF; // AAC(10), 44kHz(10), 16-bit(1), stereo(1)
seq_data[1] = 0x00; // AAC sequence header
seq_data[2] = 0x12; // AudioSpecificConfig: AAC-LC, 44.1kHz
seq_data[3] = 0x10; // AudioSpecificConfig: stereo
aac_seq_header->wrap(seq_data, 4);
// Process sequence header through on_frame (which calls on_audio)
HELPER_EXPECT_SUCCESS(builder->on_frame(aac_seq_header.get()));
// Verify audio track was initialized
SrsRtcTrackDescription *audio_desc = source->audio_desc();
EXPECT_TRUE(audio_desc != NULL);
EXPECT_EQ("audio", audio_desc->type_);
EXPECT_TRUE(builder->audio_initialized_);
// Verify no RTP packet was sent for sequence header
EXPECT_EQ(0, mock_target.on_rtp_count_);
// Step 2: Create and process AAC raw data frame
SrsUniquePtr<SrsMediaPacket> aac_frame(new SrsMediaPacket());
aac_frame->message_type_ = SrsFrameTypeAudio;
aac_frame->timestamp_ = 1000; // 1 second
// Create AAC raw data frame
// Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][raw_aac_data]
char *frame_data = new char[10];
frame_data[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo
frame_data[1] = 0x01; // AAC raw data (not sequence header)
// Add some AAC raw data
frame_data[2] = 0x21;
frame_data[3] = 0x10;
frame_data[4] = 0x05;
frame_data[5] = 0xAA;
frame_data[6] = 0xBB;
frame_data[7] = 0xCC;
frame_data[8] = 0xDD;
frame_data[9] = 0xEE;
aac_frame->wrap(frame_data, 10);
// Process AAC frame through on_frame (which calls on_audio)
HELPER_EXPECT_SUCCESS(builder->on_frame(aac_frame.get()));
// Step 3: Verify RTP packet was sent to target
// Note: We only verify the count because the RTP packet is freed after on_rtp() returns
EXPECT_EQ(1, mock_target.on_rtp_count_);
// Verify audio track description has correct SSRC and payload type
EXPECT_TRUE(audio_desc->ssrc_ != 0);
EXPECT_EQ(kAudioPayloadType, audio_desc->media_->pt_);
EXPECT_EQ(44100, audio_desc->media_->sample_);
}
// Test SrsRtspRtpBuilder::package_aac - covers the major use scenario:
// 1. Create a parsed audio packet with multiple AAC samples
// 2. Call package_aac to generate RTP packet with RFC 3640 AAC-hbr payload
// 3. Verify RTP header fields (payload type, SSRC, marker, sequence, timestamp)
// 4. Verify RFC 3640 payload structure (AU-headers-length, AU-headers, AU data)
VOID TEST(SrsRtspRtpBuilderTest, PackageAacMultipleSamples)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup audio format with AAC codec
// Note: acodec_ is created lazily in on_audio(), so we need to create it manually for testing
builder->format_->acodec_ = new SrsAudioCodecConfig();
builder->format_->acodec_->id_ = SrsAudioCodecIdAAC;
builder->format_->acodec_->sound_rate_ = SrsAudioSampleRate44100; // Index 3 = 44100Hz
builder->format_->acodec_->sound_type_ = SrsAudioChannelsStereo;
builder->format_->acodec_->aac_channels_ = 2;
// Create AAC AudioSpecificConfig: AAC-LC, 44100Hz, stereo
char aac_config[] = {0x12, 0x10};
builder->format_->acodec_->aac_extra_data_.assign(aac_config, aac_config + sizeof(aac_config));
// Initialize audio track with AAC codec
HELPER_EXPECT_SUCCESS(builder->initialize_audio_track(SrsAudioCodecIdAAC));
// Create a parsed audio packet with multiple AAC samples
SrsUniquePtr<SrsParsedAudioPacket> audio(new SrsParsedAudioPacket());
audio->dts_ = 1000; // 1 second in milliseconds (FLV TBN=1000)
// Add 3 AAC samples with different sizes
char sample1_data[] = {0x21, 0x10, 0x05, (char)0xAA, (char)0xBB};
char sample2_data[] = {0x21, 0x10, 0x06, (char)0xCC, (char)0xDD, (char)0xEE};
char sample3_data[] = {0x21, 0x10, 0x07, (char)0xFF, 0x11, 0x22, 0x33};
audio->nb_samples_ = 3;
audio->samples_[0].bytes_ = sample1_data;
audio->samples_[0].size_ = sizeof(sample1_data);
audio->samples_[1].bytes_ = sample2_data;
audio->samples_[1].size_ = sizeof(sample2_data);
audio->samples_[2].bytes_ = sample3_data;
audio->samples_[2].size_ = sizeof(sample3_data);
// Create RTP packet
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
// Call package_aac
HELPER_EXPECT_SUCCESS(builder->package_aac(audio.get(), pkt.get()));
// Verify RTP header fields
EXPECT_EQ(kAudioPayloadType, pkt->header_.get_payload_type());
EXPECT_TRUE(pkt->header_.get_ssrc() != 0);
EXPECT_EQ(SrsFrameTypeAudio, pkt->frame_type_);
EXPECT_TRUE(pkt->header_.get_marker());
EXPECT_EQ(0, pkt->header_.get_sequence()); // First packet, sequence should be 0
// Verify timestamp conversion from FLV TBN(1000) to sample rate TBN(44100)
// Expected: 1000ms * 44100 / 1000 = 44100
EXPECT_EQ(44100, (int)pkt->header_.get_timestamp());
// Verify payload structure according to RFC 3640 AAC-hbr mode
SrsRtpRawPayload *raw = dynamic_cast<SrsRtpRawPayload *>(pkt->payload());
EXPECT_TRUE(raw != NULL);
EXPECT_TRUE(raw->payload_ != NULL);
// Calculate expected payload size
int total_au_size = sizeof(sample1_data) + sizeof(sample2_data) + sizeof(sample3_data);
int au_headers_length = 3 * 16; // 3 samples * 16 bits per AU-header
int au_headers_bytes = (au_headers_length + 7) / 8; // 6 bytes
int expected_payload_size = 2 + au_headers_bytes + total_au_size; // AU-headers-length(2) + AU-headers(6) + AU data(18)
EXPECT_EQ(expected_payload_size, raw->nn_payload_);
// Parse and verify payload structure using SrsBuffer
SrsBuffer buffer(raw->payload_, raw->nn_payload_);
// Verify AU-headers-length (16 bits) - should be 48 bits (3 samples * 16 bits)
uint16_t au_headers_length_value = buffer.read_2bytes();
EXPECT_EQ(48, au_headers_length_value);
// Verify AU-headers for each sample
// Sample 0: size=5, index=0 -> (5 << 3) | 0 = 0x0028
uint16_t au_header0 = buffer.read_2bytes();
EXPECT_EQ((5 << 3) | 0, au_header0);
// Sample 1: size=6, index=1 -> (6 << 3) | 1 = 0x0031
uint16_t au_header1 = buffer.read_2bytes();
EXPECT_EQ((6 << 3) | 1, au_header1);
// Sample 2: size=7, index=2 -> (7 << 3) | 2 = 0x003A
uint16_t au_header2 = buffer.read_2bytes();
EXPECT_EQ((7 << 3) | 2, au_header2);
// Verify AU data for each sample
char read_sample1[5];
buffer.read_bytes(read_sample1, sizeof(read_sample1));
EXPECT_EQ(0, memcmp(read_sample1, sample1_data, sizeof(sample1_data)));
char read_sample2[6];
buffer.read_bytes(read_sample2, sizeof(read_sample2));
EXPECT_EQ(0, memcmp(read_sample2, sample2_data, sizeof(sample2_data)));
char read_sample3[7];
buffer.read_bytes(read_sample3, sizeof(read_sample3));
EXPECT_EQ(0, memcmp(read_sample3, sample3_data, sizeof(sample3_data)));
// Verify buffer is fully consumed
EXPECT_TRUE(buffer.empty());
}
// Test SrsRtspRtpBuilder::on_video - covers the major use scenario:
// 1. Process H.264 sequence header to cache SPS/PPS and initialize video track
// 2. Process IDR frame to generate STAP-A packet (SPS/PPS) and single NALU RTP packets
// 3. Verify RTP packets are sent to target with correct marker bit
VOID TEST(SrsRtspRtpBuilderTest, OnVideoH264IDRFrame)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Step 1: Create and process H.264 sequence header to initialize video track
SrsUniquePtr<SrsMediaPacket> h264_seq_header(new SrsMediaPacket());
h264_seq_header->message_type_ = SrsFrameTypeVideo;
// H.264 sequence header with SPS/PPS
uint8_t h264_seq_raw[] = {
0x17, // keyframe + AVC codec
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *h264_data = new char[sizeof(h264_seq_raw)];
memcpy(h264_data, h264_seq_raw, sizeof(h264_seq_raw));
h264_seq_header->wrap(h264_data, sizeof(h264_seq_raw));
h264_seq_header->timestamp_ = 1000;
// Process sequence header - should cache SPS/PPS and initialize video track
HELPER_EXPECT_SUCCESS(builder->on_video(h264_seq_header.get()));
// Verify video track is initialized
EXPECT_TRUE(builder->video_initialized_);
EXPECT_TRUE(source->video_desc() != NULL);
// Reset mock target counter for IDR frame test
mock_target.on_rtp_count_ = 0;
// Step 2: Create and process H.264 IDR frame
SrsUniquePtr<SrsMediaPacket> h264_idr_frame(new SrsMediaPacket());
h264_idr_frame->message_type_ = SrsFrameTypeVideo;
// IDR frame with single NALU (small enough to fit in single RTP packet)
uint8_t h264_frame_raw[] = {
0x17, // keyframe + AVC codec
0x01, // AVC NALU (not sequence header)
0x00, 0x00, 0x00, // composition time
0x00, 0x00, 0x00, 0x05, // NALU length (5 bytes)
0x65, 0x88, 0x84, 0x00, 0x10 // IDR slice data
};
char *frame_data = new char[sizeof(h264_frame_raw)];
memcpy(frame_data, h264_frame_raw, sizeof(h264_frame_raw));
h264_idr_frame->wrap(frame_data, sizeof(h264_frame_raw));
h264_idr_frame->timestamp_ = 2000;
// Process IDR frame - should generate STAP-A packet (SPS/PPS) + single NALU RTP packet
HELPER_EXPECT_SUCCESS(builder->on_video(h264_idr_frame.get()));
// Verify RTP packets were sent
// Expected: 1 STAP-A packet (SPS/PPS) + 1 single NALU packet (IDR)
EXPECT_EQ(2, mock_target.on_rtp_count_);
}
// Test SrsRtspRtpBuilder::filter - covers the major use scenario:
// 1. Process IDR frame with multiple NALU samples
// 2. Verify has_idr flag is set correctly
// 3. Verify all samples are collected in output vector
VOID TEST(SrsRtspRtpBuilderTest, FilterIDRFrameWithMultipleSamples)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Create a media packet
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
msg->message_type_ = SrsFrameTypeVideo;
// Create format with video samples
SrsFormat format;
format.video_ = new SrsParsedVideoPacket();
format.vcodec_ = new SrsVideoCodecConfig();
// Set IDR flag
format.video_->has_idr_ = true;
// Create multiple NALU samples (simulating SPS, PPS, IDR slice)
uint8_t sps_data[] = {0x67, 0x64, 0x00, 0x20, 0xac};
uint8_t pps_data[] = {0x68, 0xeb, 0xec, 0xb2};
uint8_t idr_data[] = {0x65, 0x88, 0x84, 0x00, 0x10};
SrsNaluSample sps_sample((char *)sps_data, sizeof(sps_data));
SrsNaluSample pps_sample((char *)pps_data, sizeof(pps_data));
SrsNaluSample idr_sample((char *)idr_data, sizeof(idr_data));
format.video_->samples_[0] = sps_sample;
format.video_->samples_[1] = pps_sample;
format.video_->samples_[2] = idr_sample;
format.video_->nb_samples_ = 3;
// Call filter method
bool has_idr = false;
std::vector<SrsNaluSample *> samples;
HELPER_EXPECT_SUCCESS(builder->filter(msg.get(), &format, has_idr, samples));
// Verify has_idr flag is set
EXPECT_TRUE(has_idr);
// Verify all samples are collected
EXPECT_EQ(3, (int)samples.size());
EXPECT_EQ(&format.video_->samples_[0], samples[0]);
EXPECT_EQ(&format.video_->samples_[1], samples[1]);
EXPECT_EQ(&format.video_->samples_[2], samples[2]);
// Cleanup
srs_freep(format.video_);
srs_freep(format.vcodec_);
}
// Test SrsRtspRtpBuilder::package_stap_a - covers the major use scenario:
// 1. Meta cache has valid video sequence header with vcodec
// 2. Successfully delegates to video_builder_->package_stap_a()
VOID TEST(SrsRtspRtpBuilderTest, PackageStapAWithValidVideoCodec)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup video sequence header in meta cache to populate vsh_format()
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->message_type_ = SrsFrameTypeVideo;
// Create H.264 sequence header with SPS/PPS
uint8_t h264_seq_raw[] = {
0x17, // keyframe + AVC codec
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *seq_data = new char[sizeof(h264_seq_raw)];
memcpy(seq_data, h264_seq_raw, sizeof(h264_seq_raw));
video_sh->wrap(seq_data, sizeof(h264_seq_raw));
video_sh->timestamp_ = 0;
// Update meta cache with video sequence header - this populates vsh_format() with vcodec
HELPER_EXPECT_SUCCESS(builder->meta_->update_vsh(video_sh.get()));
// Verify that vsh_format() returns valid format with vcodec
SrsFormat *format = builder->meta_->vsh_format();
EXPECT_TRUE(format != NULL);
EXPECT_TRUE(format->vcodec_ != NULL);
EXPECT_EQ(SrsVideoCodecIdAVC, format->vcodec_->id_);
// Initialize video track to set up video_builder_
HELPER_EXPECT_SUCCESS(builder->initialize_video_track(SrsVideoCodecIdAVC));
// Create a media packet for STAP-A packaging
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
msg->message_type_ = SrsFrameTypeVideo;
msg->timestamp_ = 1000;
// Create RTP packet to receive the STAP-A result
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
// Call package_stap_a - should succeed and delegate to video_builder_
HELPER_EXPECT_SUCCESS(builder->package_stap_a(msg.get(), pkt.get()));
// Verify that RTP packet was populated by video_builder_->package_stap_a()
// The packet should have video frame type and proper timestamp
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
EXPECT_EQ(1000 * 90, (int)pkt->header_.get_timestamp()); // timestamp * 90 for RTP
}
// Test SrsRtspRtpBuilder::package_nalus - covers the major use scenario:
// 1. Meta cache has valid video sequence header with vcodec
// 2. Successfully delegates to video_builder_->package_nalus() with multiple NALU samples
// 3. Verifies RTP packets are generated for the NALUs
VOID TEST(SrsRtspRtpBuilderTest, PackageNalusWithMultipleSamples)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup video sequence header in meta cache to populate vsh_format()
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->message_type_ = SrsFrameTypeVideo;
// Create H.264 sequence header with SPS/PPS
uint8_t h264_seq_raw[] = {
0x17, // keyframe + AVC codec
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *seq_data = new char[sizeof(h264_seq_raw)];
memcpy(seq_data, h264_seq_raw, sizeof(h264_seq_raw));
video_sh->wrap(seq_data, sizeof(h264_seq_raw));
video_sh->timestamp_ = 0;
// Update meta cache with video sequence header - this populates vsh_format() with vcodec
HELPER_EXPECT_SUCCESS(builder->meta_->update_vsh(video_sh.get()));
// Verify that vsh_format() returns valid format with vcodec
SrsFormat *format = builder->meta_->vsh_format();
EXPECT_TRUE(format != NULL);
EXPECT_TRUE(format->vcodec_ != NULL);
EXPECT_EQ(SrsVideoCodecIdAVC, format->vcodec_->id_);
// Initialize video track to set up video_builder_
HELPER_EXPECT_SUCCESS(builder->initialize_video_track(SrsVideoCodecIdAVC));
// Create a media packet for packaging NALUs
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
msg->message_type_ = SrsFrameTypeVideo;
msg->timestamp_ = 2000;
// Create multiple NALU samples (simulating IDR frame with multiple slices)
uint8_t nalu1_data[] = {0x65, 0x88, 0x84, 0x00, 0x10}; // IDR slice 1
uint8_t nalu2_data[] = {0x65, 0x88, 0x84, 0x00, 0x20}; // IDR slice 2
uint8_t nalu3_data[] = {0x65, 0x88, 0x84, 0x00, 0x30}; // IDR slice 3
SrsNaluSample nalu1_sample((char *)nalu1_data, sizeof(nalu1_data));
SrsNaluSample nalu2_sample((char *)nalu2_data, sizeof(nalu2_data));
SrsNaluSample nalu3_sample((char *)nalu3_data, sizeof(nalu3_data));
std::vector<SrsNaluSample *> samples;
samples.push_back(&nalu1_sample);
samples.push_back(&nalu2_sample);
samples.push_back(&nalu3_sample);
// Call package_nalus - should succeed and delegate to video_builder_
std::vector<SrsRtpPacket *> pkts;
HELPER_EXPECT_SUCCESS(builder->package_nalus(msg.get(), samples, pkts));
// Verify that RTP packets were generated
EXPECT_TRUE(pkts.size() > 0);
// Verify first RTP packet has correct properties
if (pkts.size() > 0) {
SrsRtpPacket *first_pkt = pkts[0];
EXPECT_EQ(SrsFrameTypeVideo, first_pkt->frame_type_);
EXPECT_EQ(2000 * 90, (int)first_pkt->header_.get_timestamp()); // timestamp * 90 for RTP
}
// Cleanup RTP packets
for (size_t i = 0; i < pkts.size(); i++) {
srs_freep(pkts[i]);
}
}
// Test SrsRtspRtpBuilder::package_single_nalu - covers the major use scenario:
// 1. Initialize video track with H.264 codec
// 2. Call package_single_nalu to package a single NALU into RTP packet
// 3. Verify RTP packet is generated with correct properties
VOID TEST(SrsRtspRtpBuilderTest, PackageSingleNalu)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup video sequence header in meta cache to populate vsh_format()
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->message_type_ = SrsFrameTypeVideo;
// Create H.264 sequence header with SPS/PPS
uint8_t h264_seq_raw[] = {
0x17, // keyframe + AVC codec
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *seq_data = new char[sizeof(h264_seq_raw)];
memcpy(seq_data, h264_seq_raw, sizeof(h264_seq_raw));
video_sh->wrap(seq_data, sizeof(h264_seq_raw));
video_sh->timestamp_ = 0;
// Update meta cache with video sequence header - this populates vsh_format() with vcodec
HELPER_EXPECT_SUCCESS(builder->meta_->update_vsh(video_sh.get()));
// Initialize video track to set up video_builder_
HELPER_EXPECT_SUCCESS(builder->initialize_video_track(SrsVideoCodecIdAVC));
// Create a media packet for packaging single NALU
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
msg->message_type_ = SrsFrameTypeVideo;
msg->timestamp_ = 3000;
// Create a single NALU sample (IDR slice)
uint8_t nalu_data[] = {0x65, 0x88, 0x84, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
SrsNaluSample nalu_sample((char *)nalu_data, sizeof(nalu_data));
// Call package_single_nalu - should succeed and delegate to video_builder_
std::vector<SrsRtpPacket *> pkts;
HELPER_EXPECT_SUCCESS(builder->package_single_nalu(msg.get(), &nalu_sample, pkts));
// Verify that exactly one RTP packet was generated
EXPECT_EQ(1, (int)pkts.size());
// Verify RTP packet has correct properties
if (pkts.size() > 0) {
SrsRtpPacket *pkt = pkts[0];
EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_);
EXPECT_EQ(3000 * 90, (int)pkt->header_.get_timestamp()); // timestamp * 90 for RTP
EXPECT_TRUE(pkt->header_.get_ssrc() != 0); // SSRC should be set
}
// Cleanup RTP packets
for (size_t i = 0; i < pkts.size(); i++) {
srs_freep(pkts[i]);
}
}
// Test SrsRtspRtpBuilder::package_fu_a - covers the major use scenario:
// 1. Meta cache has valid video sequence header with vcodec
// 2. Successfully delegates to video_builder_->package_fu_a() with large NALU that requires fragmentation
// 3. Verifies multiple RTP packets are generated with FU-A fragmentation
VOID TEST(SrsRtspRtpBuilderTest, PackageFuAWithLargeNalu)
{
srs_error_t err;
// Create mock RTP target
MockRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> source(new SrsRtspSource());
MockSrsRequest req("test.vhost", "live", "stream1");
HELPER_EXPECT_SUCCESS(source->initialize(&req));
// Create SrsRtspRtpBuilder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, source));
HELPER_EXPECT_SUCCESS(builder->initialize(&req));
// Setup video sequence header in meta cache to populate vsh_format()
SrsUniquePtr<SrsMediaPacket> video_sh(new SrsMediaPacket());
video_sh->message_type_ = SrsFrameTypeVideo;
// H.264 sequence header with valid SPS/PPS
uint8_t video_sh_data[] = {
0x17, // keyframe + AVC codec
0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20,
0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c};
char *video_sh_buf = new char[sizeof(video_sh_data)];
memcpy(video_sh_buf, video_sh_data, sizeof(video_sh_data));
video_sh->wrap(video_sh_buf, sizeof(video_sh_data));
video_sh->timestamp_ = 0;
// Update meta cache with video sequence header - this populates vsh_format() with vcodec
HELPER_EXPECT_SUCCESS(builder->meta_->update_vsh(video_sh.get()));
// Initialize video track to set up video_builder_
HELPER_EXPECT_SUCCESS(builder->initialize_video_track(SrsVideoCodecIdAVC));
// Create a media packet for packaging FU-A
SrsUniquePtr<SrsMediaPacket> msg(new SrsMediaPacket());
msg->message_type_ = SrsFrameTypeVideo;
msg->timestamp_ = 2000;
// Create a large NALU sample (IDR slice) that requires fragmentation
// NALU header: 0x65 (IDR slice), followed by large payload
int large_nalu_size = 2500; // Large enough to require FU-A fragmentation
uint8_t *large_nalu_data = new uint8_t[large_nalu_size];
large_nalu_data[0] = 0x65; // IDR slice NALU type
for (int i = 1; i < large_nalu_size; i++) {
large_nalu_data[i] = (uint8_t)(i % 256); // Fill with test data
}
SrsNaluSample large_nalu_sample((char *)large_nalu_data, large_nalu_size);
// Call package_fu_a with small payload size to force fragmentation
std::vector<SrsRtpPacket *> pkts;
int fu_payload_size = 800; // Smaller than NALU size to force multiple fragments
HELPER_EXPECT_SUCCESS(builder->package_fu_a(msg.get(), &large_nalu_sample, fu_payload_size, pkts));
// Verify that multiple RTP packets were generated (FU-A fragmentation)
EXPECT_GT((int)pkts.size(), 1);
// Verify first packet has correct properties
EXPECT_EQ(SrsFrameTypeVideo, pkts[0]->frame_type_);
EXPECT_EQ(2000 * 90, (int)pkts[0]->header_.get_timestamp()); // timestamp * 90 for RTP
EXPECT_EQ(kFuA, pkts[0]->nalu_type_); // FU-A packet type
// Verify all packets have sequential sequence numbers
for (size_t i = 1; i < pkts.size(); i++) {
EXPECT_EQ(pkts[i - 1]->header_.get_sequence() + 1, pkts[i]->header_.get_sequence());
}
// Cleanup
srs_freepa(large_nalu_data);
for (size_t i = 0; i < pkts.size(); i++) {
srs_freep(pkts[i]);
}
}
// Mock RTP target implementation
MockRtspRtpTarget::MockRtspRtpTarget()
{
on_rtp_count_ = 0;
last_rtp_ = NULL;
rtp_error_ = srs_success;
}
MockRtspRtpTarget::~MockRtspRtpTarget()
{
srs_freep(rtp_error_);
}
srs_error_t MockRtspRtpTarget::on_rtp(SrsRtpPacket *pkt)
{
on_rtp_count_++;
last_rtp_ = pkt;
return srs_error_copy(rtp_error_);
}
void MockRtspRtpTarget::set_rtp_error(srs_error_t err)
{
srs_freep(rtp_error_);
rtp_error_ = srs_error_copy(err);
}
void MockRtspRtpTarget::reset()
{
on_rtp_count_ = 0;
last_rtp_ = NULL;
srs_freep(rtp_error_);
}
// Test SrsRtspRtpBuilder::consume_packets functionality
// This test covers the major use scenario: consuming multiple RTP packets and error handling
VOID TEST(RtspRtpBuilderTest, ConsumePackets)
{
srs_error_t err;
// Create mock RTP target
MockRtspRtpTarget mock_target;
// Create RTSP source
SrsSharedPtr<SrsRtspSource> rtsp_source(new SrsRtspSource());
SrsUniquePtr<MockRtcAsyncCallRequest> req(new MockRtcAsyncCallRequest("test.vhost", "live", "stream1"));
HELPER_EXPECT_SUCCESS(rtsp_source->initialize(req.get()));
// Create RTSP RTP builder
SrsUniquePtr<SrsRtspRtpBuilder> builder(new SrsRtspRtpBuilder(&mock_target, rtsp_source));
HELPER_EXPECT_SUCCESS(builder->initialize(req.get()));
// Scenario 1: Consume multiple RTP packets successfully
vector<SrsRtpPacket *> pkts;
// Create first RTP packet
SrsRtpPacket *pkt1 = new SrsRtpPacket();
pkt1->header_.set_ssrc(12345);
pkt1->header_.set_sequence(100);
pkt1->header_.set_timestamp(90000);
pkts.push_back(pkt1);
// Create second RTP packet
SrsRtpPacket *pkt2 = new SrsRtpPacket();
pkt2->header_.set_ssrc(12345);
pkt2->header_.set_sequence(101);
pkt2->header_.set_timestamp(93600);
pkts.push_back(pkt2);
// Create third RTP packet
SrsRtpPacket *pkt3 = new SrsRtpPacket();
pkt3->header_.set_ssrc(12345);
pkt3->header_.set_sequence(102);
pkt3->header_.set_timestamp(97200);
pkts.push_back(pkt3);
// Consume packets - should succeed
HELPER_EXPECT_SUCCESS(builder->consume_packets(pkts));
// Verify all packets were consumed
EXPECT_EQ(3, mock_target.on_rtp_count_);
EXPECT_EQ(pkt3, mock_target.last_rtp_); // Last packet should be pkt3
// Cleanup
for (size_t i = 0; i < pkts.size(); i++) {
srs_freep(pkts[i]);
}
pkts.clear();
// Scenario 2: Error handling - on_rtp fails on second packet
mock_target.reset();
// Create new packets
SrsRtpPacket *pkt4 = new SrsRtpPacket();
pkt4->header_.set_ssrc(12345);
pkt4->header_.set_sequence(103);
pkts.push_back(pkt4);
SrsRtpPacket *pkt5 = new SrsRtpPacket();
pkt5->header_.set_ssrc(12345);
pkt5->header_.set_sequence(104);
pkts.push_back(pkt5);
SrsRtpPacket *pkt6 = new SrsRtpPacket();
pkt6->header_.set_ssrc(12345);
pkt6->header_.set_sequence(105);
pkts.push_back(pkt6);
// Set error to occur on second packet (after first succeeds)
// First packet will succeed (on_rtp_count_ becomes 1)
// Second packet will fail
mock_target.set_rtp_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock rtp error"));
// Consume packets - should fail on second packet
HELPER_EXPECT_FAILED(builder->consume_packets(pkts));
// Verify only first packet was consumed before error
EXPECT_EQ(1, mock_target.on_rtp_count_);
EXPECT_EQ(pkt4, mock_target.last_rtp_);
// Cleanup
for (size_t i = 0; i < pkts.size(); i++) {
srs_freep(pkts[i]);
}
}
// Test SrsRtspAudioSendTrack::on_rtp - covers the major use scenario:
// 1. Active track with media payload type conversion from publisher PT to subscriber PT
// 2. Updates SSRC and payload type correctly
// 3. Tests the core logic of PT conversion and SSRC update
VOID TEST(SrsRtspAudioSendTrackTest, OnRtpWithPayloadTypeConversion)
{
// Create track description for audio
SrsUniquePtr<SrsRtcTrackDescription> track_desc(new SrsRtcTrackDescription());
track_desc->type_ = "audio";
track_desc->id_ = "audio-track-1";
track_desc->ssrc_ = 88888888;
track_desc->is_active_ = true;
// Setup media payload: publisher uses PT 111, subscriber uses PT 96
SrsAudioPayload *media_payload = new SrsAudioPayload(96, "opus", 48000, 2);
media_payload->pt_of_publisher_ = 111; // Publisher's PT
media_payload->pt_ = 96; // Subscriber's PT
track_desc->set_codec_payload(media_payload);
// Create RTP packet with publisher's PT
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_ssrc(12345678); // Original SSRC (will be changed)
pkt->header_.set_sequence(100);
pkt->header_.set_timestamp(48000);
pkt->header_.set_payload_type(111); // Publisher's PT
// Test the core logic: SSRC update
// Simulate what on_rtp does: update SSRC
pkt->header_.set_ssrc(track_desc->ssrc_);
EXPECT_EQ(88888888, (int)pkt->header_.get_ssrc());
// Test the core logic: PT conversion from publisher to subscriber
// Simulate what on_rtp does: check and update PT
if (track_desc->media_ && pkt->header_.get_payload_type() == track_desc->media_->pt_of_publisher_) {
pkt->header_.set_payload_type(track_desc->media_->pt_);
}
// Verify payload type was converted from publisher PT (111) to subscriber PT (96)
EXPECT_EQ(96, (int)pkt->header_.get_payload_type());
// Verify other fields remain unchanged
EXPECT_EQ(100, (int)pkt->header_.get_sequence());
EXPECT_EQ(48000, (int)pkt->header_.get_timestamp());
// Test scenario 2: Inactive track should not process packet
track_desc->is_active_ = false;
// Reset packet PT to publisher's PT
pkt->header_.set_payload_type(111);
// When track is inactive, the on_rtp method returns early without modifying the packet
// We can verify this by checking that PT remains unchanged if we skip the processing
// (simulating the early return when !track_desc_->is_active_)
if (track_desc->is_active_) {
// This block won't execute because track is inactive
pkt->header_.set_payload_type(track_desc->media_->pt_);
}
// Verify PT was NOT converted (remains at publisher's PT)
EXPECT_EQ(111, (int)pkt->header_.get_payload_type());
}
// Test SrsRtspVideoSendTrack::on_rtp - covers the major use scenario:
// 1. Active track with media payload type conversion from publisher PT to subscriber PT
// 2. Updates SSRC and payload type correctly for video track
// 3. Tests the core logic of PT conversion and SSRC update for video
VOID TEST(SrsRtspVideoSendTrackTest, OnRtpWithPayloadTypeConversion)
{
// Create track description for video
SrsUniquePtr<SrsRtcTrackDescription> track_desc(new SrsRtcTrackDescription());
track_desc->type_ = "video";
track_desc->id_ = "video-track-1";
track_desc->ssrc_ = 99999999;
track_desc->is_active_ = true;
// Setup media payload: publisher uses PT 102, subscriber uses PT 97
SrsVideoPayload *media_payload = new SrsVideoPayload(97, "H264", 90000);
media_payload->pt_of_publisher_ = 102; // Publisher's PT
media_payload->pt_ = 97; // Subscriber's PT
track_desc->set_codec_payload(media_payload);
// Create RTP packet with publisher's PT
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_ssrc(87654321); // Original SSRC (will be changed)
pkt->header_.set_sequence(200);
pkt->header_.set_timestamp(90000);
pkt->header_.set_payload_type(102); // Publisher's PT
pkt->header_.set_marker(true);
// Test the core logic: SSRC update
// Simulate what on_rtp does: update SSRC
pkt->header_.set_ssrc(track_desc->ssrc_);
EXPECT_EQ(99999999, (int)pkt->header_.get_ssrc());
// Test the core logic: PT conversion from publisher to subscriber
// Simulate what on_rtp does: check and update PT
if (track_desc->media_ && pkt->header_.get_payload_type() == track_desc->media_->pt_of_publisher_) {
pkt->header_.set_payload_type(track_desc->media_->pt_);
}
// Verify payload type was converted from publisher PT (102) to subscriber PT (97)
EXPECT_EQ(97, (int)pkt->header_.get_payload_type());
// Verify other fields remain unchanged
EXPECT_EQ(200, (int)pkt->header_.get_sequence());
EXPECT_EQ(90000, (int)pkt->header_.get_timestamp());
EXPECT_TRUE(pkt->header_.get_marker());
// Test scenario 2: Inactive track should not process packet
track_desc->is_active_ = false;
// Reset packet PT to publisher's PT
pkt->header_.set_payload_type(102);
// When track is inactive, the on_rtp method returns early without modifying the packet
// We can verify this by checking that PT remains unchanged if we skip the processing
// (simulating the early return when !track_desc_->is_active_)
if (track_desc->is_active_) {
// This block won't execute because track is inactive
pkt->header_.set_payload_type(track_desc->media_->pt_);
}
// Verify PT was NOT converted (remains at publisher's PT)
EXPECT_EQ(102, (int)pkt->header_.get_payload_type());
}
MockRtspConnection::MockRtspConnection()
{
do_send_packet_count_ = 0;
last_packet_ = NULL;
send_error_ = srs_success;
}
MockRtspConnection::~MockRtspConnection()
{
srs_freep(last_packet_);
srs_freep(send_error_);
}
srs_error_t MockRtspConnection::do_send_packet(SrsRtpPacket *pkt)
{
do_send_packet_count_++;
srs_freep(last_packet_);
if (pkt) {
last_packet_ = pkt->copy();
}
return srs_error_copy(send_error_);
}
void MockRtspConnection::set_send_error(srs_error_t err)
{
srs_freep(send_error_);
send_error_ = srs_error_copy(err);
}
void MockRtspConnection::reset()
{
do_send_packet_count_ = 0;
srs_freep(last_packet_);
srs_freep(send_error_);
}
VOID TEST(AppRtspTest, RtspSendTrackBasicOperations)
{
// Create a mock RTSP connection
MockRtspConnection mock_conn;
// Create a track description with specific properties
SrsUniquePtr<SrsRtcTrackDescription> track_desc(new SrsRtcTrackDescription());
track_desc->type_ = "video";
track_desc->id_ = "video-track-001";
track_desc->ssrc_ = 12345678;
track_desc->rtx_ssrc_ = 87654321;
track_desc->fec_ssrc_ = 11223344;
track_desc->is_active_ = true;
// Create video send track (using concrete class for testing)
SrsUniquePtr<SrsRtspVideoSendTrack> send_track(new SrsRtspVideoSendTrack(&mock_conn, track_desc.get()));
// Test 1: Verify track ID
EXPECT_EQ("video-track-001", send_track->get_track_id());
// Test 2: Verify initial track status (should be active)
EXPECT_TRUE(send_track->get_track_status());
// Test 3: Test has_ssrc with primary SSRC
EXPECT_TRUE(send_track->has_ssrc(12345678));
// Test 4: Test has_ssrc with RTX SSRC
EXPECT_TRUE(send_track->has_ssrc(87654321));
// Test 5: Test has_ssrc with FEC SSRC
EXPECT_TRUE(send_track->has_ssrc(11223344));
// Test 6: Test has_ssrc with non-existent SSRC
EXPECT_FALSE(send_track->has_ssrc(99999999));
// Test 7: Set track status to inactive and verify
bool previous_status = send_track->set_track_status(false);
EXPECT_TRUE(previous_status); // Previous status was true
EXPECT_FALSE(send_track->get_track_status()); // Current status is false
// Test 8: When track is inactive, has_ssrc should return false
EXPECT_FALSE(send_track->has_ssrc(12345678));
// Test 9: Set track status back to active
previous_status = send_track->set_track_status(true);
EXPECT_FALSE(previous_status); // Previous status was false
EXPECT_TRUE(send_track->get_track_status()); // Current status is true
// Test 10: After reactivating, has_ssrc should work again
EXPECT_TRUE(send_track->has_ssrc(12345678));
}
// Test SrsRtspAudioSendTrack::on_rtp - covers the major use scenario:
// Active track receives RTP packet, updates SSRC and PT, then sends via session
VOID TEST(SrsRtspAudioSendTrackTest, OnRtpActiveTrackWithPTConversion)
{
srs_error_t err;
// Create mock RTSP connection
MockRtspConnection mock_conn;
// Create track description for audio
SrsUniquePtr<SrsRtcTrackDescription> track_desc(new SrsRtcTrackDescription());
track_desc->type_ = "audio";
track_desc->id_ = "audio-track-1";
track_desc->ssrc_ = 88888888;
track_desc->is_active_ = true;
// Setup media payload: publisher uses PT 111, subscriber uses PT 96
SrsAudioPayload *media_payload = new SrsAudioPayload(96, "opus", 48000, 2);
media_payload->pt_of_publisher_ = 111; // Publisher's PT
media_payload->pt_ = 96; // Subscriber's PT
track_desc->set_codec_payload(media_payload);
// Create audio send track
SrsUniquePtr<SrsRtspAudioSendTrack> send_track(new SrsRtspAudioSendTrack(&mock_conn, track_desc.get()));
// Create RTP packet with publisher's PT
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
char *buf = pkt->wrap(100);
ASSERT_TRUE(buf != NULL);
pkt->header_.set_ssrc(12345678); // Original SSRC (will be changed)
pkt->header_.set_sequence(100);
pkt->header_.set_timestamp(48000);
pkt->header_.set_payload_type(111); // Publisher's PT
// Call on_rtp - this is the method under test
HELPER_EXPECT_SUCCESS(send_track->on_rtp(pkt.get()));
// Verify packet was sent to session
EXPECT_EQ(1, mock_conn.do_send_packet_count_);
ASSERT_TRUE(mock_conn.last_packet_ != NULL);
// Verify SSRC was updated to track's SSRC
EXPECT_EQ(88888888, (int)mock_conn.last_packet_->header_.get_ssrc());
// Verify PT was converted from publisher PT (111) to subscriber PT (96)
EXPECT_EQ(96, (int)mock_conn.last_packet_->header_.get_payload_type());
// Verify other fields remain unchanged
EXPECT_EQ(100, (int)mock_conn.last_packet_->header_.get_sequence());
EXPECT_EQ(48000, (int)mock_conn.last_packet_->header_.get_timestamp());
}
// Test SrsRtspVideoSendTrack::on_rtp - covers the major use scenario:
// Active track receives RTP packet, updates SSRC and PT, then sends via session
VOID TEST(SrsRtspVideoSendTrackTest, OnRtpActiveTrackWithPTConversion)
{
srs_error_t err;
// Create mock RTSP connection
MockRtspConnection mock_conn;
// Create track description for video
SrsUniquePtr<SrsRtcTrackDescription> track_desc(new SrsRtcTrackDescription());
track_desc->type_ = "video";
track_desc->id_ = "video-track-1";
track_desc->ssrc_ = 99999999;
track_desc->is_active_ = true;
// Setup media payload: publisher uses PT 102, subscriber uses PT 97
SrsVideoPayload *media_payload = new SrsVideoPayload(97, "H264", 90000);
media_payload->pt_of_publisher_ = 102; // Publisher's PT
media_payload->pt_ = 97; // Subscriber's PT
track_desc->set_codec_payload(media_payload);
// Create video send track
SrsUniquePtr<SrsRtspVideoSendTrack> send_track(new SrsRtspVideoSendTrack(&mock_conn, track_desc.get()));
// Create RTP packet with publisher's PT
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
char *buf = pkt->wrap(200);
ASSERT_TRUE(buf != NULL);
pkt->header_.set_ssrc(11111111); // Original SSRC (will be changed)
pkt->header_.set_sequence(500);
pkt->header_.set_timestamp(180000);
pkt->header_.set_payload_type(102); // Publisher's PT
pkt->header_.set_marker(true);
// Call on_rtp - this is the method under test
HELPER_EXPECT_SUCCESS(send_track->on_rtp(pkt.get()));
// Verify packet was sent to session
EXPECT_EQ(1, mock_conn.do_send_packet_count_);
ASSERT_TRUE(mock_conn.last_packet_ != NULL);
// Verify SSRC was updated to track's SSRC
EXPECT_EQ(99999999, (int)mock_conn.last_packet_->header_.get_ssrc());
// Verify PT was converted from publisher PT (102) to subscriber PT (97)
EXPECT_EQ(97, (int)mock_conn.last_packet_->header_.get_payload_type());
// Verify other fields remain unchanged
EXPECT_EQ(500, (int)mock_conn.last_packet_->header_.get_sequence());
EXPECT_EQ(180000, (int)mock_conn.last_packet_->header_.get_timestamp());
EXPECT_TRUE(mock_conn.last_packet_->header_.get_marker());
}