1
0
mirror of https://github.com/ossrs/srs.git synced 2026-10-24 09:59:20 +03:00

Claude: Reset the play track cache when the publisher republishes. v7.0.164

When the publisher republishes, the source calls on_stream_change on every
player, which re-keys the play stream's audio and video track maps to the new
publisher's SSRCs and keeps the same track objects. It never cleared the fast
cache, so the slots kept the old SSRCs. Packets were still delivered through
the maps, but a new SSRC got a cache slot only while one was free, so after a
republish the cache stopped covering the lookup for the rest of the session.

Clear the three cache slots at the top of on_stream_change in SrsRtcPlayStream
and in SrsRtspPlayStream, which is a copy with the same gap. The first packet
of the new stream then fills the cache again.

Add unit tests for both streams: fill the cache, change the stream to new
SSRCs, and check the cache is empty and the first new packet takes slot 0.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
winlin
2026-09-21 16:42:27 -04:00
parent 11b457bd7c
commit 3fe0342f02
7 changed files with 223 additions and 2 deletions

View File

@@ -15,7 +15,7 @@ func VersionMinor() int {
}
func VersionRevision() int {
return 163
return 164
}
func Version() string {

View File

@@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## SRS 7.0 Changelog
* v7.0, 2026-09-21, RTC: Reset the play track cache when the publisher republishes with new SSRCs. v7.0.164
* v7.0, 2026-09-21, RTC: Resolve the publish and play tracks once per SSRC through the fast cache. v7.0.163
* <strong>v7.0, 2026-09-18, [7.0 alpha0(7.0.162)](https://github.com/ossrs/srs/releases/tag/v7.0-a0) released. 314832 lines.</strong>
* v7.0, 2026-09-18, Merge [#4742](https://github.com/ossrs/srs/pull/4742): RTC: Answer the video codec of the stream for WebRTC play. v7.0.162 (#4742)

View File

@@ -577,6 +577,11 @@ void SrsRtcPlayStream::on_stream_change(SrsRtcSourceDescription *desc)
if (!desc)
return;
// The maps are re-keyed to the new publisher's SSRCs below, so the fast cache, which holds
// the old ones, starts over; otherwise every packet of the new stream misses it from now on.
cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0;
cache_track0_ = cache_track1_ = cache_track2_ = NULL;
// Refresh the relation for audio.
// TODO: FIXME: Match by label?
if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) {

View File

@@ -139,6 +139,11 @@ void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription *desc)
if (!desc)
return;
// The maps are re-keyed to the new publisher's SSRCs below, so the fast cache, which holds
// the old ones, starts over; otherwise every packet of the new stream misses it from now on.
cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0;
cache_track0_ = cache_track1_ = cache_track2_ = NULL;
// Refresh the relation for audio.
// TODO: FIXME: Match by label?
if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) {

View File

@@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 163
#define VERSION_REVISION 164
#endif

View File

@@ -2660,6 +2660,124 @@ VOID TEST(RtcPlayStreamTest, OnStreamChangeSuccess)
srs_freep(source_desc);
}
// A republish brings new SSRCs: on_stream_change re-keys the track maps, so it must also clear the fast cache, or the
// slots keep the old SSRCs and every packet of the new stream misses the cache for the rest of the session.
VOID TEST(RtcPlayStreamTest, OnStreamChangeResetsTrackCache)
{
srs_error_t err;
MockAppConfig mock_config;
MockRtcSourceManager mock_rtc_sources;
MockAppStatistic mock_stat;
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
MockRtcAsyncTaskExecutor mock_async_executor;
MockExpire mock_expire;
MockRtcPacketSender mock_packet_sender;
SrsContextId cid;
cid.set_value("test-stream-change-resets-cache");
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_async_executor, &mock_expire, &mock_packet_sender, cid));
play_stream->config_ = &mock_config;
play_stream->rtc_sources_ = &mock_rtc_sources;
play_stream->stat_ = &mock_stat;
MockRtcPliWorker *mock_pli_worker = new MockRtcPliWorker(play_stream.get());
srs_freep(play_stream->pli_worker_);
play_stream->pli_worker_ = mock_pli_worker;
// The first publisher: audio 12345 and video 67890. The send tracks copy the descriptions.
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations;
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
audio_desc->type_ = "audio";
audio_desc->id_ = "audio-track-id";
audio_desc->ssrc_ = 12345;
audio_desc->is_active_ = true;
audio_desc->media_ = new SrsCodecPayload();
audio_desc->media_->pt_ = 111;
audio_desc->media_->pt_of_publisher_ = 111;
sub_relations[12345] = audio_desc;
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
video_desc->type_ = "video";
video_desc->id_ = "video-track-id";
video_desc->ssrc_ = 67890;
video_desc->is_active_ = true;
video_desc->media_ = new SrsCodecPayload();
video_desc->media_->pt_ = 96;
video_desc->media_->pt_of_publisher_ = 96;
sub_relations[67890] = video_desc;
HELPER_EXPECT_SUCCESS(play_stream->initialize(&mock_request, sub_relations));
// One packet per SSRC fills two slots.
if (true) {
SrsRtpPacket *audio_pkt = new SrsRtpPacket();
audio_pkt->header_.set_ssrc(12345);
audio_pkt->header_.set_sequence(100);
audio_pkt->header_.set_payload_type(111);
audio_pkt->frame_type_ = SrsFrameTypeAudio;
HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt));
srs_freep(audio_pkt);
SrsRtpPacket *video_pkt = new SrsRtpPacket();
video_pkt->header_.set_ssrc(67890);
video_pkt->header_.set_sequence(200);
video_pkt->header_.set_payload_type(96);
video_pkt->frame_type_ = SrsFrameTypeVideo;
HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt));
srs_freep(video_pkt);
}
EXPECT_EQ(12345u, play_stream->cache_ssrc0_);
EXPECT_EQ(67890u, play_stream->cache_ssrc1_);
// The publisher republishes with new SSRCs.
SrsRtcSourceDescription *source_desc = new SrsRtcSourceDescription();
SrsRtcTrackDescription *new_audio_desc = new SrsRtcTrackDescription();
new_audio_desc->type_ = "audio";
new_audio_desc->id_ = "new-audio-track-id";
new_audio_desc->ssrc_ = 54321;
new_audio_desc->is_active_ = true;
new_audio_desc->media_ = new SrsCodecPayload();
new_audio_desc->media_->pt_ = 111;
new_audio_desc->media_->pt_of_publisher_ = 111;
source_desc->audio_track_desc_ = new_audio_desc;
SrsRtcTrackDescription *new_video_desc = new SrsRtcTrackDescription();
new_video_desc->type_ = "video";
new_video_desc->id_ = "new-video-track-id";
new_video_desc->ssrc_ = 98765;
new_video_desc->is_active_ = true;
new_video_desc->media_ = new SrsCodecPayload();
new_video_desc->media_->pt_ = 96;
new_video_desc->media_->pt_of_publisher_ = 96;
source_desc->video_track_descs_.push_back(new_video_desc);
play_stream->on_stream_change(source_desc);
// The cache starts over, so nothing routes by the old SSRCs.
EXPECT_EQ(0u, play_stream->cache_ssrc0_);
EXPECT_EQ(0u, play_stream->cache_ssrc1_);
EXPECT_EQ(0u, play_stream->cache_ssrc2_);
EXPECT_TRUE(play_stream->cache_track0_ == NULL);
EXPECT_TRUE(play_stream->cache_track1_ == NULL);
// The first packet of the new stream takes the first slot again.
if (true) {
SrsRtpPacket *video_pkt = new SrsRtpPacket();
video_pkt->header_.set_ssrc(98765);
video_pkt->header_.set_sequence(201);
video_pkt->header_.set_payload_type(96);
video_pkt->frame_type_ = SrsFrameTypeVideo;
HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt));
srs_freep(video_pkt);
}
EXPECT_EQ(98765u, play_stream->cache_ssrc0_);
EXPECT_TRUE(play_stream->cache_track0_ == (SrsRtcSendTrack *)play_stream->video_tracks_[98765]);
EXPECT_EQ(0u, play_stream->cache_ssrc1_);
srs_freep(audio_desc);
srs_freep(video_desc);
srs_freep(source_desc);
}
// Mock RTC send track implementation
MockRtcSendTrack::MockRtcSendTrack(ISrsRtcPacketSender *sender, SrsRtcTrackDescription *track_desc, bool is_audio)
: SrsRtcSendTrack(sender, track_desc, is_audio)

