diff --git a/trunk/configure b/trunk/configure index cbe49c8f3f0..fdc909631fc 100755 --- a/trunk/configure +++ b/trunk/configure @@ -471,7 +471,7 @@ if [[ $SRS_UTEST == YES ]]; then "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload" "srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc" "srs_utest_config2" "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3" - "srs_utest_st" "srs_utest_rtc2") + "srs_utest_st" "srs_utest_rtc2" "srs_utest_rtc3") if [[ $SRS_SRT == YES ]]; then MODULE_FILES+=("srs_utest_srt") fi diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 2a2ba9626da..9aa3649bffb 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-07-16, Merge [#4295](https://github.com/ossrs/srs/pull/4295): RTC: audio packet jitter buffer. v7.0.48 (#4295) * v7.0, 2025-07-11, Merge [#4333](https://github.com/ossrs/srs/pull/4333): NEW PROTOCOL: Support viewing stream over RTSP. v7.0.47 (#4333) * v7.0, 2025-07-10, Merge [#4414](https://github.com/ossrs/srs/pull/4414): Fix H.264 B-frame detection logic to comply with specification. v7.0.46 (#4414) * v7.0, 2025-07-04, Merge [#4412](https://github.com/ossrs/srs/pull/4412): Refine code and add tests for #4289. v7.0.45 (#4412) diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index 99ba4c51231..74f9277206a 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -1006,7 +1006,7 @@ srs_error_t SrsRtcRtpBuilder::on_audio(SrsSharedPtrMessage* msg) return err; } - // ts support audio codec: aac/mp3 + // support audio codec: aac/mp3 SrsAudioCodecId acodec = format->acodec->id; if (acodec != SrsAudioCodecIdAAC && acodec != SrsAudioCodecIdMP3) { return err; @@ -1205,7 +1205,7 @@ srs_error_t SrsRtcRtpBuilder::on_video(SrsSharedPtrMessage* msg) // If merge Nalus, we pcakges all NALUs(samples) as one NALU, in a RTP or FUA packet. vector pkts; - // auto free when exit + // TODO: FIXME: Should rename to pkts_disposer. SrsUniquePtr> pkts_ptr(&pkts, free_packets); if (merge_nalus && nn_samples > 1) { @@ -1593,12 +1593,122 @@ bool SrsRtcFrameBuilderVideoFrameDetector::is_lost_sn(uint16_t received) return lost_sn_ == received; } +SrsRtcFrameBuilderAudioPacketCache::SrsRtcFrameBuilderAudioPacketCache() +{ + last_audio_seq_num_ = 0; + last_audio_process_time_ = 0; + initialized_ = false; + timeout_ = MAX_AUDIO_WAIT_MS * SRS_UTIME_MILLISECONDS; // Default timeout in microseconds +} + +SrsRtcFrameBuilderAudioPacketCache::~SrsRtcFrameBuilderAudioPacketCache() +{ + clear_all(); +} + +void SrsRtcFrameBuilderAudioPacketCache::set_timeout(srs_utime_t timeout) +{ + timeout_ = timeout; +} + +srs_error_t SrsRtcFrameBuilderAudioPacketCache::process_packet(SrsRtpPacket* src, std::vector& ready_packets) +{ + srs_error_t err = srs_success; + + uint16_t seq = src->header.get_sequence(); + srs_utime_t now = srs_update_system_time(); + + if (!initialized_) { + last_audio_seq_num_ = seq - 1; + last_audio_process_time_ = now; + initialized_ = true; + } + + // Check if packet is too old (already processed) + if (srs_rtp_seq_distance(last_audio_seq_num_, seq) < 0) { + srs_warn("Discard late audio packet, seq=%u, last_seq=%u", seq, last_audio_seq_num_); + return err; + } + + // Store packet in jitter buffer + if (true) { + std::map::iterator it = audio_buffer_.find(seq); + if (it != audio_buffer_.end()) { + SrsRtpPacket* pkt = it->second; + srs_freep(pkt); + } + audio_buffer_[seq] = src->copy(); + } + + // Try to process packets in the sliding window + bool force_process = audio_buffer_.size() >= AUDIO_JITTER_BUFFER_SIZE || + (now - last_audio_process_time_) > timeout_; + uint16_t window_end = last_audio_seq_num_ + SLIDING_WINDOW_SIZE; + + while (!audio_buffer_.empty()) { + std::map::iterator it = audio_buffer_.begin(); + uint16_t next_seq = it->first; + + // Check if the packet is within our sliding window + if (!force_process) { + // If packet is before window start (shouldn't happen normally) + if (srs_rtp_seq_distance(last_audio_seq_num_, next_seq) < 0) { + // Process it anyway as it's already late + srs_warn("Late audio packet, seq=%u, expected>=%u", next_seq, last_audio_seq_num_); + } else if (srs_rtp_seq_distance(next_seq, window_end) < 0) { + // If packet is beyond window end, stop processing + srs_warn("Audio packet beyond window end, seq=%u, window_end=%u", next_seq, window_end); + break; + } else if (srs_rtp_seq_distance(last_audio_seq_num_, next_seq) > 1) { + // If there's a gap and we haven't exceeded wait time, wait for missing packets + if ((now - last_audio_process_time_) <= timeout_) { + break; + } + srs_warn("Audio packet loss, expected=%u, got=%u", last_audio_seq_num_ + 1, next_seq); + } + } + + // Take the packet from buffer + SrsRtpPacket* pkt = it->second; + audio_buffer_.erase(it); + + // Update last sequence number + last_audio_seq_num_ = next_seq; + last_audio_process_time_ = now; + + // Add to ready packets for processing + ready_packets.push_back(pkt); + + // Update window end for next iteration + window_end = last_audio_seq_num_ + SLIDING_WINDOW_SIZE; + } + + // If buffer is getting too full, force process oldest packets + if (audio_buffer_.size() >= AUDIO_JITTER_BUFFER_SIZE * 0.8) { + srs_warn("Audio jitter buffer nearly full, size=%zu", audio_buffer_.size()); + } + + return err; +} + +void SrsRtcFrameBuilderAudioPacketCache::clear_all() +{ + std::map::iterator it; + for (it = audio_buffer_.begin(); it != audio_buffer_.end(); ++it) { + SrsRtpPacket* pkt = it->second; + srs_freep(pkt); + } + + audio_buffer_.clear(); +} + SrsRtcFrameBuilder::SrsRtcFrameBuilder(ISrsStreamBridge* bridge) { bridge_ = bridge; is_first_audio_ = true; audio_transcoder_ = NULL; video_codec_ = SrsVideoCodecIdAVC; + audio_cache_ = new SrsRtcFrameBuilderAudioPacketCache(); video_cache_ = new SrsRtcFrameBuilderVideoPacketCache(); frame_detector_ = new SrsRtcFrameBuilderVideoFrameDetector(video_cache_); sync_state_ = -1; @@ -1608,6 +1718,7 @@ SrsRtcFrameBuilder::SrsRtcFrameBuilder(ISrsStreamBridge* bridge) SrsRtcFrameBuilder::~SrsRtcFrameBuilder() { srs_freep(audio_transcoder_); + srs_freep(audio_cache_); srs_freep(video_cache_); srs_freep(frame_detector_); srs_freep(obs_whip_vps_); @@ -1648,6 +1759,7 @@ srs_error_t SrsRtcFrameBuilder::on_publish() void SrsRtcFrameBuilder::on_unpublish() { + audio_cache_->clear_all(); } srs_error_t SrsRtcFrameBuilder::on_rtp(SrsRtpPacket *pkt) @@ -1675,7 +1787,7 @@ srs_error_t SrsRtcFrameBuilder::on_rtp(SrsRtpPacket *pkt) } if (pkt->is_audio()) { - err = transcode_audio(pkt); + err = packet_audio(pkt); } else { err = packet_video(pkt); } @@ -1683,6 +1795,30 @@ srs_error_t SrsRtcFrameBuilder::on_rtp(SrsRtpPacket *pkt) return err; } +srs_error_t SrsRtcFrameBuilder::packet_audio(SrsRtpPacket* src) +{ + srs_error_t err = srs_success; + + std::vector ready_packets; + SrsUniquePtr> pkts_disposer(&ready_packets, free_packets); + + // Use audio cache to process packet through jitter buffer + if ((err = audio_cache_->process_packet(src, ready_packets)) != srs_success) { + return srs_error_wrap(err, "audio cache process"); + } + + // Process all ready packets in order + for (size_t i = 0; i < ready_packets.size(); ++i) { + SrsRtpPacket* pkt = ready_packets[i]; + + if ((err = transcode_audio(pkt)) != srs_success) { + return srs_error_wrap(err, "transcode audio"); + } + } + + return err; +} + srs_error_t SrsRtcFrameBuilder::transcode_audio(SrsRtpPacket *pkt) { srs_error_t err = srs_success; @@ -1709,6 +1845,7 @@ srs_error_t SrsRtcFrameBuilder::transcode_audio(SrsRtpPacket *pkt) is_first_audio_ = false; } + // TODO: FIXME: Should use SrsUniquePtr to dispose it automatically. std::vector out_pkts; SrsRtpRawPayload *payload = dynamic_cast(pkt->payload()); diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 32056c4526c..35486abe6dd 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -52,6 +52,13 @@ const int kVideoPayloadType = 102; // Chrome HEVC defaults as 49. const int KVideoPayloadTypeHevc = 49; +// Audio jitter buffer size (in packets) +const int AUDIO_JITTER_BUFFER_SIZE = 100; +// Sliding window size for continuous processing +const int SLIDING_WINDOW_SIZE = 10; +// Maximum waiting time for out-of-order packets (in ms) +const int MAX_AUDIO_WAIT_MS = 100; + class SrsNtp { public: @@ -378,6 +385,33 @@ class SrsRtcFrameBuilderVideoFrameDetector bool is_lost_sn(uint16_t received); }; +// Audio packet cache for RTP packet jitter buffer management +class SrsRtcFrameBuilderAudioPacketCache +{ +private: + // Audio jitter buffer, map sequence number to packet + std::map audio_buffer_; + // Last processed sequence number + uint16_t last_audio_seq_num_; + // Last time we processed the jitter buffer + srs_utime_t last_audio_process_time_; + // Whether the cache has been initialized + bool initialized_; + // Timeout for waiting out-of-order packets (in microseconds) + srs_utime_t timeout_; +public: + SrsRtcFrameBuilderAudioPacketCache(); + virtual ~SrsRtcFrameBuilderAudioPacketCache(); +public: + // Set timeout for waiting out-of-order packets (in microseconds) + void set_timeout(srs_utime_t timeout); + // Process audio packet through jitter buffer + // Returns packets ready for transcoding in order + srs_error_t process_packet(SrsRtpPacket* src, std::vector& ready_packets); + // Clear all cached packets + void clear_all(); +}; + // Collect and build WebRTC RTP packets to AV frames. class SrsRtcFrameBuilder { @@ -386,9 +420,9 @@ class SrsRtcFrameBuilder private: bool is_first_audio_; SrsAudioTranscoder *audio_transcoder_; - SrsVideoCodecId video_codec_; private: + SrsRtcFrameBuilderAudioPacketCache* audio_cache_; SrsRtcFrameBuilderVideoPacketCache* video_cache_; SrsRtcFrameBuilderVideoFrameDetector* frame_detector_; private: @@ -408,6 +442,7 @@ class SrsRtcFrameBuilder virtual void on_unpublish(); virtual srs_error_t on_rtp(SrsRtpPacket *pkt); private: + srs_error_t packet_audio(SrsRtpPacket* pkt); srs_error_t transcode_audio(SrsRtpPacket *pkt); void packet_aac(SrsCommonMessage* audio, char* data, int len, uint32_t pts, bool is_header); private: diff --git a/trunk/src/core/srs_core_time.hpp b/trunk/src/core/srs_core_time.hpp index d840f2beea4..a05021a9218 100644 --- a/trunk/src/core/srs_core_time.hpp +++ b/trunk/src/core/srs_core_time.hpp @@ -42,5 +42,11 @@ srs_utime_t srs_duration(srs_utime_t start, srs_utime_t end); // Never timeout. #define SRS_UTIME_NO_TIMEOUT ((srs_utime_t) -1LL) +// Get current system time in srs_utime_t, use cache to avoid performance problem +extern srs_utime_t srs_get_system_time(); +extern srs_utime_t srs_get_system_startup_time(); +// A daemon st-thread updates it. +extern srs_utime_t srs_update_system_time(); + #endif diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index deb6c501df8..8934e32a92b 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 47 +#define VERSION_REVISION 48 #endif \ No newline at end of file diff --git a/trunk/src/utest/srs_utest_rtc3.cpp b/trunk/src/utest/srs_utest_rtc3.cpp new file mode 100644 index 00000000000..644b8d2b890 --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc3.cpp @@ -0,0 +1,329 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include + +#include +#include +#include +#include +#include + +// Helper function to create a mock RTP packet for testing +SrsRtpPacket* mock_create_audio_rtp_packet(uint16_t sequence, uint32_t timestamp, const char* payload_data = NULL, int payload_size = 10) +{ + SrsRtpPacket* pkt = new SrsRtpPacket(); + + // Set RTP header + pkt->header.set_padding(false); + pkt->header.set_marker(false); + pkt->header.set_payload_type(111); // Audio payload type + pkt->header.set_sequence(sequence); + pkt->header.set_timestamp(timestamp); + pkt->header.set_ssrc(0x12345678); + + // For audio cache testing, we don't need actual payload data or avsync time + // The cache is only concerned with sequence numbers and system time for jitter buffer logic + // We're not testing the downstream transcoding, just the cache reordering behavior + + // Set frame type for audio + pkt->frame_type = SrsFrameTypeAudio; + + return pkt; +} + +// Helper function to free a vector of RTP packets +void free_audio_packets(std::vector& packets) +{ + for (size_t i = 0; i < packets.size(); ++i) { + srs_freep(packets[i]); + } + packets.clear(); +} + +VOID TEST(RTC3AudioCacheTest, BasicPacketProcessing) +{ + srs_error_t err; + + // Test basic packet processing in order + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process first packet + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + + // First packet should be processed immediately + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(100, ready_packets[0]->header.get_sequence()); + + free_audio_packets(ready_packets); + + // Process second packet in sequence + SrsUniquePtr pkt2(mock_create_audio_rtp_packet(101, 1020)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt2.get(), ready_packets)); + + // Second packet should be processed immediately + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(101, ready_packets[0]->header.get_sequence()); + + free_audio_packets(ready_packets); + } +} + +VOID TEST(RTC3AudioCacheTest, OutOfOrderPackets) +{ + srs_error_t err; + + // Test out-of-order packet handling + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process first packet 100 to initialize + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + free_audio_packets(ready_packets); + + // Process packet 103 (out of order - missing 101, 102) + SrsUniquePtr pkt3(mock_create_audio_rtp_packet(103, 1060)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets)); + + // Should not process yet, waiting for missing packets + EXPECT_EQ(0, (int)ready_packets.size()); + + // Process packet 101 (fills gap) + SrsUniquePtr pkt2(mock_create_audio_rtp_packet(101, 1020)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt2.get(), ready_packets)); + + // Should process packet 101 now + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(101, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + + // Process packet 102 (completes sequence) + SrsUniquePtr pkt4(mock_create_audio_rtp_packet(102, 1040)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt4.get(), ready_packets)); + + // Should process both 102 and 103 + EXPECT_EQ(2, (int)ready_packets.size()); + EXPECT_EQ(102, ready_packets[0]->header.get_sequence()); + EXPECT_EQ(103, ready_packets[1]->header.get_sequence()); + + free_audio_packets(ready_packets); + } +} + +VOID TEST(RTC3AudioCacheTest, LatePacketHandling) +{ + srs_error_t err; + + // Test late packet detection and discard + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process packets 100, 101, 102 in order + for (uint16_t seq = 100; seq <= 102; seq++) { + SrsUniquePtr pkt(mock_create_audio_rtp_packet(seq, 1000 + (seq - 100) * 20)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(seq, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + } + + // Try to process packet 99 (late packet - already processed) + SrsUniquePtr late_pkt(mock_create_audio_rtp_packet(99, 980)); + HELPER_EXPECT_SUCCESS(cache.process_packet(late_pkt.get(), ready_packets)); + + // Late packet should be discarded, no packets ready + EXPECT_EQ(0, (int)ready_packets.size()); + } +} + +VOID TEST(RTC3AudioCacheTest, SequenceNumberWrapAround) +{ + srs_error_t err; + + // Test sequence number wrap-around (16-bit overflow) + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process packets near the 16-bit boundary + uint16_t seq_near_max = 65534; + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(seq_near_max, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(seq_near_max, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + + // Process packet 65535 + SrsUniquePtr pkt2(mock_create_audio_rtp_packet(65535, 1020)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt2.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(65535, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + + // Process packet 0 (after wrap-around) + SrsUniquePtr pkt3(mock_create_audio_rtp_packet(0, 1040)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(0, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + + // Process packet 1 + SrsUniquePtr pkt4(mock_create_audio_rtp_packet(1, 1060)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt4.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + EXPECT_EQ(1, ready_packets[0]->header.get_sequence()); + free_audio_packets(ready_packets); + } +} + +VOID TEST(RTC3AudioCacheTest, PacketLossWithTimeout) +{ + srs_error_t err; + + // Test packet loss handling with timeout + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Set a very short timeout for testing (1ms) + cache.set_timeout(1 * SRS_UTIME_MILLISECONDS); + + // Process first packet to initialize + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + free_audio_packets(ready_packets); + + // Process packet 103 (missing 101, 102) + SrsUniquePtr pkt3(mock_create_audio_rtp_packet(103, 1060)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets)); + + // Should not process yet, waiting for missing packets + EXPECT_EQ(0, (int)ready_packets.size()); + + // Sleep for 10ms to exceed the 1ms timeout + srs_usleep(10 * SRS_UTIME_MILLISECONDS); + + // Process another packet to trigger timeout check + SrsUniquePtr pkt4(mock_create_audio_rtp_packet(104, 1080)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt4.get(), ready_packets)); + + // Should process packet 103 despite missing 101, 102 due to timeout + bool found_103 = false; + for (size_t i = 0; i < ready_packets.size(); i++) { + if (ready_packets[i]->header.get_sequence() == 103) { + found_103 = true; + break; + } + } + EXPECT_TRUE(found_103); + free_audio_packets(ready_packets); + } +} + +VOID TEST(RTC3AudioCacheTest, BufferOverflowProtection) +{ + srs_error_t err; + + // Test buffer overflow protection + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process first packet to initialize + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + free_audio_packets(ready_packets); + + // Fill buffer with out-of-order packets to trigger overflow protection + // Add packets with large gaps to fill the buffer + for (uint16_t i = 0; i < AUDIO_JITTER_BUFFER_SIZE + 10; i++) { + uint16_t seq = 200 + i * 2; // Create gaps to avoid immediate processing + SrsUniquePtr pkt(mock_create_audio_rtp_packet(seq, 2000 + i * 20)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt.get(), ready_packets)); + } + + // Buffer overflow protection should have kicked in and processed some packets + EXPECT_GT((int)ready_packets.size(), 0); + free_audio_packets(ready_packets); + } +} + +VOID TEST(RTC3AudioCacheTest, ClearAllFunctionality) +{ + srs_error_t err; + + // Test clear_all functionality + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process first packet to initialize + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + free_audio_packets(ready_packets); + + // Add some out-of-order packets to buffer + SrsUniquePtr pkt3(mock_create_audio_rtp_packet(103, 1060)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets)); + + SrsUniquePtr pkt5(mock_create_audio_rtp_packet(105, 1100)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt5.get(), ready_packets)); + + // Should have some packets waiting in buffer + EXPECT_EQ(0, (int)ready_packets.size()); + + // Clear all cached packets + cache.clear_all(); + + EXPECT_EQ(0, (int)cache.audio_buffer_.size()); + } +} + +VOID TEST(RTC3AudioCacheTest, DuplicatePacketHandling) +{ + srs_error_t err; + + // Test duplicate packet handling + if (true) { + SrsRtcFrameBuilderAudioPacketCache cache; + std::vector ready_packets; + + // Process first packet + SrsUniquePtr pkt1(mock_create_audio_rtp_packet(100, 1000)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt1.get(), ready_packets)); + EXPECT_EQ(1, (int)ready_packets.size()); + free_audio_packets(ready_packets); + + // Process packet 102 (out of order) + SrsUniquePtr pkt3(mock_create_audio_rtp_packet(102, 1040)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets)); + EXPECT_EQ(0, (int)ready_packets.size()); // Waiting for 101 + + // Process duplicate packet 102 + SrsUniquePtr pkt3_dup(mock_create_audio_rtp_packet(102, 1040)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3_dup.get(), ready_packets)); + EXPECT_EQ(0, (int)ready_packets.size()); // Still waiting for 101 + + // Process packet 101 to complete sequence + SrsUniquePtr pkt2(mock_create_audio_rtp_packet(101, 1020)); + HELPER_EXPECT_SUCCESS(cache.process_packet(pkt2.get(), ready_packets)); + + // Should process 101 and one instance of 102 (duplicate should be handled) + EXPECT_GE((int)ready_packets.size(), 2); + EXPECT_EQ(101, ready_packets[0]->header.get_sequence()); + EXPECT_EQ(102, ready_packets[1]->header.get_sequence()); + free_audio_packets(ready_packets); + } +} diff --git a/trunk/src/utest/srs_utest_rtc3.hpp b/trunk/src/utest/srs_utest_rtc3.hpp new file mode 100644 index 00000000000..9fd1375c7a7 --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc3.hpp @@ -0,0 +1,15 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_RTC3_HPP +#define SRS_UTEST_RTC3_HPP + +/* +#include +*/ +#include + +#endif