AI: Add workflow utest for rtc publisher

This commit is contained in:
OSSRS-AI 2025-10-17 07:58:32 -04:00 committed by winlin
parent 0d43ed5dd6
commit 8b76e1f6d2
9 changed files with 389 additions and 139 deletions

4
trunk/configure vendored
View File

@ -384,8 +384,8 @@ if [[ $SRS_UTEST == YES ]]; then
"srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4" "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_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_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_app10" "srs_utest_app11" "srs_utest_app15" "srs_utest_app16" "srs_utest_app17"
"srs_utest_app17" "srs_utest_mock" "srs_utest_rtc_playstream") "srs_utest_mock" "srs_utest_rtc_playstream" "srs_utest_rtc_publishstream")
# Always include SRT utest # Always include SRT utest
MODULE_FILES+=("srs_utest_srt") MODULE_FILES+=("srs_utest_srt")
if [[ $SRS_GB28181 == YES ]]; then if [[ $SRS_GB28181 == YES ]]; then

View File

@ -346,6 +346,15 @@ void SrsRtcPliWorker::request_keyframe(uint32_t ssrc, SrsContextId cid)
wait_->signal(); wait_->signal();
} }
void SrsRtcPliWorker::stop()
{
wait_->signal();
if (trd_) {
trd_->stop();
}
}
srs_error_t SrsRtcPliWorker::cycle() srs_error_t SrsRtcPliWorker::cycle()
{ {
srs_error_t err = srs_success; srs_error_t err = srs_success;
@ -963,19 +972,37 @@ ISrsRtcRtcpSender::~ISrsRtcRtcpSender()
{ {
} }
ISrsRtcPublishRtcpTimer::ISrsRtcPublishRtcpTimer()
{
}
ISrsRtcPublishRtcpTimer::~ISrsRtcPublishRtcpTimer()
{
}
SrsRtcPublishRtcpTimer::SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender) : sender_(sender) SrsRtcPublishRtcpTimer::SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender) : sender_(sender)
{ {
lock_ = srs_mutex_new(); lock_ = srs_mutex_new();
_srs_shared_timer->timer1s()->subscribe(this);
shared_timer_ = _srs_shared_timer;
} }
SrsRtcPublishRtcpTimer::~SrsRtcPublishRtcpTimer() SrsRtcPublishRtcpTimer::~SrsRtcPublishRtcpTimer()
{ {
if (true) { if (shared_timer_) {
SrsLocker(&lock_); SrsLocker(&lock_);
_srs_shared_timer->timer1s()->unsubscribe(this); shared_timer_->timer1s()->unsubscribe(this);
} }
srs_mutex_destroy(lock_); 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) 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; return err;
} }
ISrsRtcPublishTwccTimer::ISrsRtcPublishTwccTimer()
{
}
ISrsRtcPublishTwccTimer::~ISrsRtcPublishTwccTimer()
{
}
SrsRtcPublishTwccTimer::SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender) : sender_(sender) SrsRtcPublishTwccTimer::SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender) : sender_(sender)
{ {
lock_ = srs_mutex_new(); lock_ = srs_mutex_new();
_srs_shared_timer->timer100ms()->subscribe(this);
circuit_breaker_ = _srs_circuit_breaker; circuit_breaker_ = _srs_circuit_breaker;
shared_timer_ = _srs_shared_timer;
} }
SrsRtcPublishTwccTimer::~SrsRtcPublishTwccTimer() SrsRtcPublishTwccTimer::~SrsRtcPublishTwccTimer()
{ {
if (true) { if (shared_timer_) {
SrsLocker(&lock_); SrsLocker(&lock_);
_srs_shared_timer->timer100ms()->unsubscribe(this); shared_timer_->timer100ms()->unsubscribe(this);
} }
srs_mutex_destroy(lock_); srs_mutex_destroy(lock_);
circuit_breaker_ = NULL; 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) 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_rtcp_ = new SrsRtcPublishRtcpTimer(this);
timer_twcc_ = new SrsRtcPublishTwccTimer(this); timer_twcc_ = new SrsRtcPublishTwccTimer(this);
rtcp_twcc_ = new SrsRtcpTWCC();
stat_ = _srs_stat; stat_ = _srs_stat;
config_ = _srs_config; config_ = _srs_config;
@ -1169,6 +1213,7 @@ SrsRtcPublishStream::~SrsRtcPublishStream()
srs_freep(timer_rtcp_); srs_freep(timer_rtcp_);
srs_freep(timer_twcc_); srs_freep(timer_twcc_);
srs_freep(rtcp_twcc_);
source_->set_publish_stream(NULL); source_->set_publish_stream(NULL);
source_->on_unpublish(); source_->on_unpublish();
@ -1192,7 +1237,9 @@ SrsRtcPublishStream::~SrsRtcPublishStream()
// update the statistic when client coveried. // update the statistic when client coveried.
// TODO: FIXME: Should finger out the err. // 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. // Optional but just to make it clear.
stat_ = NULL; stat_ = NULL;
@ -1209,6 +1256,14 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript
req_ = r->copy(); 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. // 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) { if ((err = stat_->on_client(cid_.c_str(), req_, expire_, SrsRtcConnPublish)) != srs_success) {
return srs_error_wrap(err, "rtc: stat client"); return srs_error_wrap(err, "rtc: stat client");
@ -1236,7 +1291,7 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript
if (twcc_id > 0) { if (twcc_id > 0) {
twcc_id_ = twcc_id; twcc_id_ = twcc_id;
extension_types_.register_by_uri(twcc_id_, kTWCCExt); 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_); nack_enabled_ = config_->get_rtc_nack_enabled(req_->vhost_);
@ -1346,6 +1401,11 @@ srs_error_t SrsRtcPublishStream::start()
return err; return err;
} }
void SrsRtcPublishStream::stop()
{
pli_worker_->stop();
}
void SrsRtcPublishStream::set_all_tracks_status(bool status) void SrsRtcPublishStream::set_all_tracks_status(bool status)
{ {
std::ostringstream merged_log; std::ostringstream merged_log;
@ -1437,7 +1497,7 @@ srs_error_t SrsRtcPublishStream::on_twcc(uint16_t sn)
srs_error_t err = srs_success; srs_error_t err = srs_success;
srs_utime_t now = srs_time_now_cached(); srs_utime_t now = srs_time_now_cached();
err = rtcp_twcc_.recv_packet(sn, now); err = rtcp_twcc_->recv_packet(sn, now);
return err; return err;
} }
@ -1621,21 +1681,21 @@ srs_error_t SrsRtcPublishStream::send_periodic_twcc()
} }
last_time_send_twcc_ = srs_time_now_cached(); last_time_send_twcc_ = srs_time_now_cached();
if (!rtcp_twcc_.need_feedback()) { if (!rtcp_twcc_->need_feedback()) {
return err; return err;
} }
++_srs_pps_srtcps->sugar_; ++_srs_pps_srtcps->sugar_;
// limit the max count=1024 to avoid dead loop. // 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]; char pkt[kMaxUDPDataSize];
SrsUniquePtr<SrsBuffer> buffer(new SrsBuffer(pkt, sizeof(pkt))); SrsUniquePtr<SrsBuffer> 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_++; 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_); return srs_error_wrap(err, "encode, count=%u", twcc_fb_count_);
} }

