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:
@@ -15,7 +15,7 @@ func VersionMinor() int {
|
||||
}
|
||||
|
||||
func VersionRevision() int {
|
||||
return 163
|
||||
return 164
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 163
|
||||
#define VERSION_REVISION 164
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user