From 8b76e1f6d20aca6edf87ed0b333e01c8c99a26bb Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Fri, 17 Oct 2025 07:58:32 -0400 Subject: [PATCH] AI: Add workflow utest for rtc publisher --- trunk/configure | 4 +- trunk/src/app/srs_app_rtc_conn.cpp | 86 +++++++++++--- trunk/src/app/srs_app_rtc_conn.hpp | 52 +++++++-- trunk/src/utest/srs_utest_app6.cpp | 82 -------------- trunk/src/utest/srs_utest_app6.hpp | 33 ------ trunk/src/utest/srs_utest_mock.cpp | 98 +++++++++++++++- trunk/src/utest/srs_utest_mock.hpp | 38 ++++++- .../src/utest/srs_utest_rtc_publishstream.cpp | 105 ++++++++++++++++++ .../src/utest/srs_utest_rtc_publishstream.hpp | 30 +++++ 9 files changed, 389 insertions(+), 139 deletions(-) create mode 100644 trunk/src/utest/srs_utest_rtc_publishstream.cpp create mode 100644 trunk/src/utest/srs_utest_rtc_publishstream.hpp diff --git a/trunk/configure b/trunk/configure index 5a2fcc528..1863c0866 100755 --- a/trunk/configure +++ b/trunk/configure @@ -384,8 +384,8 @@ if [[ $SRS_UTEST == YES ]]; then "srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4" "srs_utest_protocol3" "srs_utest_app" "srs_utest_app2" "srs_utest_app3" "srs_utest_app4" "srs_utest_app5" "srs_utest_app6" "srs_utest_app7" "srs_utest_app8" "srs_utest_app9" - "srs_utest_app10" "srs_utest_app11" "srs_utest_app15" "srs_utest_app16" - "srs_utest_app17" "srs_utest_mock" "srs_utest_rtc_playstream") + "srs_utest_app10" "srs_utest_app11" "srs_utest_app15" "srs_utest_app16" "srs_utest_app17" + "srs_utest_mock" "srs_utest_rtc_playstream" "srs_utest_rtc_publishstream") # Always include SRT utest MODULE_FILES+=("srs_utest_srt") if [[ $SRS_GB28181 == YES ]]; then diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 79516167e..868efa925 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -346,6 +346,15 @@ void SrsRtcPliWorker::request_keyframe(uint32_t ssrc, SrsContextId cid) wait_->signal(); } +void SrsRtcPliWorker::stop() +{ + wait_->signal(); + + if (trd_) { + trd_->stop(); + } +} + srs_error_t SrsRtcPliWorker::cycle() { srs_error_t err = srs_success; @@ -963,19 +972,37 @@ ISrsRtcRtcpSender::~ISrsRtcRtcpSender() { } +ISrsRtcPublishRtcpTimer::ISrsRtcPublishRtcpTimer() +{ +} + +ISrsRtcPublishRtcpTimer::~ISrsRtcPublishRtcpTimer() +{ +} + SrsRtcPublishRtcpTimer::SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender) : sender_(sender) { lock_ = srs_mutex_new(); - _srs_shared_timer->timer1s()->subscribe(this); + + shared_timer_ = _srs_shared_timer; } SrsRtcPublishRtcpTimer::~SrsRtcPublishRtcpTimer() { - if (true) { + if (shared_timer_) { SrsLocker(&lock_); - _srs_shared_timer->timer1s()->unsubscribe(this); + shared_timer_->timer1s()->unsubscribe(this); } srs_mutex_destroy(lock_); + + shared_timer_ = NULL; +} + +srs_error_t SrsRtcPublishRtcpTimer::initialize() +{ + shared_timer_->timer1s()->subscribe(this); + + return srs_success; } srs_error_t SrsRtcPublishRtcpTimer::on_timer(srs_utime_t interval) @@ -1010,23 +1037,39 @@ srs_error_t SrsRtcPublishRtcpTimer::on_timer(srs_utime_t interval) return err; } +ISrsRtcPublishTwccTimer::ISrsRtcPublishTwccTimer() +{ +} + +ISrsRtcPublishTwccTimer::~ISrsRtcPublishTwccTimer() +{ +} + SrsRtcPublishTwccTimer::SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender) : sender_(sender) { lock_ = srs_mutex_new(); - _srs_shared_timer->timer100ms()->subscribe(this); circuit_breaker_ = _srs_circuit_breaker; + shared_timer_ = _srs_shared_timer; } SrsRtcPublishTwccTimer::~SrsRtcPublishTwccTimer() { - if (true) { + if (shared_timer_) { SrsLocker(&lock_); - _srs_shared_timer->timer100ms()->unsubscribe(this); + shared_timer_->timer100ms()->unsubscribe(this); } srs_mutex_destroy(lock_); circuit_breaker_ = NULL; + shared_timer_ = NULL; +} + +srs_error_t SrsRtcPublishTwccTimer::initialize() +{ + shared_timer_->timer100ms()->subscribe(this); + + return srs_success; } srs_error_t SrsRtcPublishTwccTimer::on_timer(srs_utime_t interval) @@ -1152,6 +1195,7 @@ SrsRtcPublishStream::SrsRtcPublishStream(ISrsExecRtcAsyncTask *exec, ISrsExpire timer_rtcp_ = new SrsRtcPublishRtcpTimer(this); timer_twcc_ = new SrsRtcPublishTwccTimer(this); + rtcp_twcc_ = new SrsRtcpTWCC(); stat_ = _srs_stat; config_ = _srs_config; @@ -1169,6 +1213,7 @@ SrsRtcPublishStream::~SrsRtcPublishStream() srs_freep(timer_rtcp_); srs_freep(timer_twcc_); + srs_freep(rtcp_twcc_); source_->set_publish_stream(NULL); source_->on_unpublish(); @@ -1192,7 +1237,9 @@ SrsRtcPublishStream::~SrsRtcPublishStream() // update the statistic when client coveried. // TODO: FIXME: Should finger out the err. - stat_->on_disconnect(cid_.c_str(), srs_success); + if (stat_) { + stat_->on_disconnect(cid_.c_str(), srs_success); + } // Optional but just to make it clear. stat_ = NULL; @@ -1209,6 +1256,14 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript req_ = r->copy(); + if ((err = timer_rtcp_->initialize()) != srs_success) { + return srs_error_wrap(err, "initialize timer rtcp"); + } + + if ((err = timer_twcc_->initialize()) != srs_success) { + return srs_error_wrap(err, "initialize timer twcc"); + } + // We must do stat the client before hooks, because hooks depends on it. if ((err = stat_->on_client(cid_.c_str(), req_, expire_, SrsRtcConnPublish)) != srs_success) { return srs_error_wrap(err, "rtc: stat client"); @@ -1236,7 +1291,7 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript if (twcc_id > 0) { twcc_id_ = twcc_id; extension_types_.register_by_uri(twcc_id_, kTWCCExt); - rtcp_twcc_.set_media_ssrc(media_ssrc); + rtcp_twcc_->set_media_ssrc(media_ssrc); } nack_enabled_ = config_->get_rtc_nack_enabled(req_->vhost_); @@ -1346,6 +1401,11 @@ srs_error_t SrsRtcPublishStream::start() return err; } +void SrsRtcPublishStream::stop() +{ + pli_worker_->stop(); +} + void SrsRtcPublishStream::set_all_tracks_status(bool status) { std::ostringstream merged_log; @@ -1437,7 +1497,7 @@ srs_error_t SrsRtcPublishStream::on_twcc(uint16_t sn) srs_error_t err = srs_success; srs_utime_t now = srs_time_now_cached(); - err = rtcp_twcc_.recv_packet(sn, now); + err = rtcp_twcc_->recv_packet(sn, now); return err; } @@ -1621,21 +1681,21 @@ srs_error_t SrsRtcPublishStream::send_periodic_twcc() } last_time_send_twcc_ = srs_time_now_cached(); - if (!rtcp_twcc_.need_feedback()) { + if (!rtcp_twcc_->need_feedback()) { return err; } ++_srs_pps_srtcps->sugar_; // limit the max count=1024 to avoid dead loop. - for (int i = 0; i < 1024 && rtcp_twcc_.need_feedback(); ++i) { + for (int i = 0; i < 1024 && rtcp_twcc_->need_feedback(); ++i) { char pkt[kMaxUDPDataSize]; SrsUniquePtr buffer(new SrsBuffer(pkt, sizeof(pkt))); - rtcp_twcc_.set_feedback_count(twcc_fb_count_); + rtcp_twcc_->set_feedback_count(twcc_fb_count_); twcc_fb_count_++; - if ((err = rtcp_twcc_.encode(buffer.get())) != srs_success) { + if ((err = rtcp_twcc_->encode(buffer.get())) != srs_success) { return srs_error_wrap(err, "encode, count=%u", twcc_fb_count_); } diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index d899e22a7..7698e9c94 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -204,6 +204,7 @@ public: public: virtual srs_error_t start() = 0; virtual void request_keyframe(uint32_t ssrc, SrsContextId cid) = 0; + virtual void stop() = 0; }; // A worker coroutine to request the PLI. @@ -228,6 +229,7 @@ public: public: virtual srs_error_t start(); virtual void request_keyframe(uint32_t ssrc, SrsContextId cid); + virtual void stop(); // interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); @@ -369,9 +371,24 @@ public: virtual srs_error_t send_periodic_twcc() = 0; }; -// A fast timer for publish stream, for RTCP feedback. -class SrsRtcPublishRtcpTimer : public ISrsFastTimerHandler +// The RTC publish RTCP timer interface. +class ISrsRtcPublishRtcpTimer: public ISrsFastTimerHandler { +public: + ISrsRtcPublishRtcpTimer(); + virtual ~ISrsRtcPublishRtcpTimer(); + +public: + virtual srs_error_t initialize() = 0; +}; + +// A fast timer for publish stream, for RTCP feedback. +class SrsRtcPublishRtcpTimer : public ISrsRtcPublishRtcpTimer +{ +// clang-format off +SRS_DECLARE_PRIVATE: // clang-format on + ISrsSharedTimer *shared_timer_; + // clang-format off SRS_DECLARE_PRIVATE: // clang-format on ISrsRtcRtcpSender *sender_; @@ -380,17 +397,33 @@ SRS_DECLARE_PRIVATE: // clang-format on public: SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender); virtual ~SrsRtcPublishRtcpTimer(); + +public: + virtual srs_error_t initialize(); + // interface ISrsFastTimerHandler // clang-format off SRS_DECLARE_PRIVATE: // clang-format on srs_error_t on_timer(srs_utime_t interval); }; +// The RTC publish TWCC timer interface. +class ISrsRtcPublishTwccTimer: public ISrsFastTimerHandler +{ +public: + ISrsRtcPublishTwccTimer(); + virtual ~ISrsRtcPublishTwccTimer(); + +public: + virtual srs_error_t initialize() = 0; +}; + // A fast timer for publish stream, for TWCC feedback. -class SrsRtcPublishTwccTimer : public ISrsFastTimerHandler +class SrsRtcPublishTwccTimer : public ISrsRtcPublishTwccTimer { // clang-format off SRS_DECLARE_PRIVATE: // clang-format on + ISrsSharedTimer *shared_timer_; ISrsCircuitBreaker *circuit_breaker_; // clang-format off @@ -401,6 +434,10 @@ SRS_DECLARE_PRIVATE: // clang-format on public: SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender); virtual ~SrsRtcPublishTwccTimer(); + +public: + virtual srs_error_t initialize(); + // interface ISrsFastTimerHandler // clang-format off SRS_DECLARE_PRIVATE: // clang-format on @@ -451,14 +488,14 @@ SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on friend class SrsRtcPublishRtcpTimer; friend class SrsRtcPublishTwccTimer; - SrsRtcPublishRtcpTimer *timer_rtcp_; - SrsRtcPublishTwccTimer *timer_twcc_; + ISrsRtcPublishRtcpTimer *timer_rtcp_; + ISrsRtcPublishTwccTimer *timer_twcc_; // clang-format off SRS_DECLARE_PRIVATE: // clang-format on SrsContextId cid_; uint64_t nn_audio_frames_; - SrsRtcPliWorker *pli_worker_; + ISrsRtcPliWorker *pli_worker_; SrsErrorPithyPrint *twcc_epp_; // clang-format off @@ -492,7 +529,7 @@ SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on int twcc_id_; uint8_t twcc_fb_count_; - SrsRtcpTWCC rtcp_twcc_; + SrsRtcpTWCC *rtcp_twcc_; SrsRtpExtensionTypes extension_types_; bool is_sender_started_; srs_utime_t last_time_send_twcc_; @@ -504,6 +541,7 @@ public: public: srs_error_t initialize(ISrsRequest *req, SrsRtcSourceDescription *stream_desc); srs_error_t start(); + void stop(); // Directly set the status of track, generally for init to set the default value. void set_all_tracks_status(bool status); virtual const SrsContextId &context_id(); diff --git a/trunk/src/utest/srs_utest_app6.cpp b/trunk/src/utest/srs_utest_app6.cpp index 681e3f11f..b1392c00e 100644 --- a/trunk/src/utest/srs_utest_app6.cpp +++ b/trunk/src/utest/srs_utest_app6.cpp @@ -3439,88 +3439,6 @@ void MockRtcRtcpSender::reset() send_periodic_twcc_count_ = 0; } -// Mock RTC packet receiver implementation -MockRtcPacketReceiver::MockRtcPacketReceiver() -{ - send_rtcp_rr_error_ = srs_success; - send_rtcp_xr_rrtr_error_ = srs_success; - send_rtcp_error_ = srs_success; - send_rtcp_fb_pli_error_ = srs_success; - send_rtcp_rr_count_ = 0; - send_rtcp_xr_rrtr_count_ = 0; - send_rtcp_count_ = 0; - send_rtcp_fb_pli_count_ = 0; - check_send_nacks_count_ = 0; -} - -MockRtcPacketReceiver::~MockRtcPacketReceiver() -{ -} - -srs_error_t MockRtcPacketReceiver::send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer *rtp_queue, const uint64_t &last_send_systime, const SrsNtp &last_send_ntp) -{ - send_rtcp_rr_count_++; - return send_rtcp_rr_error_; -} - -srs_error_t MockRtcPacketReceiver::send_rtcp_xr_rrtr(uint32_t ssrc) -{ - send_rtcp_xr_rrtr_count_++; - return send_rtcp_xr_rrtr_error_; -} - -void MockRtcPacketReceiver::check_send_nacks(SrsRtpNackForReceiver *nack, uint32_t ssrc, uint32_t &sent_nacks, uint32_t &timeout_nacks) -{ - check_send_nacks_count_++; - sent_nacks = 0; - timeout_nacks = 0; -} - -srs_error_t MockRtcPacketReceiver::send_rtcp(char *data, int nb_data) -{ - send_rtcp_count_++; - return send_rtcp_error_; -} - -srs_error_t MockRtcPacketReceiver::send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId &cid_of_subscriber) -{ - send_rtcp_fb_pli_count_++; - return send_rtcp_fb_pli_error_; -} - -void MockRtcPacketReceiver::set_send_rtcp_rr_error(srs_error_t err) -{ - send_rtcp_rr_error_ = err; -} - -void MockRtcPacketReceiver::set_send_rtcp_xr_rrtr_error(srs_error_t err) -{ - send_rtcp_xr_rrtr_error_ = err; -} - -void MockRtcPacketReceiver::set_send_rtcp_error(srs_error_t err) -{ - send_rtcp_error_ = err; -} - -void MockRtcPacketReceiver::set_send_rtcp_fb_pli_error(srs_error_t err) -{ - send_rtcp_fb_pli_error_ = err; -} - -void MockRtcPacketReceiver::reset() -{ - send_rtcp_rr_error_ = srs_success; - send_rtcp_xr_rrtr_error_ = srs_success; - send_rtcp_error_ = srs_success; - send_rtcp_fb_pli_error_ = srs_success; - send_rtcp_rr_count_ = 0; - send_rtcp_xr_rrtr_count_ = 0; - send_rtcp_count_ = 0; - send_rtcp_fb_pli_count_ = 0; - check_send_nacks_count_ = 0; -} - VOID TEST(SrsRtcPublishRtcpTimerTest, OnTimer) { srs_error_t err; diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 3bef0970b..c8b72214c 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -438,39 +438,6 @@ public: void reset(); }; -// Mock RTC packet receiver for testing SrsRtcPublishStream -class MockRtcPacketReceiver : public ISrsRtcPacketReceiver -{ -public: - srs_error_t send_rtcp_rr_error_; - srs_error_t send_rtcp_xr_rrtr_error_; - srs_error_t send_rtcp_error_; - srs_error_t send_rtcp_fb_pli_error_; - int send_rtcp_rr_count_; - int send_rtcp_xr_rrtr_count_; - int send_rtcp_count_; - int send_rtcp_fb_pli_count_; - int check_send_nacks_count_; - -public: - MockRtcPacketReceiver(); - virtual ~MockRtcPacketReceiver(); - -public: - virtual srs_error_t send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer *rtp_queue, const uint64_t &last_send_systime, const SrsNtp &last_send_ntp); - virtual srs_error_t send_rtcp_xr_rrtr(uint32_t ssrc); - virtual void check_send_nacks(SrsRtpNackForReceiver *nack, uint32_t ssrc, uint32_t &sent_nacks, uint32_t &timeout_nacks); - virtual srs_error_t send_rtcp(char *data, int nb_data); - virtual srs_error_t send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId &cid_of_subscriber); - -public: - void set_send_rtcp_rr_error(srs_error_t err); - void set_send_rtcp_xr_rrtr_error(srs_error_t err); - void set_send_rtcp_error(srs_error_t err); - void set_send_rtcp_fb_pli_error(srs_error_t err); - void reset(); -}; - // Mock expire for testing SrsRtcPublishStream class MockRtcExpire : public ISrsExpire { diff --git a/trunk/src/utest/srs_utest_mock.cpp b/trunk/src/utest/srs_utest_mock.cpp index 53c134d55..e6c2ca2d7 100644 --- a/trunk/src/utest/srs_utest_mock.cpp +++ b/trunk/src/utest/srs_utest_mock.cpp @@ -42,6 +42,19 @@ std::map MockRtcTrackDescriptionFactory::cre return sub_relations; } +SrsRtcSourceDescription *MockRtcTrackDescriptionFactory::create_stream_description() +{ + SrsRtcSourceDescription *stream_desc = new SrsRtcSourceDescription(); + + // Create audio track + stream_desc->audio_track_desc_ = create_audio_track(audio_ssrc_, "audio-track-1", "0"); + + // Create video track + stream_desc->video_track_descs_.push_back(create_video_track(video_ssrc_, "video-track-1", "1")); + + return stream_desc; +} + SrsRtcTrackDescription *MockRtcTrackDescriptionFactory::create_audio_track(uint32_t ssrc, std::string id, std::string mid) { SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); @@ -147,7 +160,8 @@ srs_error_t MockRtcSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtrinitialize(r); } SrsSharedPtr MockRtcSourceManager::fetch(ISrsRequest *r) @@ -805,3 +819,85 @@ void MockAppConfig::set_keep_api_domain(bool enabled) { keep_api_domain_ = enabled; } + +// Mock RTC packet receiver implementation +MockRtcPacketReceiver::MockRtcPacketReceiver() +{ + send_rtcp_rr_error_ = srs_success; + send_rtcp_xr_rrtr_error_ = srs_success; + send_rtcp_error_ = srs_success; + send_rtcp_fb_pli_error_ = srs_success; + send_rtcp_rr_count_ = 0; + send_rtcp_xr_rrtr_count_ = 0; + send_rtcp_count_ = 0; + send_rtcp_fb_pli_count_ = 0; + check_send_nacks_count_ = 0; +} + +MockRtcPacketReceiver::~MockRtcPacketReceiver() +{ +} + +srs_error_t MockRtcPacketReceiver::send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer *rtp_queue, const uint64_t &last_send_systime, const SrsNtp &last_send_ntp) +{ + send_rtcp_rr_count_++; + return send_rtcp_rr_error_; +} + +srs_error_t MockRtcPacketReceiver::send_rtcp_xr_rrtr(uint32_t ssrc) +{ + send_rtcp_xr_rrtr_count_++; + return send_rtcp_xr_rrtr_error_; +} + +void MockRtcPacketReceiver::check_send_nacks(SrsRtpNackForReceiver *nack, uint32_t ssrc, uint32_t &sent_nacks, uint32_t &timeout_nacks) +{ + check_send_nacks_count_++; + sent_nacks = 0; + timeout_nacks = 0; +} + +srs_error_t MockRtcPacketReceiver::send_rtcp(char *data, int nb_data) +{ + send_rtcp_count_++; + return send_rtcp_error_; +} + +srs_error_t MockRtcPacketReceiver::send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId &cid_of_subscriber) +{ + send_rtcp_fb_pli_count_++; + return send_rtcp_fb_pli_error_; +} + +void MockRtcPacketReceiver::set_send_rtcp_rr_error(srs_error_t err) +{ + send_rtcp_rr_error_ = err; +} + +void MockRtcPacketReceiver::set_send_rtcp_xr_rrtr_error(srs_error_t err) +{ + send_rtcp_xr_rrtr_error_ = err; +} + +void MockRtcPacketReceiver::set_send_rtcp_error(srs_error_t err) +{ + send_rtcp_error_ = err; +} + +void MockRtcPacketReceiver::set_send_rtcp_fb_pli_error(srs_error_t err) +{ + send_rtcp_fb_pli_error_ = err; +} + +void MockRtcPacketReceiver::reset() +{ + send_rtcp_rr_error_ = srs_success; + send_rtcp_xr_rrtr_error_ = srs_success; + send_rtcp_error_ = srs_success; + send_rtcp_fb_pli_error_ = srs_success; + send_rtcp_rr_count_ = 0; + send_rtcp_xr_rrtr_count_ = 0; + send_rtcp_count_ = 0; + send_rtcp_fb_pli_count_ = 0; + check_send_nacks_count_ = 0; +} diff --git a/trunk/src/utest/srs_utest_mock.hpp b/trunk/src/utest/srs_utest_mock.hpp index c66d81b60..2c76d5e17 100644 --- a/trunk/src/utest/srs_utest_mock.hpp +++ b/trunk/src/utest/srs_utest_mock.hpp @@ -38,9 +38,12 @@ public: uint32_t screen_ssrc_; public: - // Create a map of track descriptions with audio and video tracks + // Create a map of track descriptions with audio and video tracks (for play stream) std::map create_audio_video_tracks(); + // Create a stream description with audio and video tracks (for publish stream) + SrsRtcSourceDescription *create_stream_description(); + // Create a single audio track description SrsRtcTrackDescription *create_audio_track(uint32_t ssrc, std::string id, std::string mid); @@ -467,4 +470,37 @@ public: void set_keep_api_domain(bool enabled); }; +// Mock RTC packet receiver for testing SrsRtcPublishStream +class MockRtcPacketReceiver : public ISrsRtcPacketReceiver +{ +public: + srs_error_t send_rtcp_rr_error_; + srs_error_t send_rtcp_xr_rrtr_error_; + srs_error_t send_rtcp_error_; + srs_error_t send_rtcp_fb_pli_error_; + int send_rtcp_rr_count_; + int send_rtcp_xr_rrtr_count_; + int send_rtcp_count_; + int send_rtcp_fb_pli_count_; + int check_send_nacks_count_; + +public: + MockRtcPacketReceiver(); + virtual ~MockRtcPacketReceiver(); + +public: + virtual srs_error_t send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer *rtp_queue, const uint64_t &last_send_systime, const SrsNtp &last_send_ntp); + virtual srs_error_t send_rtcp_xr_rrtr(uint32_t ssrc); + virtual void check_send_nacks(SrsRtpNackForReceiver *nack, uint32_t ssrc, uint32_t &sent_nacks, uint32_t &timeout_nacks); + virtual srs_error_t send_rtcp(char *data, int nb_data); + virtual srs_error_t send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId &cid_of_subscriber); + +public: + void set_send_rtcp_rr_error(srs_error_t err); + void set_send_rtcp_xr_rrtr_error(srs_error_t err); + void set_send_rtcp_error(srs_error_t err); + void set_send_rtcp_fb_pli_error(srs_error_t err); + void reset(); +}; + #endif diff --git a/trunk/src/utest/srs_utest_rtc_publishstream.cpp b/trunk/src/utest/srs_utest_rtc_publishstream.cpp new file mode 100644 index 000000000..d377420bb --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc_publishstream.cpp @@ -0,0 +1,105 @@ +/** + * 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 + +#include +#include +#include +#include +#include + +// This test is used to verify the basic workflow of the RTC publish stream. +// 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(RtcPublishStreamTest, ManuallyVerifyBasicWorkflow) +{ + srs_error_t err; + + // Create mock objects for dependencies + MockAppConfig mock_config; + MockRtcSourceManager mock_rtc_sources; + MockRtcStatistic mock_stat; + MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1"); + MockRtcAsyncTaskExecutor mock_exec; + MockExpire mock_expire; + MockRtcPacketReceiver mock_receiver; + MockRtcTrackDescriptionFactory track_factory; + SrsContextId cid; + cid.set_value("test-publish-stream-cid"); + + // Create RTC publish stream - use real pli_worker_ + SrsUniquePtr publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid)); + + // Mock the publish stream object + if (true) { + // Inject mock dependencies + publish_stream->config_ = &mock_config; + publish_stream->rtc_sources_ = &mock_rtc_sources; + publish_stream->stat_ = &mock_stat; + } + + // Create stream description with audio and video tracks + if (true) { + SrsUniquePtr 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, 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); + + // 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_); + + // Wait for coroutine to start. Normally it should be ready and stopped at wait + // for PLI requests. + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + } + + // Request a PLI about the video ssrc to the publisher. + if (true) { + uint32_t video_ssrc = track_factory.video_ssrc_; + publish_stream->request_keyframe(video_ssrc, cid); + + // Wait for coroutine to process the request + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Verify the PLI is sent out + EXPECT_EQ(mock_receiver.send_rtcp_fb_pli_count_, 1); + } + + // Stop the publish stream + 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; +} + diff --git a/trunk/src/utest/srs_utest_rtc_publishstream.hpp b/trunk/src/utest/srs_utest_rtc_publishstream.hpp new file mode 100644 index 000000000..0efc16f1e --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc_publishstream.hpp @@ -0,0 +1,30 @@ +/** + * 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_RTC_PUBLISHSTREAM_HPP +#define SRS_UTEST_RTC_PUBLISHSTREAM_HPP + +#include + +#endif +