3545 lines
133 KiB
C++
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());
|
|
}
|