AI: Add workflow utest to verify rtc2rtmp audio async.

This commit is contained in:
OSSRS-AI 2025-10-27 10:58:52 -04:00 committed by winlin
parent 2943fcb10f
commit 19ac413101
16 changed files with 521 additions and 104 deletions

8
trunk/configure vendored
View File

@ -381,12 +381,14 @@ if [[ $SRS_UTEST == YES ]]; then
"srs_utest_manual_protocol" "srs_utest_manual_protocol2" "srs_utest_manual_kernel2" "srs_utest_manual_st" "srs_utest_manual_fmp4" "srs_utest_manual_protocol" "srs_utest_manual_protocol2" "srs_utest_manual_kernel2" "srs_utest_manual_st" "srs_utest_manual_fmp4"
"srs_utest_manual_source_lock" "srs_utest_manual_stream_token" "srs_utest_manual_rtc_recv_track" "srs_utest_manual_st2" "srs_utest_manual_source_lock" "srs_utest_manual_stream_token" "srs_utest_manual_rtc_recv_track" "srs_utest_manual_st2"
"srs_utest_manual_hevc_structs" "srs_utest_manual_coworkers" "srs_utest_manual_pithy_print" "srs_utest_manual_protocol3" "srs_utest_manual_hevc_structs" "srs_utest_manual_coworkers" "srs_utest_manual_pithy_print" "srs_utest_manual_protocol3"
"srs_utest_manual_app" "srs_utest_manual_mock" "srs_utest_workflow_rtc_playstream" "srs_utest_workflow_rtc_publishstream" "srs_utest_manual_app" "srs_utest_manual_mock")
"srs_utest_workflow_rtc_conn" "srs_utest_workflow_rtmp_conn" "srs_utest_workflow_srt_conn" "srs_utest_workflow_http_conn") MODULE_FILES+=("srs_utest_workflow_rtc_playstream" "srs_utest_workflow_rtc_publishstream"
"srs_utest_workflow_rtc_conn" "srs_utest_workflow_rtmp_conn" "srs_utest_workflow_srt_conn" "srs_utest_workflow_http_conn"
"srs_utest_workflow_forward" "srs_utest_workflow_rtc2rtmp")
MODULE_FILES+=("srs_utest_ai01" "srs_utest_ai02" "srs_utest_ai03" "srs_utest_ai04" "srs_utest_ai05" MODULE_FILES+=("srs_utest_ai01" "srs_utest_ai02" "srs_utest_ai03" "srs_utest_ai04" "srs_utest_ai05"
"srs_utest_ai06" "srs_utest_ai07" "srs_utest_ai08" "srs_utest_ai09" "srs_utest_ai10" "srs_utest_ai11" "srs_utest_ai06" "srs_utest_ai07" "srs_utest_ai08" "srs_utest_ai09" "srs_utest_ai10" "srs_utest_ai11"
"srs_utest_ai12" "srs_utest_ai13" "srs_utest_ai14" "srs_utest_ai15" "srs_utest_ai16" "srs_utest_ai17" "srs_utest_ai12" "srs_utest_ai13" "srs_utest_ai14" "srs_utest_ai15" "srs_utest_ai16" "srs_utest_ai17"
"srs_utest_ai18" "srs_utest_ai19" "srs_utest_ai20" "srs_utest_ai24" "srs_utest_workflow_forward") "srs_utest_ai18" "srs_utest_ai19" "srs_utest_ai20" "srs_utest_ai24")
if [[ $SRS_GB28181 == YES ]]; then if [[ $SRS_GB28181 == YES ]]; then
MODULE_FILES+=("srs_utest_manual_gb28181" "srs_utest_ai23") MODULE_FILES+=("srs_utest_manual_gb28181" "srs_utest_ai23")
fi fi

View File

