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:
parent
0631715a65
commit
e712b12a15
2
trunk/configure
vendored
2
trunk/configure
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 47
|
||||
#define VERSION_REVISION 48
|
||||
|
||||
#endif
|
||||
329
trunk/src/utest/srs_utest_rtc3.cpp
Normal file
329
trunk/src/utest/srs_utest_rtc3.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
15
trunk/src/utest/srs_utest_rtc3.hpp
Normal file
15
trunk/src/utest/srs_utest_rtc3.hpp
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user