// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include using namespace std; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 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 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 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 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 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 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 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 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 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 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 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 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 channel(new SrsTsChannel()); channel->apply_ = SrsTsPidApplyVideo; channel->stream_ = SrsTsStreamVideoH264; SrsUniquePtr 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 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 channel(new SrsTsChannel()); channel->apply_ = SrsTsPidApplyVideo; channel->stream_ = SrsTsStreamVideoHEVC; SrsUniquePtr 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 builder(new SrsSrtFrameBuilder(&mock_target)); // Create a TsMessage with valid timestamp SrsUniquePtr 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 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 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 > 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 builder(new SrsSrtFrameBuilder(&mock_target)); // Create a mock TsMessage with valid DTS/PTS (in 90kHz timebase) SrsUniquePtr 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 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 channel(new SrsTsChannel()); channel->apply_ = SrsTsPidApplyVideo; channel->stream_ = SrsTsStreamVideoHEVC; // Create a TS message with HEVC video data SrsUniquePtr 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 > 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 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 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 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 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 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 channel(new SrsTsChannel()); channel->apply_ = SrsTsPidApplyAudio; channel->stream_ = SrsTsStreamAudioAAC; // Create a TS message (the actual message content doesn't matter for this test) SrsUniquePtr 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 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 builder(new SrsSrtFrameBuilder(&mock_target)); // Create a mock SrsTsMessage (only used for context, not directly accessed in on_aac_frame) SrsUniquePtr 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 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 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(consumer1); SrsSrtConsumer *consumer2_impl = dynamic_cast(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 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 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 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 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 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 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 source1; SrsSharedPtr source2; SrsSharedPtr 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 fetched1 = manager->fetch(&req1); SrsSharedPtr fetched2 = manager->fetch(&req2); SrsSharedPtr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr 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(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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr 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(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 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 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 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr 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 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 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(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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, source)); HELPER_EXPECT_SUCCESS(builder->initialize(&req)); // Create a media packet SrsUniquePtr 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, source)); HELPER_EXPECT_SUCCESS(builder->initialize(&req)); // Setup video sequence header in meta cache to populate vsh_format() SrsUniquePtr 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 msg(new SrsMediaPacket()); msg->message_type_ = SrsFrameTypeVideo; msg->timestamp_ = 1000; // Create RTP packet to receive the STAP-A result SrsUniquePtr 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, source)); HELPER_EXPECT_SUCCESS(builder->initialize(&req)); // Setup video sequence header in meta cache to populate vsh_format() SrsUniquePtr 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 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, source)); HELPER_EXPECT_SUCCESS(builder->initialize(&req)); // Setup video sequence header in meta cache to populate vsh_format() SrsUniquePtr 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 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 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 source(new SrsRtspSource()); MockSrsRequest req("test.vhost", "live", "stream1"); HELPER_EXPECT_SUCCESS(source->initialize(&req)); // Create SrsRtspRtpBuilder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, source)); HELPER_EXPECT_SUCCESS(builder->initialize(&req)); // Setup video sequence header in meta cache to populate vsh_format() SrsUniquePtr 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 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 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 rtsp_source(new SrsRtspSource()); SrsUniquePtr req(new MockRtcAsyncCallRequest("test.vhost", "live", "stream1")); HELPER_EXPECT_SUCCESS(rtsp_source->initialize(req.get())); // Create RTSP RTP builder SrsUniquePtr builder(new SrsRtspRtpBuilder(&mock_target, rtsp_source)); HELPER_EXPECT_SUCCESS(builder->initialize(req.get())); // Scenario 1: Consume multiple RTP packets successfully vector 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 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 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 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 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 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 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 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 send_track(new SrsRtspAudioSendTrack(&mock_conn, track_desc.get())); // Create RTP packet with publisher's PT SrsUniquePtr 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 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 send_track(new SrsRtspVideoSendTrack(&mock_conn, track_desc.get())); // Create RTP packet with publisher's PT SrsUniquePtr 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()); }