@ -1377,6 +1377,14 @@ srs_error_t SrsRtcRtpBuilder::consume_packets(vector<SrsRtpPacket *> &pkts)
return err; return err;
} }
ISrsRtcFrameBuilderVideoPacketCache::ISrsRtcFrameBuilderVideoPacketCache()
{
}
ISrsRtcFrameBuilderVideoPacketCache::~ISrsRtcFrameBuilderVideoPacketCache()
{
}
SrsRtcFrameBuilderVideoPacketCache::SrsRtcFrameBuilderVideoPacketCache() SrsRtcFrameBuilderVideoPacketCache::SrsRtcFrameBuilderVideoPacketCache()
{ {
memset(cache_pkts_, 0, sizeof(cache_pkts_)); memset(cache_pkts_, 0, sizeof(cache_pkts_));
@ -1536,7 +1544,15 @@ bool SrsRtcFrameBuilderVideoPacketCache::check_frame_complete(const uint16_t sta
return nn_fu_start == nn_fu_end; return nn_fu_start == nn_fu_end;
} }
SrsRtcFrameBuilderVideoFrameDetector::SrsRtcFrameBuilderVideoFrameDetector(SrsRtcFrameBuilderVideoPacketCache *cache) ISrsRtcFrameBuilderVideoFrameDetector::ISrsRtcFrameBuilderVideoFrameDetector()
{
}
ISrsRtcFrameBuilderVideoFrameDetector::~ISrsRtcFrameBuilderVideoFrameDetector()
{
}
SrsRtcFrameBuilderVideoFrameDetector::SrsRtcFrameBuilderVideoFrameDetector(ISrsRtcFrameBuilderVideoPacketCache *cache)
{ {
video_cache_ = cache; video_cache_ = cache;
header_sn_ = 0; header_sn_ = 0;
@ -1640,6 +1656,14 @@ bool SrsRtcFrameBuilderVideoFrameDetector::is_lost_sn(uint16_t received)
return lost_sn_ == received; return lost_sn_ == received;
} }
ISrsRtcFrameBuilderAudioPacketCache::ISrsRtcFrameBuilderAudioPacketCache()
{
}
ISrsRtcFrameBuilderAudioPacketCache::~ISrsRtcFrameBuilderAudioPacketCache()
{
}
SrsRtcFrameBuilderAudioPacketCache::SrsRtcFrameBuilderAudioPacketCache() SrsRtcFrameBuilderAudioPacketCache::SrsRtcFrameBuilderAudioPacketCache()
{ {
last_audio_seq_num_ = 0; last_audio_seq_num_ = 0;
@ -3115,7 +3139,7 @@ SrsRtcRecvTrack::SrsRtcRecvTrack(ISrsRtcPacketReceiver *receiver, SrsRtcTrackDes
if (track_desc_->media_) { if (track_desc_->media_) {
rate_ = static_cast<double>(track_desc_->media_->sample_) / 1000.0; rate_ = static_cast<double>(track_desc_->media_->sample_) / 1000.0;
srs_trace("RTC: Init %s track, ssrc=%u, rate from SDP=%.0f (RTP units per ms, will be updated after 2nd SR)", srs_trace("RTC: Init %s track, ssrc=%u, rate from SDP=%.0f (RTP units per ms, will be updated after 2nd SR)",
track_desc_->type_.c_str(), track_desc_->ssrc_, rate_); track_desc_->type_.c_str(), track_desc_->ssrc_, rate_);
} }
last_sender_report_sys_time_ = 0; last_sender_report_sys_time_ = 0;
@ -3177,11 +3201,11 @@ void SrsRtcRecvTrack::update_send_report_time(const SrsNtp &ntp, uint32_t rtp_ti
if (rate > 0) { if (rate > 0) {
if (rate_ != rate) { if (rate_ != rate) {
srs_warn("RTC: SR update %s, ssrc=%u, ntp_ms=%u->%u (delta=%.0fms), rtp_time=%u->%u (delta=%.0f), rate %.0f->%.0f", srs_warn("RTC: SR update %s, ssrc=%u, ntp_ms=%u->%u (delta=%.0fms), rtp_time=%u->%u (delta=%.0f), rate %.0f->%.0f",
track_desc_->type_.c_str(), track_desc_->ssrc_, track_desc_->type_.c_str(), track_desc_->ssrc_,
last_sender_report_ntp1_.system_ms_, last_sender_report_ntp_.system_ms_, sys_time_elapsed, last_sender_report_ntp1_.system_ms_, last_sender_report_ntp_.system_ms_, sys_time_elapsed,
(uint32_t)last_sender_report_rtp_time1_, (uint32_t)last_sender_report_rtp_time_, rtp_time_elpased, (uint32_t)last_sender_report_rtp_time1_, (uint32_t)last_sender_report_rtp_time_, rtp_time_elpased,
rate_, rate); rate_, rate);
} }
rate_ = rate; rate_ = rate;
} }
} }

View File

@ -419,9 +419,25 @@ SRS_DECLARE_PRIVATE: // clang-format on
srs_error_t consume_packets(std::vector<SrsRtpPacket *> &pkts); srs_error_t consume_packets(std::vector<SrsRtpPacket *> &pkts);
}; };
// Video packet cache interface
class ISrsRtcFrameBuilderVideoPacketCache
{
public:
ISrsRtcFrameBuilderVideoPacketCache();
virtual ~ISrsRtcFrameBuilderVideoPacketCache();
public:
virtual SrsRtpPacket *get_packet(uint16_t sequence_number) = 0;
virtual void store_packet(SrsRtpPacket *pkt) = 0;
virtual void clear_all() = 0;
virtual SrsRtpPacket *take_packet(uint16_t sequence_number) = 0;
virtual int32_t find_next_lost_sn(uint16_t current_sn, uint16_t header_sn, uint16_t &end_sn) = 0;
virtual bool check_frame_complete(const uint16_t start, const uint16_t end) = 0;
};
// Video packet cache for RTP packet management // Video packet cache for RTP packet management
// TODO: Maybe should use SrsRtpRingBuffer? // TODO: Maybe should use SrsRtpRingBuffer?
class SrsRtcFrameBuilderVideoPacketCache class SrsRtcFrameBuilderVideoPacketCache : public ISrsRtcFrameBuilderVideoPacketCache
{ {
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
@ -462,18 +478,33 @@ SRS_DECLARE_PRIVATE: // clang-format on
} }
}; };
// Video frame detector interface
class ISrsRtcFrameBuilderVideoFrameDetector
{
public:
ISrsRtcFrameBuilderVideoFrameDetector();
virtual ~ISrsRtcFrameBuilderVideoFrameDetector();
public:
virtual void on_keyframe_start(SrsRtpPacket *pkt) = 0;
virtual srs_error_t detect_frame(uint16_t received, uint16_t &frame_start, uint16_t &frame_end, bool &frame_ready) = 0;
virtual srs_error_t detect_next_frame(uint16_t next_head, uint16_t &next_start, uint16_t &next_end, bool &next_ready) = 0;
virtual void on_keyframe_detached() = 0;
virtual bool is_lost_sn(uint16_t received) = 0;
};
// Video frame detector for managing frame boundaries and packet loss detection // Video frame detector for managing frame boundaries and packet loss detection
class SrsRtcFrameBuilderVideoFrameDetector class SrsRtcFrameBuilderVideoFrameDetector : public ISrsRtcFrameBuilderVideoFrameDetector
{ {
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
SrsRtcFrameBuilderVideoPacketCache *video_cache_; ISrsRtcFrameBuilderVideoPacketCache *video_cache_;
uint16_t header_sn_; uint16_t header_sn_;
uint16_t lost_sn_; uint16_t lost_sn_;
int64_t rtp_key_frame_ts_; int64_t rtp_key_frame_ts_;
public: public:
SrsRtcFrameBuilderVideoFrameDetector(SrsRtcFrameBuilderVideoPacketCache *cache); SrsRtcFrameBuilderVideoFrameDetector(ISrsRtcFrameBuilderVideoPacketCache *cache);
virtual ~SrsRtcFrameBuilderVideoFrameDetector(); virtual ~SrsRtcFrameBuilderVideoFrameDetector();
public: public:
@ -484,8 +515,20 @@ public:
bool is_lost_sn(uint16_t received); bool is_lost_sn(uint16_t received);
}; };
// Audio packet cache interface
class ISrsRtcFrameBuilderAudioPacketCache
{
public:
ISrsRtcFrameBuilderAudioPacketCache();
virtual ~ISrsRtcFrameBuilderAudioPacketCache();
public:
virtual srs_error_t process_packet(SrsRtpPacket *src, std::vector<SrsRtpPacket *> &ready_packets) = 0;
virtual void clear_all() = 0;
};
// Audio packet cache for RTP packet jitter buffer management // Audio packet cache for RTP packet jitter buffer management
class SrsRtcFrameBuilderAudioPacketCache class SrsRtcFrameBuilderAudioPacketCache : public ISrsRtcFrameBuilderAudioPacketCache
{ {
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
@ -530,9 +573,9 @@ SRS_DECLARE_PRIVATE: // clang-format on
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
SrsRtcFrameBuilderAudioPacketCache *audio_cache_; ISrsRtcFrameBuilderAudioPacketCache *audio_cache_;
SrsRtcFrameBuilderVideoPacketCache *video_cache_; ISrsRtcFrameBuilderVideoPacketCache *video_cache_;
SrsRtcFrameBuilderVideoFrameDetector *frame_detector_; ISrsRtcFrameBuilderVideoFrameDetector *frame_detector_;
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on

View File

@ -142,6 +142,25 @@ srs_error_t SrsParsedPacket::add_sample(char *bytes, int size)
return err; return err;
} }
SrsParsedPacket *SrsParsedPacket::copy()
{
SrsParsedPacket *p = new SrsParsedPacket();
do_copy(p);
return p;
}
void SrsParsedPacket::do_copy(SrsParsedPacket *p)
{
p->codec_ = codec_;
p->nb_samples_ = nb_samples_;
for (int i = 0; i < nb_samples_; i++) {
p->samples_[i].size_ = samples_[i].size_;
p->samples_[i].bytes_ = samples_[i].bytes_;
}
p->dts_ = dts_;
p->cts_ = cts_;
}
SrsParsedAudioPacket::SrsParsedAudioPacket() SrsParsedAudioPacket::SrsParsedAudioPacket()
{ {
aac_packet_type_ = SrsAudioAacFrameTraitForbidden; aac_packet_type_ = SrsAudioAacFrameTraitForbidden;
@ -156,6 +175,16 @@ SrsAudioCodecConfig *SrsParsedAudioPacket::acodec()
return (SrsAudioCodecConfig *)codec_; return (SrsAudioCodecConfig *)codec_;
} }
SrsParsedAudioPacket *SrsParsedAudioPacket::copy()
{
SrsParsedAudioPacket *p = new SrsParsedAudioPacket();
do_copy(p);
p->aac_packet_type_ = aac_packet_type_;
return p;
}
SrsParsedVideoPacket::SrsParsedVideoPacket() SrsParsedVideoPacket::SrsParsedVideoPacket()
{ {
frame_type_ = SrsVideoAvcFrameTypeForbidden; frame_type_ = SrsVideoAvcFrameTypeForbidden;
@ -213,6 +242,21 @@ srs_error_t SrsParsedVideoPacket::add_sample(char *bytes, int size)
return err; return err;
} }
SrsParsedVideoPacket *SrsParsedVideoPacket::copy()
{
SrsParsedVideoPacket *p = new SrsParsedVideoPacket();
do_copy(p);
p->frame_type_ = frame_type_;
p->avc_packet_type_ = avc_packet_type_;
p->has_idr_ = has_idr_;
p->has_aud_ = has_aud_;
p->has_sps_pps_ = has_sps_pps_;
p->first_nalu_type_ = first_nalu_type_;
return p;
}
SrsVideoCodecConfig *SrsParsedVideoPacket::vcodec() SrsVideoCodecConfig *SrsParsedVideoPacket::vcodec()
{ {
return (SrsVideoCodecConfig *)codec_; return (SrsVideoCodecConfig *)codec_;

View File

@ -109,6 +109,12 @@ public:
virtual srs_error_t initialize(SrsCodecConfig *c); virtual srs_error_t initialize(SrsCodecConfig *c);
// Add a sample to frame. // Add a sample to frame.
virtual srs_error_t add_sample(char *bytes, int size); virtual srs_error_t add_sample(char *bytes, int size);
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
// Copy the packet.
virtual SrsParsedPacket *copy();
virtual void do_copy(SrsParsedPacket *p);
}; };
// A parsed audio packet, besides a frame, contains the audio frame info, such as frame type. // A parsed audio packet, besides a frame, contains the audio frame info, such as frame type.
@ -123,6 +129,10 @@ public:
public: public:
virtual SrsAudioCodecConfig *acodec(); virtual SrsAudioCodecConfig *acodec();
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
virtual SrsParsedAudioPacket *copy();
}; };
// A parsed video packet, besides a frame, contains the video frame info, such as frame type. // A parsed video packet, besides a frame, contains the video frame info, such as frame type.
@ -151,6 +161,10 @@ public:
// Add the sample without ANNEXB or IBMF header, or RAW AAC or MP3 data. // Add the sample without ANNEXB or IBMF header, or RAW AAC or MP3 data.
virtual srs_error_t add_sample(char *bytes, int size); virtual srs_error_t add_sample(char *bytes, int size);
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
virtual SrsParsedVideoPacket *copy();
public: public:
virtual SrsVideoCodecConfig *vcodec(); virtual SrsVideoCodecConfig *vcodec();

