diff --git a/trunk/src/app/srs_app_rtsp_conn.cpp b/trunk/src/app/srs_app_rtsp_conn.cpp index 507b66aec..0af38fd35 100644 --- a/trunk/src/app/srs_app_rtsp_conn.cpp +++ b/trunk/src/app/srs_app_rtsp_conn.cpp @@ -347,6 +347,14 @@ void SrsRtspPlayStream::set_all_tracks_status(bool status) srs_trace("RTSP: Init tracks %s ok", merged_log.str().c_str()); } +ISrsRtspConnection::ISrsRtspConnection() +{ +} + +ISrsRtspConnection::~ISrsRtspConnection() +{ +} + SrsRtspConnection::SrsRtspConnection(ISrsResourceManager *cm, ISrsProtocolReadWriter *skt, std::string cip, int port) { manager_ = cm; diff --git a/trunk/src/app/srs_app_rtsp_conn.hpp b/trunk/src/app/srs_app_rtsp_conn.hpp index 8cf1e66db..220565f9e 100644 --- a/trunk/src/app/srs_app_rtsp_conn.hpp +++ b/trunk/src/app/srs_app_rtsp_conn.hpp @@ -88,7 +88,24 @@ public: void set_all_tracks_status(bool status); }; -class SrsRtspConnection : public ISrsResource, public ISrsDisposingHandler, public ISrsExpire, public ISrsCoroutineHandler, public ISrsStartable +// The handler for RTSP connection send packet. +class ISrsRtspConnection +{ +public: + ISrsRtspConnection(); + virtual ~ISrsRtspConnection(); + +public: + virtual srs_error_t do_send_packet(SrsRtpPacket *pkt) = 0; +}; + +// A RTSP session, client request and response with RTSP. +class SrsRtspConnection : public ISrsResource, // It's a resource. + public ISrsDisposingHandler, + public ISrsExpire, + public ISrsCoroutineHandler, + public ISrsStartable, + public ISrsRtspConnection { private: bool disposing_; diff --git a/trunk/src/app/srs_app_rtsp_source.cpp b/trunk/src/app/srs_app_rtsp_source.cpp index 21bb5698f..c92f9fcdb 100644 --- a/trunk/src/app/srs_app_rtsp_source.cpp +++ b/trunk/src/app/srs_app_rtsp_source.cpp @@ -250,6 +250,9 @@ SrsRtspSource::SrsRtspSource() req_ = NULL; stream_die_at_ = 0; + + stat_ = _srs_stat; + circuit_breaker_ = _srs_circuit_breaker; } SrsRtspSource::~SrsRtspSource() @@ -266,6 +269,9 @@ SrsRtspSource::~SrsRtspSource() if (cid.empty()) cid = _pre_source_id; srs_trace("free rtc source id=[%s]", cid.c_str()); + + stat_ = NULL; + circuit_breaker_ = NULL; } // CRITICAL: This method is called AFTER the source has been added to the source pool @@ -437,8 +443,7 @@ srs_error_t SrsRtspSource::on_publish() return srs_error_wrap(err, "source id change"); } - SrsStatistic *stat = _srs_stat; - stat->on_stream_publish(req_, _source_id.c_str()); + stat_->on_stream_publish(req_, _source_id.c_str()); return err; } @@ -457,8 +462,7 @@ void SrsRtspSource::on_unpublish() } _source_id = SrsContextId(); - SrsStatistic *stat = _srs_stat; - stat->on_stream_close(req_); + stat_->on_stream_close(req_); // Destroy and cleanup source when no publishers and consumers. if (consumers_.empty()) { @@ -475,7 +479,7 @@ srs_error_t SrsRtspSource::on_rtp(SrsRtpPacket *pkt) srs_error_t err = srs_success; // If circuit-breaker is dying, drop packet. - if (_srs_circuit_breaker->hybrid_dying_water_level()) { + if (circuit_breaker_->hybrid_dying_water_level()) { _srs_pps_aloss2->sugar_ += (int64_t)consumers_.size(); return err; } @@ -531,6 +535,8 @@ SrsRtspRtpBuilder::SrsRtspRtpBuilder(ISrsRtpTarget *target, SrsSharedPtrtry_annexb_first_ = _srs_config->try_annexb_first(r->vhost_); + format_->try_annexb_first_ = config_->try_annexb_first(r->vhost_); srs_trace("RTSP bridge from RTMP, try_annexb_first=%d", format_->try_annexb_first_); @@ -997,7 +1005,7 @@ srs_error_t SrsRtspRtpBuilder::consume_packets(vector &pkts) return err; } -SrsRtspSendTrack::SrsRtspSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc, bool is_audio) +SrsRtspSendTrack::SrsRtspSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc, bool is_audio) { session_ = session; track_desc_ = track_desc->copy(); @@ -1031,7 +1039,7 @@ std::string SrsRtspSendTrack::get_track_id() return track_desc_->id_; } -SrsRtspAudioSendTrack::SrsRtspAudioSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +SrsRtspAudioSendTrack::SrsRtspAudioSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) : SrsRtspSendTrack(session, track_desc, true) { } @@ -1071,7 +1079,7 @@ srs_error_t SrsRtspAudioSendTrack::on_rtp(SrsRtpPacket *pkt) return err; } -SrsRtspVideoSendTrack::SrsRtspVideoSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc) +SrsRtspVideoSendTrack::SrsRtspVideoSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc) : SrsRtspSendTrack(session, track_desc, false) { } diff --git a/trunk/src/app/srs_app_rtsp_source.hpp b/trunk/src/app/srs_app_rtsp_source.hpp index 8a074aa54..d8f7b1b92 100644 --- a/trunk/src/app/srs_app_rtsp_source.hpp +++ b/trunk/src/app/srs_app_rtsp_source.hpp @@ -26,6 +26,10 @@ class SrsRtcSourceDescription; class SrsResourceManager; class SrsRtspConnection; class SrsRtpVideoBuilder; +class ISrsStatistic; +class ISrsCircuitBreaker; +class ISrsAppConfig; +class ISrsRtspConnection; // The RTSP stream consumer, consume packets from RTSP stream source. class SrsRtspConsumer @@ -118,6 +122,10 @@ extern SrsResourceManager *_srs_rtsp_manager; // A Source is a stream, to publish and to play with, binding to SrsRtspPlayStream. class SrsRtspSource : public ISrsRtpTarget { +private: + ISrsStatistic *stat_; + ISrsCircuitBreaker *circuit_breaker_; + private: // For publish, it's the publish client id. // For edge, it's the edge ingest id. @@ -201,6 +209,9 @@ public: // Convert AV frame to RTSP RTP packets. class SrsRtspRtpBuilder { +private: + ISrsAppConfig *config_; + private: ISrsRequest *req_; ISrsRtpTarget *rtp_target_; @@ -264,10 +275,10 @@ public: protected: // The owner connection for this track. - SrsRtspConnection *session_; + ISrsRtspConnection *session_; public: - SrsRtspSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc, bool is_audio); + SrsRtspSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc, bool is_audio); virtual ~SrsRtspSendTrack(); public: @@ -284,7 +295,7 @@ public: class SrsRtspAudioSendTrack : public SrsRtspSendTrack { public: - SrsRtspAudioSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + SrsRtspAudioSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); virtual ~SrsRtspAudioSendTrack(); public: @@ -294,7 +305,7 @@ public: class SrsRtspVideoSendTrack : public SrsRtspSendTrack { public: - SrsRtspVideoSendTrack(SrsRtspConnection *session, SrsRtcTrackDescription *track_desc); + SrsRtspVideoSendTrack(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc); virtual ~SrsRtspVideoSendTrack(); public: diff --git a/trunk/src/app/srs_app_srt_conn.cpp b/trunk/src/app/srs_app_srt_conn.cpp index 7b3a49f32..6e2285cfa 100644 --- a/trunk/src/app/srs_app_srt_conn.cpp +++ b/trunk/src/app/srs_app_srt_conn.cpp @@ -531,7 +531,6 @@ srs_error_t SrsMpegtsSrtConn::do_playing() srs_assert(consumer_raw); SrsUniquePtr consumer(consumer_raw); - SrsSrtConsumer *consumer_impl = dynamic_cast(consumer_raw); // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames. if ((err = srt_source_->consumer_dumps(consumer.get())) != srs_success) { diff --git a/trunk/src/utest/srs_utest_app12.cpp b/trunk/src/utest/srs_utest_app12.cpp index 4ec3073f4..0c23891a9 100644 --- a/trunk/src/utest/srs_utest_app12.cpp +++ b/trunk/src/utest/srs_utest_app12.cpp @@ -7,14 +7,20 @@ using namespace std; +#include +#include #include +#include +#include #include #include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include #include // Mock frame target implementation @@ -283,7 +289,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnTsMessageH264Video) // 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->dts_ = 90000; // 1 second in 90kHz timebase msg->pts_ = 90000; // Create simple H.264 NAL unit data (IDR frame with SPS/PPS) @@ -347,7 +353,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnTsVideoAvc) SrsUniquePtr msg(new SrsTsMessage(channel.get(), NULL)); msg->sid_ = SrsTsPESStreamIdVideoCommon; - msg->dts_ = 90000; // 1 second in 90kHz timebase + msg->dts_ = 90000; // 1 second in 90kHz timebase msg->pts_ = 90000; // Create H.264 annexb format data with SPS, PPS, and IDR frame @@ -420,7 +426,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnTsVideoHevc) SrsUniquePtr msg(new SrsTsMessage(channel.get(), NULL)); msg->sid_ = SrsTsPESStreamIdVideoCommon; - msg->dts_ = 90000; // 1 second in 90kHz timebase + msg->dts_ = 90000; // 1 second in 90kHz timebase msg->pts_ = 90000; // Create HEVC annexb format data with VPS, SPS, PPS, and IDR frame @@ -490,7 +496,7 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckSpsPpsChange) // Create a TsMessage with valid timestamp SrsUniquePtr msg(new SrsTsMessage()); - msg->dts_ = 90000; // 1 second in 90kHz timebase (will be converted to 1000ms) + 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 @@ -499,9 +505,9 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckSpsPpsChange) 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 + 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())); @@ -541,8 +547,8 @@ VOID TEST(SrsSrtFrameBuilderTest, OnH264Frame) // 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) + 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) @@ -552,8 +558,8 @@ VOID TEST(SrsSrtFrameBuilderTest, OnH264Frame) // 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))); + 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)); @@ -622,8 +628,8 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckVpsSppsPpsChange) // 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 + 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) @@ -676,7 +682,7 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckVpsSppsPpsChange) // 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 + builder->hevc_vps_ = ""; // Empty VPS HELPER_EXPECT_SUCCESS(builder->check_vps_sps_pps_change(msg.get())); @@ -708,7 +714,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnHevcFrameWithIDR) // 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->dts_ = 90000; // 1 second in 90kHz timebase (will be converted to 1000ms in FLV) msg->pts_ = 90000; // Create HEVC NAL units for testing @@ -766,7 +772,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnHevcFrameWithIDR) // Verify HEVC fourcc 'hvc1' uint32_t fourcc = buffer->read_4bytes(); - EXPECT_EQ(0x68766331, fourcc); // 'h' 'v' 'c' '1' + EXPECT_EQ(0x68766331, fourcc); // 'h' 'v' 'c' '1' // Verify NAL units are written correctly with 4-byte length prefix // VPS @@ -830,13 +836,13 @@ VOID TEST(SrsSrtFrameBuilderTest, OnTsAudioAAC) // 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 + 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); @@ -847,7 +853,7 @@ VOID TEST(SrsSrtFrameBuilderTest, OnTsAudioAAC) // 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->dts_ = 90000; // 1 second in 90kHz timebase msg->pts_ = 90000; // Append payload to the message's payload stream @@ -895,7 +901,7 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckAudioShChange) // 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)); + builder->audio_sh_.assign((char *)asc_data, sizeof(asc_data)); // Create a TS channel for AAC audio SrsUniquePtr channel(new SrsTsChannel()); @@ -905,10 +911,10 @@ VOID TEST(SrsSrtFrameBuilderTest, CheckAudioShChange) // 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->dts_ = 90000; // 1 second in 90kHz timebase msg->pts_ = 90000; - uint32_t pts = 1000; // 1000ms in FLV timebase + 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)); @@ -1020,10 +1026,10 @@ VOID TEST(SrsSrtSourceTest, StreamLifecycleAndSourceIdChange) // 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 + 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 + source->on_unpublish(); // Sets can_publish_ back to true EXPECT_TRUE(source->can_publish()); // Create a consumer @@ -1428,6 +1434,26 @@ void MockSrtConsumer::reset() 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) @@ -1582,3 +1608,1937 @@ VOID TEST(SrsSrtSourceTest, OnPacketDistribution) // 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()); +} diff --git a/trunk/src/utest/srs_utest_app12.hpp b/trunk/src/utest/srs_utest_app12.hpp index a754df410..775d51bff 100644 --- a/trunk/src/utest/srs_utest_app12.hpp +++ b/trunk/src/utest/srs_utest_app12.hpp @@ -12,9 +12,10 @@ */ #include -#include +#include #include #include +#include // Mock frame target for testing SrsSrtFrameBuilder class MockSrtFrameTarget : public ISrsFrameTarget @@ -107,5 +108,52 @@ public: void reset(); }; -#endif +// Forward declaration +class SrsRtspConsumer; +// Mock RTSP source for testing SrsRtspConsumer +class MockRtspSource +{ +public: + int on_consumer_destroy_count_; + +public: + MockRtspSource(); + virtual ~MockRtspSource(); + void on_consumer_destroy(SrsRtspConsumer *consumer); + void reset(); +}; + +// Mock RTP target for testing SrsRtspRtpBuilder +class MockRtspRtpTarget : public ISrsRtpTarget +{ +public: + int on_rtp_count_; + SrsRtpPacket *last_rtp_; + srs_error_t rtp_error_; + +public: + MockRtspRtpTarget(); + virtual ~MockRtspRtpTarget(); + virtual srs_error_t on_rtp(SrsRtpPacket *pkt); + void set_rtp_error(srs_error_t err); + void reset(); +}; + +// Mock RTSP connection for testing SrsRtspSendTrack +class MockRtspConnection : public ISrsRtspConnection +{ +public: + int do_send_packet_count_; + SrsRtpPacket *last_packet_; + srs_error_t send_error_; + +public: + MockRtspConnection(); + virtual ~MockRtspConnection(); + virtual srs_error_t do_send_packet(SrsRtpPacket *pkt); + void set_send_error(srs_error_t err); + void reset(); +}; + +#endif