RTC: audio packet jitter buffer. v7.0.48 (#4295)

Rtp packets may be retransmitted, disordered, jittery, delayed,
etc.There may be abnormalities when converting to rtmp.

To reproduce this problem, you need to set the network reordering by
[tc-ui](https://github.com/ossrs/tc-ui). Note that you need a linux
server, and start it by docker:

```bash
docker run --network=host --privileged -it --restart always -d \
    --name tc -v /lib/modules:/lib/modules:ro ossrs/tc-ui:1
```

Set up 5% packet reordering and a 1ms delay; then you will notice that
the audio is stuttering, somewhat noisy, and lacks fluency.

```bash
curl http://localhost:2023/tc/api/v1/config/raw -X POST \
  -d 'tcset ens5 --direction incoming --delay 40ms --reordering 5% --port 8000'
```

> Note: Even without network conditions, the natural state can also
cause packet reordering, especially in public cloud platforms such as
AWS EC2.

> Note: You can use command `curl
http://localhost:2023/tc/api/v1/config/raw -X POST -d 'tcdel --all
ens5'` to reset the network condition settings.

Check the web console, you will see the reordering setup:

<img width="500" alt="TC Settings"
src="https://github.com/user-attachments/assets/b278fdf4-9fcc-4aac-b534-dfa34e28c371"
/>

Then, publish stream via WHIP: http://localhost:8080/players/whip.html

And, play via HTTP-FLV: http://localhost:8080/players/srs_player.html

Finished by AI:

* [AI: Extract audio jitter buffer to class
AudioPacketCache](a4097d9374)
* [AI: Add utest and fix
bug.](c919227af5)

---------

Co-authored-by: Haibo Chen <495810242@qq.com>
Co-authored-by: winlin <winlinvip@gmail.com>
This commit is contained in:
chundonglinlin 2025-07-17 09:36:56 +08:00 committed by GitHub
parent 0631715a65
commit e712b12a15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 529 additions and 6 deletions

2
trunk/configure vendored
View File

@ -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

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## 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)

View File

@ -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<SrsRtpPacket*> pkts;
// auto free when exit
// TODO: FIXME: Should rename to pkts_disposer.
SrsUniquePtr<vector<SrsRtpPacket*>> 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<SrsRtpPacket*>& 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<uint16_t, SrsRtpPacket*>::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<uint16_t, SrsRtpPacket*>::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<uint16_t, SrsRtpPacket*>::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<SrsRtpPacket*> ready_packets;
SrsUniquePtr<vector<SrsRtpPacket*>> 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<SrsAudioFrame*> out_pkts;
SrsRtpRawPayload *payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload());

View File

@ -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 @@ public:
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<uint16_t, SrsRtpPacket*> 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<SrsRtpPacket*>& ready_packets);
// Clear all cached packets
void clear_all();
};
// Collect and build WebRTC RTP packets to AV frames.
class SrsRtcFrameBuilder
{
@ -386,9 +420,9 @@ private:
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 @@ public:
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:

View File

@ -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

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 47
#define VERSION_REVISION 48
#endif

View File

@ -0,0 +1,329 @@
//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_rtc3.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#include <srs_core_autofree.hpp>
#include <srs_utest_kernel.hpp>
#include <srs_protocol_st.hpp>
// 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<SrsRtpPacket*>& 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<SrsRtpPacket*> ready_packets;
// Process first packet
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process first packet 100 to initialize
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process packets 100, 101, 102 in order
for (uint16_t seq = 100; seq <= 102; seq++) {
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process packets near the 16-bit boundary
uint16_t seq_near_max = 65534;
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Set a very short timeout for testing (1ms)
cache.set_timeout(1 * SRS_UTIME_MILLISECONDS);
// Process first packet to initialize
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process first packet to initialize
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process first packet to initialize
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> pkt3(mock_create_audio_rtp_packet(103, 1060));
HELPER_EXPECT_SUCCESS(cache.process_packet(pkt3.get(), ready_packets));
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket*> ready_packets;
// Process first packet
SrsUniquePtr<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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<SrsRtpPacket> 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);
}
}

View File

@ -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 <srs_utest_rtc3.hpp>
*/
#include <srs_utest.hpp>
#endif