View File

@ -15,10 +15,10 @@ using namespace std;
// Mock class to access protected members of SrsRtcRecvTrack // Mock class to access protected members of SrsRtcRecvTrack
class MockSrsRtcRecvTrackForAVSync : public SrsRtcRecvTrack class MockSrsRtcRecvTrackForAVSync : public SrsRtcRecvTrack
{ {
SRS_DECLARE_PRIVATE: SRS_DECLARE_PRIVATE:
static SrsRtcTrackDescription* create_track_desc(const string& type, uint32_t ssrc, int sample_rate) static SrsRtcTrackDescription* create_track_desc(const string& type, uint32_t ssrc, int sample_rate)
{ {
SrsRtcTrackDescription* desc = new SrsRtcTrackDescription(); SrsRtcTrackDescription *desc = new SrsRtcTrackDescription();
desc->type_ = type; desc->type_ = type;
desc->id_ = "test_track"; desc->id_ = "test_track";
desc->ssrc_ = ssrc; desc->ssrc_ = ssrc;
@ -32,7 +32,7 @@ SRS_DECLARE_PRIVATE:
} }
public: public:
MockSrsRtcRecvTrackForAVSync(const string& type, uint32_t ssrc, int sample_rate, bool is_audio) MockSrsRtcRecvTrackForAVSync(const string &type, uint32_t ssrc, int sample_rate, bool is_audio)
: SrsRtcRecvTrack(NULL, create_track_desc(type, ssrc, sample_rate), is_audio) : SrsRtcRecvTrack(NULL, create_track_desc(type, ssrc, sample_rate), is_audio)
{ {
} }
@ -42,20 +42,24 @@ public:
void set_rate(double rate) { rate_ = rate; } void set_rate(double rate) { rate_ = rate; }
int64_t test_cal_avsync_time(uint32_t rtp_time) { int64_t test_cal_avsync_time(uint32_t rtp_time)
{
return cal_avsync_time(rtp_time); return cal_avsync_time(rtp_time);
} }
void test_update_send_report_time(const SrsNtp& ntp, uint32_t rtp_time) { void test_update_send_report_time(const SrsNtp &ntp, uint32_t rtp_time)
{
update_send_report_time(ntp, rtp_time); update_send_report_time(ntp, rtp_time);
} }
// Implement pure virtual methods // Implement pure virtual methods
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt) { virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource> &source, SrsRtpPacket *pkt)
{
return srs_success; return srs_success;
} }
virtual srs_error_t check_send_nacks() { virtual srs_error_t check_send_nacks()
{
return srs_success; return srs_success;
} }
}; };
@ -85,8 +89,8 @@ VOID TEST(RtcAVSyncTest, CalAVSyncTimeWithSDPRate)
// Simulate first SR received // Simulate first SR received
SrsNtp ntp; SrsNtp ntp;
ntp.system_ms_ = 1000; // 1000 ms ntp.system_ms_ = 1000; // 1000 ms
uint32_t rtp_time = 48000; // 48000 RTP units uint32_t rtp_time = 48000; // 48000 RTP units
track.test_update_send_report_time(ntp, rtp_time); track.test_update_send_report_time(ntp, rtp_time);
// Calculate avsync time for a later RTP packet // Calculate avsync time for a later RTP packet
@ -128,8 +132,8 @@ VOID TEST(RtcAVSyncTest, AudioRateUpdateAfter2ndSR)
// Second SR (20ms later, RTP increased by 960) // Second SR (20ms later, RTP increased by 960)
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 1020; // 20ms later ntp2.system_ms_ = 1020; // 20ms later
uint32_t rtp_time2 = 48960; // 960 RTP units later (48 * 20) uint32_t rtp_time2 = 48960; // 960 RTP units later (48 * 20)
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Rate should be updated to calculated value: 960 / 20 = 48 // Rate should be updated to calculated value: 960 / 20 = 48
@ -155,8 +159,8 @@ VOID TEST(RtcAVSyncTest, VideoRateUpdateAfter2ndSR)
// Second SR (100ms later, RTP increased by 9000) // Second SR (100ms later, RTP increased by 9000)
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 2100; // 100ms later ntp2.system_ms_ = 2100; // 100ms later
uint32_t rtp_time2 = 189000; // 9000 RTP units later (90 * 100) uint32_t rtp_time2 = 189000; // 9000 RTP units later (90 * 100)
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Rate should be updated to calculated value: 9000 / 100 = 90 // Rate should be updated to calculated value: 9000 / 100 = 90
@ -182,7 +186,7 @@ VOID TEST(RtcAVSyncTest, RateUpdateWithClockDrift)
// Actual: 100ms -> 9010 RTP units (slight drift) // Actual: 100ms -> 9010 RTP units (slight drift)
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 1100; ntp2.system_ms_ = 1100;
uint32_t rtp_time2 = 99010; // Slightly more than expected uint32_t rtp_time2 = 99010; // Slightly more than expected
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Rate should be updated to: round(9010 / 100) = 90 // Rate should be updated to: round(9010 / 100) = 90
@ -203,7 +207,7 @@ VOID TEST(RtcAVSyncTest, RateUpdateWithLargeInterval)
// Second SR (1000ms later) // Second SR (1000ms later)
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 6000; ntp2.system_ms_ = 6000;
uint32_t rtp_time2 = 288000; // 48000 RTP units later (48 * 1000) uint32_t rtp_time2 = 288000; // 48000 RTP units later (48 * 1000)
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Rate should be: 48000 / 1000 = 48 // Rate should be: 48000 / 1000 = 48
@ -249,9 +253,9 @@ VOID TEST(RtcAVSyncTest, ImmediateAVSyncAvailability)
track.test_update_send_report_time(ntp1, rtp_time1); track.test_update_send_report_time(ntp1, rtp_time1);
// Should be able to calculate avsync_time immediately (not -1) // Should be able to calculate avsync_time immediately (not -1)
int64_t avsync_time = track.test_cal_avsync_time(48480); // 10ms later int64_t avsync_time = track.test_cal_avsync_time(48480); // 10ms later
EXPECT_GT(avsync_time, 0); // Should be > 0, not -1 EXPECT_GT(avsync_time, 0); // Should be > 0, not -1
EXPECT_EQ(1010, avsync_time); // Should be 1000 + 10 = 1010 EXPECT_EQ(1010, avsync_time); // Should be 1000 + 10 = 1010
} }
// Test: RTP timestamp wraparound handling // Test: RTP timestamp wraparound handling
@ -262,13 +266,13 @@ VOID TEST(RtcAVSyncTest, RTPTimestampWraparound)
// First SR near wraparound // First SR near wraparound
SrsNtp ntp1; SrsNtp ntp1;
ntp1.system_ms_ = 1000; ntp1.system_ms_ = 1000;
uint32_t rtp_time1 = 0xFFFFF000; // Near max uint32_t uint32_t rtp_time1 = 0xFFFFF000; // Near max uint32_t
track.test_update_send_report_time(ntp1, rtp_time1); track.test_update_send_report_time(ntp1, rtp_time1);
// Second SR after wraparound // Second SR after wraparound
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 1020; // 20ms later ntp2.system_ms_ = 1020; // 20ms later
uint32_t rtp_time2 = 0x000003C0; // Wrapped around, 960 units after wraparound uint32_t rtp_time2 = 0x000003C0; // Wrapped around, 960 units after wraparound
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Note: Current implementation may not handle wraparound correctly // Note: Current implementation may not handle wraparound correctly
@ -292,11 +296,10 @@ VOID TEST(RtcAVSyncTest, ZeroTimeElapsedBetweenSRs)
// Second SR with same timestamp (0ms elapsed) // Second SR with same timestamp (0ms elapsed)
SrsNtp ntp2; SrsNtp ntp2;
ntp2.system_ms_ = 1000; // Same time ntp2.system_ms_ = 1000; // Same time
uint32_t rtp_time2 = 48000; // Same RTP time uint32_t rtp_time2 = 48000; // Same RTP time
track.test_update_send_report_time(ntp2, rtp_time2); track.test_update_send_report_time(ntp2, rtp_time2);
// Rate should remain unchanged (SDP rate) // Rate should remain unchanged (SDP rate)
EXPECT_DOUBLE_EQ(rate_before, track.get_rate()); EXPECT_DOUBLE_EQ(rate_before, track.get_rate());
} }