View File

@ -204,6 +204,7 @@ public:
public: public:
virtual srs_error_t start() = 0; virtual srs_error_t start() = 0;
virtual void request_keyframe(uint32_t ssrc, SrsContextId cid) = 0; virtual void request_keyframe(uint32_t ssrc, SrsContextId cid) = 0;
virtual void stop() = 0;
}; };
// A worker coroutine to request the PLI. // A worker coroutine to request the PLI.
@ -228,6 +229,7 @@ public:
public: public:
virtual srs_error_t start(); virtual srs_error_t start();
virtual void request_keyframe(uint32_t ssrc, SrsContextId cid); virtual void request_keyframe(uint32_t ssrc, SrsContextId cid);
virtual void stop();
// interface ISrsCoroutineHandler // interface ISrsCoroutineHandler
public: public:
virtual srs_error_t cycle(); virtual srs_error_t cycle();
@ -369,9 +371,24 @@ public:
virtual srs_error_t send_periodic_twcc() = 0; virtual srs_error_t send_periodic_twcc() = 0;
}; };
// A fast timer for publish stream, for RTCP feedback. // The RTC publish RTCP timer interface.
class SrsRtcPublishRtcpTimer : public ISrsFastTimerHandler 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 // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
ISrsRtcRtcpSender *sender_; ISrsRtcRtcpSender *sender_;
@ -380,17 +397,33 @@ SRS_DECLARE_PRIVATE: // clang-format on
public: public:
SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender); SrsRtcPublishRtcpTimer(ISrsRtcRtcpSender *sender);
virtual ~SrsRtcPublishRtcpTimer(); virtual ~SrsRtcPublishRtcpTimer();
public:
virtual srs_error_t initialize();
// interface ISrsFastTimerHandler // interface ISrsFastTimerHandler
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
srs_error_t on_timer(srs_utime_t interval); 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. // A fast timer for publish stream, for TWCC feedback.
class SrsRtcPublishTwccTimer : public ISrsFastTimerHandler class SrsRtcPublishTwccTimer : public ISrsRtcPublishTwccTimer
{ {
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
ISrsSharedTimer *shared_timer_;
ISrsCircuitBreaker *circuit_breaker_; ISrsCircuitBreaker *circuit_breaker_;
// clang-format off // clang-format off
@ -401,6 +434,10 @@ SRS_DECLARE_PRIVATE: // clang-format on
public: public:
SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender); SrsRtcPublishTwccTimer(ISrsRtcRtcpSender *sender);
virtual ~SrsRtcPublishTwccTimer(); virtual ~SrsRtcPublishTwccTimer();
public:
virtual srs_error_t initialize();
// interface ISrsFastTimerHandler // interface ISrsFastTimerHandler
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
@ -451,14 +488,14 @@ SRS_DECLARE_PRIVATE: // clang-format on
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
friend class SrsRtcPublishRtcpTimer; friend class SrsRtcPublishRtcpTimer;
friend class SrsRtcPublishTwccTimer; friend class SrsRtcPublishTwccTimer;
SrsRtcPublishRtcpTimer *timer_rtcp_; ISrsRtcPublishRtcpTimer *timer_rtcp_;
SrsRtcPublishTwccTimer *timer_twcc_; ISrsRtcPublishTwccTimer *timer_twcc_;
// clang-format off // clang-format off
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
SrsContextId cid_; SrsContextId cid_;
uint64_t nn_audio_frames_; uint64_t nn_audio_frames_;
SrsRtcPliWorker *pli_worker_; ISrsRtcPliWorker *pli_worker_;
SrsErrorPithyPrint *twcc_epp_; SrsErrorPithyPrint *twcc_epp_;
// clang-format off // clang-format off
@ -492,7 +529,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
SRS_DECLARE_PRIVATE: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on
int twcc_id_; int twcc_id_;
uint8_t twcc_fb_count_; uint8_t twcc_fb_count_;
SrsRtcpTWCC rtcp_twcc_; SrsRtcpTWCC *rtcp_twcc_;
SrsRtpExtensionTypes extension_types_; SrsRtpExtensionTypes extension_types_;
bool is_sender_started_; bool is_sender_started_;
srs_utime_t last_time_send_twcc_; srs_utime_t last_time_send_twcc_;
@ -504,6 +541,7 @@ public:
public: public:
srs_error_t initialize(ISrsRequest *req, SrsRtcSourceDescription *stream_desc); srs_error_t initialize(ISrsRequest *req, SrsRtcSourceDescription *stream_desc);
srs_error_t start(); srs_error_t start();
void stop();
// Directly set the status of track, generally for init to set the default value. // Directly set the status of track, generally for init to set the default value.
void set_all_tracks_status(bool status); void set_all_tracks_status(bool status);
virtual const SrsContextId &context_id(); virtual const SrsContextId &context_id();

View File

@ -3439,88 +3439,6 @@ void MockRtcRtcpSender::reset()
send_periodic_twcc_count_ = 0; 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) VOID TEST(SrsRtcPublishRtcpTimerTest, OnTimer)
{ {
srs_error_t err; srs_error_t err;

View File

@ -438,39 +438,6 @@ public:
void reset(); 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 // Mock expire for testing SrsRtcPublishStream
class MockRtcExpire : public ISrsExpire class MockRtcExpire : public ISrsExpire
{ {

View File

@ -42,6 +42,19 @@ std::map<uint32_t, SrsRtcTrackDescription *> MockRtcTrackDescriptionFactory::cre
return sub_relations; 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 *MockRtcTrackDescriptionFactory::create_audio_track(uint32_t ssrc, std::string id, std::string mid)
{ {
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription(); SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
@ -147,7 +160,8 @@ srs_error_t MockRtcSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<S
return srs_error_copy(fetch_or_create_error_); return srs_error_copy(fetch_or_create_error_);
} }
pps = mock_source_; pps = mock_source_;
return srs_success;
return mock_source_->initialize(r);
} }
SrsSharedPtr<SrsRtcSource> MockRtcSourceManager::fetch(ISrsRequest *r) SrsSharedPtr<SrsRtcSource> MockRtcSourceManager::fetch(ISrsRequest *r)
@ -805,3 +819,85 @@ void MockAppConfig::set_keep_api_domain(bool enabled)
{ {
keep_api_domain_ = 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;
}

View File

@ -38,9 +38,12 @@ public:
uint32_t screen_ssrc_; uint32_t screen_ssrc_;
public: 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<uint32_t, SrsRtcTrackDescription *> create_audio_video_tracks(); std::map<uint32_t, SrsRtcTrackDescription *> 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 // Create a single audio track description
SrsRtcTrackDescription *create_audio_track(uint32_t ssrc, std::string id, std::string mid); 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); 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 #endif

View File

@ -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 <srs_utest_rtc_publishstream.hpp>
#include <srs_kernel_error.hpp>
#include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_utest_mock.hpp>
#include <srs_utest_app6.hpp>
// 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<SrsRtcPublishStream> 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<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, 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;
}

View File

@ -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 <srs_utest.hpp>
#endif