From 0c26a3c30977ca944debe1f1cb724b0a7875e52c Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Thu, 6 Nov 2025 15:30:48 -0500 Subject: [PATCH] AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850) --- trunk/conf/full.conf | 11 +++++++++++ trunk/doc/CHANGELOG.md | 1 + trunk/src/app/srs_app_config.cpp | 21 ++++++++++++++++++++- trunk/src/app/srs_app_config.hpp | 2 ++ trunk/src/app/srs_app_rtc_conn.cpp | 13 +++++++++++-- trunk/src/app/srs_app_rtc_source.cpp | 8 ++++++++ trunk/src/app/srs_app_rtc_source.hpp | 4 ++++ trunk/src/core/srs_core_version7.hpp | 2 +- 8 files changed, 58 insertions(+), 4 deletions(-) diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 3029cecd4..df40356cb 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -960,6 +960,17 @@ vhost rtc.vhost.srs.com { # Overwrite by env SRS_VHOST_RTC_INIT_RATE_FROM_SDP for all vhosts. # Default: off init_rate_from_sdp off; + # Whether keep original SSRC and timestamp when forwarding RTC packets. + # When enabled, SRS will preserve the original SSRC and RTP timestamps from + # publisher to player, which is helpful for end-to-end debugging and troubleshooting + # (e.g., tracking packet loss or delays using Wireshark). + # When disabled (default), SRS generates new SSRC for each player and rebuilds + # timestamps to ensure continuous playback during stream restarts and support + # play-before-publish scenarios. + # @see https://github.com/ossrs/srs/issues/3850 + # Overwrite by env SRS_VHOST_RTC_KEEP_ORIGINAL_SSRC for all vhosts. + # Default: off + keep_original_ssrc off; } ############################################################### # For transmuxing RTMP to RTC, it will impact the default values if RTC is on. diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index b7f589062..e78cef07d 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850) * v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554) * v7.0, 2025-11-04, AI: SRT: Report video/audio codec info and frame stats in HTTP API. v7.0.117 (#4554) * v7.0, 2025-11-03, Merge [#4556](https://github.com/ossrs/srs/pull/4556): Fill missing defs for H264/AVC video levels. v7.0.116 (#4556) diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 45716df8e..b830b02ef 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -2340,7 +2340,7 @@ srs_error_t SrsConfig::check_normal_config() } else if (n == "rtc") { for (int j = 0; j < (int)conf->directives_.size(); j++) { string m = conf->at(j)->name_; - if (m != "enabled" && m != "nack" && m != "twcc" && m != "nack_no_copy" && m != "bframe" && m != "aac" && m != "stun_timeout" && m != "stun_strict_check" && m != "dtls_role" && m != "dtls_version" && m != "drop_for_pt" && m != "rtc_to_rtmp" && m != "pli_for_rtmp" && m != "rtmp_to_rtc" && m != "keep_bframe" && m != "opus_bitrate" && m != "aac_bitrate" && m != "keep_avc_nalu_sei" && m != "init_rate_from_sdp") { + if (m != "enabled" && m != "nack" && m != "twcc" && m != "nack_no_copy" && m != "bframe" && m != "aac" && m != "stun_timeout" && m != "stun_strict_check" && m != "dtls_role" && m != "dtls_version" && m != "drop_for_pt" && m != "rtc_to_rtmp" && m != "pli_for_rtmp" && m != "rtmp_to_rtc" && m != "keep_bframe" && m != "opus_bitrate" && m != "aac_bitrate" && m != "keep_avc_nalu_sei" && m != "init_rate_from_sdp" && m != "keep_original_ssrc") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.rtc.%s of %s", m.c_str(), vhost->arg0().c_str()); } } @@ -4084,6 +4084,25 @@ bool SrsConfig::get_rtc_init_rate_from_sdp(string vhost) return SRS_CONF_PREFER_FALSE(conf->arg0()); } +bool SrsConfig::get_rtc_keep_original_ssrc(string vhost) +{ + SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.rtc.keep_original_ssrc"); // SRS_VHOST_RTC_KEEP_ORIGINAL_SSRC + + static bool DEFAULT = false; + + SrsConfDirective *conf = get_rtc(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("keep_original_ssrc"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PREFER_FALSE(conf->arg0()); +} + SrsConfDirective *SrsConfig::get_vhost(string vhost, bool try_default_vhost) { srs_assert(root_); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index b294692ef..477f6cb97 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -482,6 +482,7 @@ public: virtual int get_rtc_drop_for_pt(std::string vhost) = 0; virtual bool get_rtc_twcc_enabled(std::string vhost) = 0; virtual bool get_rtc_init_rate_from_sdp(std::string vhost) = 0; + virtual bool get_rtc_keep_original_ssrc(std::string vhost) = 0; virtual bool get_srt_enabled() = 0; virtual bool get_srt_enabled(std::string vhost) = 0; virtual std::string get_srt_default_streamid() = 0; @@ -911,6 +912,7 @@ public: int get_rtc_opus_bitrate(std::string vhost); int get_rtc_aac_bitrate(std::string vhost); bool get_rtc_init_rate_from_sdp(std::string vhost); + bool get_rtc_keep_original_ssrc(std::string vhost); // vhost specified section public: diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 26d29eabd..2f1854844 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -553,17 +553,20 @@ srs_error_t SrsRtcPlayStream::initialize(ISrsRequest *req, std::mapget_rtc_nack_enabled(req->vhost_); nack_no_copy_ = config_->get_rtc_nack_no_copy(req->vhost_); - srs_trace("RTC player nack=%d, nnc=%d", nack_enabled_, nack_no_copy_); + bool keep_original_ssrc = config_->get_rtc_keep_original_ssrc(req->vhost_); + srs_trace("RTC player nack=%d, nnc=%d, keep_original_ssrc=%d", nack_enabled_, nack_no_copy_, keep_original_ssrc); // Setup tracks. for (map::iterator it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { SrsRtcAudioSendTrack *track = it->second; track->set_nack_no_copy(nack_no_copy_); + track->set_keep_original_ssrc(keep_original_ssrc); } for (map::iterator it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { SrsRtcVideoSendTrack *track = it->second; track->set_nack_no_copy(nack_no_copy_); + track->set_keep_original_ssrc(keep_original_ssrc); } return err; @@ -3718,6 +3721,7 @@ srs_error_t SrsRtcPlayerNegotiator::negotiate_play_capability(SrsRtcUserConfig * bool nack_enabled = config_->get_rtc_nack_enabled(req->vhost_); bool twcc_enabled = config_->get_rtc_twcc_enabled(req->vhost_); + bool keep_original_ssrc = config_->get_rtc_keep_original_ssrc(req->vhost_); SrsSharedPtr source; if ((err = rtc_sources_->fetch_or_create(req, source)) != srs_success) { @@ -3879,7 +3883,12 @@ srs_error_t SrsRtcPlayerNegotiator::negotiate_play_capability(SrsRtcUserConfig * } } - track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + // When keep_original_ssrc is enabled, preserve the original SSRC from publisher. + // Otherwise, generate a new SSRC for each player. + // @see https://github.com/ossrs/srs/issues/3850 + if (!keep_original_ssrc) { + track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + } // TODO: FIXME: set audio_payload rtcp_fbs_, // according by whether downlink is support transport algorithms. diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index e2e20e213..635fb2262 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -3521,6 +3521,7 @@ SrsRtcSendTrack::SrsRtcSendTrack(ISrsRtcPacketSender *sender, SrsRtcTrackDescrip sender_ = sender; track_desc_ = track_desc->copy(); nack_no_copy_ = false; + keep_original_ssrc_ = false; // Make a different start of sequence number, for debugging. jitter_ts_ = new SrsRtcTsJitter(track_desc_->type_ == "audio" ? 10000 : 20000); @@ -3594,6 +3595,13 @@ std::string SrsRtcSendTrack::get_track_id() void SrsRtcSendTrack::rebuild_packet(SrsRtpPacket *pkt) { + // If keep_original_ssrc is enabled, skip rebuilding sequence number and timestamp. + // This preserves the original SSRC and timestamps for end-to-end debugging. + // @see https://github.com/ossrs/srs/issues/3850 + if (keep_original_ssrc_) { + return; + } + // Rebuild the sequence number. int16_t seq = pkt->header_.get_sequence(); pkt->header_.set_sequence(jitter_seq_->correct(seq)); diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 6804a4390..0bfc2e2f8 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -1073,6 +1073,8 @@ SRS_DECLARE_PROTECTED: // clang-format on SRS_DECLARE_PRIVATE: // clang-format on // By config, whether no copy. bool nack_no_copy_; + // By config, whether keep original SSRC and timestamp. + bool keep_original_ssrc_; // The pithy print for special stage. SrsErrorPithyPrint *nack_epp; @@ -1083,6 +1085,8 @@ public: public: // SrsRtcSendTrack::set_nack_no_copy void set_nack_no_copy(bool v) { nack_no_copy_ = v; } + // SrsRtcSendTrack::set_keep_original_ssrc + void set_keep_original_ssrc(bool v) { keep_original_ssrc_ = v; } bool has_ssrc(uint32_t ssrc); SrsRtpPacket *fetch_rtp_packet(uint16_t seq); bool set_track_status(bool active); diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 2e1bb4054..9ca5f80fb 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 118 +#define VERSION_REVISION 119 #endif \ No newline at end of file