View File

@ -9,4 +9,3 @@
#include <srs_utest.hpp> #include <srs_utest.hpp>
#endif #endif

View File

@ -180,6 +180,10 @@ MockRtcTrackDescriptionFactory::MockRtcTrackDescriptionFactory()
audio_ssrc_ = 12345; audio_ssrc_ = 12345;
video_ssrc_ = 67890; video_ssrc_ = 67890;
screen_ssrc_ = 98765; screen_ssrc_ = 98765;
audio_pt_ = 111;
video_pt_ = 96;
screen_pt_ = 97;
} }
MockRtcTrackDescriptionFactory::~MockRtcTrackDescriptionFactory() MockRtcTrackDescriptionFactory::~MockRtcTrackDescriptionFactory()
@ -227,12 +231,17 @@ SrsRtcTrackDescription *MockRtcTrackDescriptionFactory::create_audio_track(uint3
audio_desc->is_active_ = true; audio_desc->is_active_ = true;
audio_desc->direction_ = "sendrecv"; audio_desc->direction_ = "sendrecv";
audio_desc->mid_ = mid; audio_desc->mid_ = mid;
audio_desc->media_ = new SrsAudioPayload(111, "opus", 48000, 2); audio_desc->media_ = new SrsAudioPayload(audio_pt_, "opus", 48000, 2);
return audio_desc; return audio_desc;
} }
SrsRtcTrackDescription *MockRtcTrackDescriptionFactory::create_video_track(uint32_t ssrc, std::string id, std::string mid) SrsRtcTrackDescription *MockRtcTrackDescriptionFactory::create_video_track(uint32_t ssrc, std::string id, std::string mid)
{ {
uint8_t pt = video_pt_;
if (ssrc == screen_ssrc_) {
pt = screen_pt_;
}
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription(); SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
video_desc->type_ = "video"; video_desc->type_ = "video";
video_desc->ssrc_ = ssrc; video_desc->ssrc_ = ssrc;
@ -240,7 +249,7 @@ SrsRtcTrackDescription *MockRtcTrackDescriptionFactory::create_video_track(uint3
video_desc->is_active_ = true; video_desc->is_active_ = true;
video_desc->direction_ = "sendrecv"; video_desc->direction_ = "sendrecv";
video_desc->mid_ = mid; video_desc->mid_ = mid;
video_desc->media_ = new SrsVideoPayload(96, "H264", 90000); video_desc->media_ = new SrsVideoPayload(pt, "H264", 90000);
return video_desc; return video_desc;
} }
@ -1160,6 +1169,7 @@ MockLiveSource::MockLiveSource()
on_audio_count_ = 0; on_audio_count_ = 0;
on_video_count_ = 0; on_video_count_ = 0;
on_dump_packets_count_ = 0; on_dump_packets_count_ = 0;
on_frame_count_ = 0;
} }
MockLiveSource::~MockLiveSource() MockLiveSource::~MockLiveSource()
@ -1196,16 +1206,25 @@ srs_error_t MockLiveSource::consumer_dumps(ISrsLiveConsumer *consumer, bool ds,
srs_error_t MockLiveSource::on_audio(SrsRtmpCommonMessage *audio) srs_error_t MockLiveSource::on_audio(SrsRtmpCommonMessage *audio)
{ {
on_audio_count_++;
return SrsLiveSource::on_audio(audio); return SrsLiveSource::on_audio(audio);
} }
srs_error_t MockLiveSource::on_video(SrsRtmpCommonMessage *video) srs_error_t MockLiveSource::on_video(SrsRtmpCommonMessage *video)
{ {
on_video_count_++;
return SrsLiveSource::on_video(video); return SrsLiveSource::on_video(video);
} }
srs_error_t MockLiveSource::on_frame(SrsMediaPacket *msg)
{
on_frame_count_++;
if (msg->is_audio()) {
on_audio_count_++;
} else if (msg->is_video()) {
on_video_count_++;
}
return SrsLiveSource::on_frame(msg);
}
// Mock SRT source implementation // Mock SRT source implementation
MockSrtSource::MockSrtSource() MockSrtSource::MockSrtSource()
{ {

View File

@ -110,6 +110,11 @@ public:
uint32_t video_ssrc_; uint32_t video_ssrc_;
uint32_t screen_ssrc_; uint32_t screen_ssrc_;
public:
uint8_t audio_pt_;
uint8_t video_pt_;
uint8_t screen_pt_;
public: public:
// Create a map of track descriptions with audio and video tracks (for play stream) // Create a map of track descriptions with audio and video tracks (for play stream)
std::map<uint32_t, SrsRtcTrackDescription *> create_audio_video_tracks(); std::map<uint32_t, SrsRtcTrackDescription *> create_audio_video_tracks();
@ -634,6 +639,7 @@ public:
int on_audio_count_; int on_audio_count_;
int on_video_count_; int on_video_count_;
int on_dump_packets_count_; int on_dump_packets_count_;
int on_frame_count_;
public: public:
MockLiveSource(); MockLiveSource();
@ -647,6 +653,7 @@ public:
public: public:
virtual srs_error_t on_audio(SrsRtmpCommonMessage *audio); virtual srs_error_t on_audio(SrsRtmpCommonMessage *audio);
virtual srs_error_t on_video(SrsRtmpCommonMessage *video); virtual srs_error_t on_video(SrsRtmpCommonMessage *video);
virtual srs_error_t on_frame(SrsMediaPacket *msg);
}; };
// Mock SRT source for testing SrsRtcPublishStream // Mock SRT source for testing SrsRtcPublishStream

View File

@ -0,0 +1,240 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Winlin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <srs_utest_workflow_rtc2rtmp.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_rtc_codec.hpp>
#include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_server.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_app_stream_token.hpp>
#include <srs_kernel_error.hpp>
#include <srs_protocol_rtmp_stack.hpp>
#include <srs_utest_ai11.hpp>
#include <srs_utest_manual_mock.hpp>
#include <srs_utest_manual_service.hpp>
#include <srs_utest_workflow_rtc_conn.hpp>
// Create a mock audio cache ISrsRtcFrameBuilderAudioPacketCache
class MockAudioCache : public ISrsRtcFrameBuilderAudioPacketCache
{
public:
int process_packet_count_;
public:
MockAudioCache();
virtual ~MockAudioCache();
public:
virtual srs_error_t process_packet(SrsRtpPacket *src, std::vector<SrsRtpPacket *> &ready_packets);
virtual void clear_all();
};
MockAudioCache::MockAudioCache()
{
process_packet_count_ = 0;
}
MockAudioCache::~MockAudioCache()
{
}
srs_error_t MockAudioCache::process_packet(SrsRtpPacket *src, std::vector<SrsRtpPacket *> &ready_packets)
{
process_packet_count_++;
// Copy the packet.
SrsRtpPacket *copy = src->copy();
ready_packets.push_back(copy);
return srs_success;
}
void MockAudioCache::clear_all()
{
}
// Mock the audio transcoder ISrsAudioTranscoder.
class MockAudioTranscoderForRtc2Rtmp : public ISrsAudioTranscoder
{
public:
int transcode_count_;
std::vector<SrsParsedAudioPacket *> output_packets_;
std::string aac_header_;
public:
MockAudioTranscoderForRtc2Rtmp();
virtual ~MockAudioTranscoderForRtc2Rtmp();
public:
virtual srs_error_t initialize(SrsAudioCodecId from, SrsAudioCodecId to, int channels, int sample_rate, int bit_rate);
virtual srs_error_t transcode(SrsParsedAudioPacket *in, std::vector<SrsParsedAudioPacket *> &outs);
virtual void free_frames(std::vector<SrsParsedAudioPacket *> &frames);
virtual void aac_codec_header(uint8_t **data, int *len);
};
MockAudioTranscoderForRtc2Rtmp::MockAudioTranscoderForRtc2Rtmp()
{
transcode_count_ = 0;
}
MockAudioTranscoderForRtc2Rtmp::~MockAudioTranscoderForRtc2Rtmp()
{
}
srs_error_t MockAudioTranscoderForRtc2Rtmp::initialize(SrsAudioCodecId from, SrsAudioCodecId to, int channels, int sample_rate, int bit_rate)
{
return srs_success;
}
srs_error_t MockAudioTranscoderForRtc2Rtmp::transcode(SrsParsedAudioPacket *in, std::vector<SrsParsedAudioPacket *> &outs)
{
transcode_count_++;
SrsParsedAudioPacket *out = in->copy();
output_packets_.push_back(out);
outs.push_back(out);
return srs_success;
}
void MockAudioTranscoderForRtc2Rtmp::free_frames(std::vector<SrsParsedAudioPacket *> &frames)
{
}
void MockAudioTranscoderForRtc2Rtmp::aac_codec_header(uint8_t **data, int *len)
{
int size = aac_header_.size();
uint8_t *copy = new uint8_t[size];
memcpy(copy, aac_header_.data(), size);
*data = copy;
*len = size;
}
// This test is used to verify the basic workflow of the RTC connection.
// It's finished with the help of AI, but each step is manually designed
// and verified. So this is not dominated by AI, but by humanbeing.
VOID TEST(BasicWorkflowRtc2RtmpTest, ManuallyVerifyTypicalScenario)
{
srs_error_t err;
// Create mock objects for dependencies
SrsUniquePtr<MockAppConfig> mock_config(new MockAppConfig());
SrsUniquePtr<MockRtcSourceManager> mock_rtc_sources(new MockRtcSourceManager());
SrsUniquePtr<MockAppStatistic> mock_stat(new MockAppStatistic());
SrsUniquePtr<MockRtcAsyncCallRequest> mock_request(new MockRtcAsyncCallRequest("test.vhost", "live", "stream1"));
SrsUniquePtr<MockRtcAsyncTaskExecutor> mock_exec(new MockRtcAsyncTaskExecutor());
SrsUniquePtr<MockExpire> mock_expire(new MockExpire());
SrsUniquePtr<MockRtcPacketReceiver> mock_receiver(new MockRtcPacketReceiver());
SrsUniquePtr<MockRtcTrackDescriptionFactory> track_factory(new MockRtcTrackDescriptionFactory());
SrsUniquePtr<MockLiveSourceManager> mock_sources(new MockLiveSourceManager());
MockAudioCache *mock_audio_cache = new MockAudioCache();
MockAudioTranscoderForRtc2Rtmp *mock_audio_transcoder = new MockAudioTranscoderForRtc2Rtmp();
mock_audio_transcoder->aac_header_ = std::string("\xAF\x00\x12\x10", 4); // AAC sequence header.
mock_config->rtc_to_rtmp_ = true;
// Create RTC publish stream - use real pli_worker_
SrsContextId cid;
cid.set_value("test-rtc2rtmp-workflow-typical-scenario");
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(mock_exec.get(), mock_expire.get(), mock_receiver.get(), cid));
// Mock the publish stream object
if (true) {
// Inject mock dependencies
publish_stream->config_ = mock_config.get();
publish_stream->rtc_sources_ = mock_rtc_sources.get();
publish_stream->live_sources_ = mock_sources.get();
publish_stream->stat_ = mock_stat.get();
}
// Initialize publish stream, rtc2rtmp bridge should be created
SrsRtcBridge *bridge = NULL;
SrsLiveSource *live_source = NULL;
SrsRtcFrameBuilder *frame_builder = NULL;
if (true) {
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(track_factory->create_stream_description());
// Initialize the publish stream (it will take ownership of track descriptions)
HELPER_EXPECT_SUCCESS(publish_stream->initialize(mock_request.get(), stream_desc.get()));
// Check the tracks, should be one audio track
EXPECT_EQ(publish_stream->audio_tracks_.size(), 1);
// Check the tracks, should be one video track
EXPECT_EQ(publish_stream->video_tracks_.size(), 1);
// source bridge should be created
bridge = dynamic_cast<SrsRtcBridge *>(publish_stream->source_->rtc_bridge_);
EXPECT_TRUE(bridge != NULL);
live_source = bridge->rtmp_target_.get();
EXPECT_TRUE(live_source != NULL);
frame_builder = bridge->frame_builder_;
EXPECT_TRUE(frame_builder != NULL);
}
// Start the publish stream.
if (true) {
// Test: First call to start() should succeed
HELPER_EXPECT_SUCCESS(publish_stream->start());
// Verify is_sender_started_ flag is set
EXPECT_TRUE(publish_stream->is_sender_started_);
// When starting the publish stream, the frame builder should be recreated
EXPECT_TRUE(frame_builder != bridge->frame_builder_);
frame_builder = bridge->frame_builder_;
EXPECT_TRUE(frame_builder != NULL);
// Mock the frame builder object
srs_freep(frame_builder->audio_cache_);
frame_builder->audio_cache_ = mock_audio_cache;
srs_freep(frame_builder->audio_transcoder_);
frame_builder->audio_transcoder_ = mock_audio_transcoder;
}
// Got a RTP audio packet.
MockLiveSource *mock_source = dynamic_cast<MockLiveSource *>(mock_sources->mock_source_.get());
if (true) {
SrsRtpPacket pkt;
pkt.header_.set_ssrc(track_factory->audio_ssrc_);
pkt.header_.set_sequence(100);
pkt.header_.set_timestamp(1000);
pkt.header_.set_payload_type(track_factory->audio_pt_);
SrsUniquePtr<char[]> data(new char[1500]);
SrsBuffer buf(data.get(), 1500);
HELPER_EXPECT_SUCCESS(pkt.encode(&buf));
HELPER_EXPECT_SUCCESS(publish_stream->on_rtp_plaintext(data.get(), buf.pos()));
// The live source should got 2 audio packets, one is sequence header, another is audio data.
EXPECT_EQ(mock_source->on_audio_count_, 2);
EXPECT_EQ(mock_source->on_frame_count_, 2);
EXPECT_EQ(mock_source->on_video_count_, 0);
}
publish_stream->stop();
}