View File

@@ -1958,6 +1958,98 @@ VOID TEST(RtspPlayStreamTest, OnStreamChange)
EXPECT_EQ(1, (int)play_stream->video_tracks_.size());
}
// A republish brings new SSRCs: on_stream_change re-keys the track maps, so it must also clear the fast cache, or the
// slots keep the old SSRCs and every packet of the new stream misses the cache for the rest of the session.
VOID TEST(RtspPlayStreamTest, OnStreamChangeResetsTrackCache)
{
srs_error_t err = srs_success;
MockRtspConnection mock_session;
MockStatisticForRtspPlayStream mock_stat;
MockRtspSourceManager mock_rtsp_sources;
MockAppFactoryForRtspPlayStream mock_app_factory;
mock_rtsp_sources.mock_source_ = SrsSharedPtr<SrsRtspSource>(new SrsRtspSource());
SrsUniquePtr<MockEdgeRequest> mock_req(new MockEdgeRequest("test.vhost", "live", "stream1"));
SrsContextId cid;
SrsUniquePtr<SrsRtspPlayStream> play_stream(new SrsRtspPlayStream(&mock_session, cid));
play_stream->stat_ = &mock_stat;
play_stream->rtsp_sources_ = &mock_rtsp_sources;
play_stream->app_factory_ = &mock_app_factory;
// The first publisher: audio 1001 and video 2001.
SrsUniquePtr<SrsRtcTrackDescription> audio_desc(new SrsRtcTrackDescription());
audio_desc->type_ = "audio";
audio_desc->id_ = "audio_track";
audio_desc->ssrc_ = 1001;
audio_desc->media_ = new SrsAudioPayload(111, "opus", 48000, 2);
SrsUniquePtr<SrsRtcTrackDescription> video_desc(new SrsRtcTrackDescription());
video_desc->type_ = "video";
video_desc->id_ = "video_track";
video_desc->ssrc_ = 2001;
video_desc->media_ = new SrsVideoPayload(102, "H264", 90000);
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations;
sub_relations[1001] = audio_desc.get();
sub_relations[2001] = video_desc.get();
HELPER_EXPECT_SUCCESS(play_stream->initialize(mock_req.get(), sub_relations));
// One packet per SSRC fills two slots.
if (true) {
SrsUniquePtr<SrsRtpPacket> audio_pkt(new SrsRtpPacket());
audio_pkt->header_.set_ssrc(1001);
audio_pkt->header_.set_sequence(100);
audio_pkt->frame_type_ = SrsFrameTypeAudio;
SrsRtpPacket *audio_pkt_ptr = audio_pkt.get();
HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt_ptr));
SrsUniquePtr<SrsRtpPacket> video_pkt(new SrsRtpPacket());
video_pkt->header_.set_ssrc(2001);
video_pkt->header_.set_sequence(200);
video_pkt->frame_type_ = SrsFrameTypeVideo;
SrsRtpPacket *video_pkt_ptr = video_pkt.get();
HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt_ptr));
}
EXPECT_EQ(1001u, play_stream->cache_ssrc0_);
EXPECT_EQ(2001u, play_stream->cache_ssrc1_);
// The publisher republishes with new SSRCs.
SrsUniquePtr<SrsRtcSourceDescription> new_desc(new SrsRtcSourceDescription());
SrsRtcTrackDescription *new_audio_desc = new SrsRtcTrackDescription();
new_audio_desc->type_ = "audio";
new_audio_desc->ssrc_ = 1002;
new_audio_desc->media_ = new SrsAudioPayload(111, "opus", 48000, 2);
new_desc->audio_track_desc_ = new_audio_desc;
SrsRtcTrackDescription *new_video_desc = new SrsRtcTrackDescription();
new_video_desc->type_ = "video";
new_video_desc->ssrc_ = 2002;
new_video_desc->media_ = new SrsVideoPayload(102, "H264", 90000);
new_desc->video_track_descs_.push_back(new_video_desc);
play_stream->on_stream_change(new_desc.get());
// The cache starts over, so nothing routes by the old SSRCs.
EXPECT_EQ(0u, play_stream->cache_ssrc0_);
EXPECT_EQ(0u, play_stream->cache_ssrc1_);
EXPECT_EQ(0u, play_stream->cache_ssrc2_);
EXPECT_TRUE(play_stream->cache_track0_ == NULL);
EXPECT_TRUE(play_stream->cache_track1_ == NULL);
// The first packet of the new stream takes the first slot again.
MockRtspSendTrack *video_track = dynamic_cast<MockRtspSendTrack *>(play_stream->video_tracks_[2002]);
ASSERT_TRUE(video_track != NULL);
video_track->reset();
SrsUniquePtr<SrsRtpPacket> pkt(new SrsRtpPacket());
pkt->header_.set_ssrc(2002);
pkt->header_.set_sequence(201);
pkt->frame_type_ = SrsFrameTypeVideo;
SrsRtpPacket *pkt_ptr = pkt.get();
HELPER_EXPECT_SUCCESS(play_stream->send_packet(pkt_ptr));
EXPECT_EQ(1, video_track->on_rtp_count_);
EXPECT_EQ(2002u, play_stream->cache_ssrc0_);
EXPECT_EQ(video_track, play_stream->cache_track0_);
EXPECT_EQ(0u, play_stream->cache_ssrc1_);
}
VOID TEST(RtspPlayStreamTest, SendPacketWithCacheAndTrackLookup)
{
srs_error_t err = srs_success;