View File

@ -0,0 +1,32 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2013-2025 Winlin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SRS_UTEST_WORKFLOW_RTC2RTMP_HPP
#define SRS_UTEST_WORKFLOW_RTC2RTMP_HPP
/*
#include <srs_utest_workflow_rtc2rtmp.hpp>
*/
#include <srs_utest.hpp>
#endif

View File

@ -21,11 +21,11 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#ifndef SRS_UTEST_RTC_CONN_HPP #ifndef SRS_UTEST_WORKFLOW_RTC_CONN_HPP
#define SRS_UTEST_RTC_CONN_HPP #define SRS_UTEST_WORKFLOW_RTC_CONN_HPP
/* /*
#include <srs_utest_rtc_conn.hpp> #include <srs_utest_workflow_rtc_conn.hpp>
*/ */
#include <srs_utest.hpp> #include <srs_utest.hpp>

View File

@ -221,9 +221,4 @@ VOID TEST(BasicWorkflowRtcPlayStreamTest, ManuallyVerify)
// Stop the play stream // Stop the play stream
play_stream->stop(); play_stream->stop();
// Clean up - set to NULL to avoid double-free
play_stream->config_ = NULL;
play_stream->rtc_sources_ = NULL;
play_stream->stat_ = NULL;
} }

View File

@ -96,9 +96,4 @@ VOID TEST(BasicWorkflowRtcPublishStreamTest, ManuallyVerify)
// Stop the publish stream // Stop the publish stream
publish_stream->stop(); publish_stream->stop();
// Clean up - set injected fields to NULL to avoid double-free
publish_stream->config_ = NULL;
publish_stream->rtc_sources_ = NULL;
publish_stream->stat_ = NULL;
} }