Files
srs/trunk/src/utest/srs_utest_rtc_playstream.cpp
2025-10-17 07:16:42 -04:00

230 lines
9.3 KiB
C++

//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//
#include <srs_utest_rtc_playstream.hpp>
#include <srs_app_rtc_conn.hpp>
#include <srs_app_rtc_source.hpp>
#include <srs_kernel_error.hpp>
#include <srs_utest_app.hpp>
#include <srs_utest_app6.hpp>
#include <srs_utest_mock.hpp>
// This test is used to verify the basic workflow of the RTC play 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(RtcPlayStreamTest, 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;
MockRtcPacketSender mock_sender;
MockRtcTrackDescriptionFactory track_factory;
SrsContextId cid;
cid.set_value("test-play-stream-start-cid");
// Create RTC play stream - uses real app_factory_ and real pli_worker_
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_exec, &mock_expire, &mock_sender, cid));
// Mock the play stream object.
if (true) {
// Inject mock dependencies
play_stream->config_ = &mock_config;
play_stream->rtc_sources_ = &mock_rtc_sources;
play_stream->stat_ = &mock_stat;
// Set mw_msgs to 0 to make the consumer block until we push a packet
mock_config.mw_msgs_ = 0;
}
// Create track descriptions using factory
if (true) {
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations = track_factory.create_audio_video_tracks();
// Initialize the play stream (it will take ownership of track descriptions)
HELPER_EXPECT_SUCCESS(play_stream->initialize(&mock_request, sub_relations));
// Check the tracks, should be two video tracks.
EXPECT_EQ(play_stream->video_tracks_.size(), 2);
// Check the tracks, should be a audio track.
EXPECT_EQ(play_stream->audio_tracks_.size(), 1);
// Test: First call to start() should succeed
HELPER_EXPECT_SUCCESS(play_stream->start());
// Verify is_started_ flag is set
EXPECT_TRUE(play_stream->is_started_);
// Wait for coroutine to start and create consumer. Normally it should be ready
// and stopped at wait for RTP packets from consumer.
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}
// Push a video packet to the source to feed the consumer.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeVideo;
test_pkt->header_.set_sequence(1000);
test_pkt->header_.set_timestamp(5000);
test_pkt->header_.set_ssrc(track_factory.video_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
}
// Verify the video packet is sent out
if (true) {
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 1);
// The packet should create a cached track for this ssrc.
EXPECT_EQ(play_stream->cache_ssrc0_, track_factory.video_ssrc_);
EXPECT_TRUE(play_stream->cache_track0_ != NULL);
// The packet should be in the nack ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track0_->rtp_queue_->at(1000);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.video_ssrc_);
}
// Push a audio packet to the source to feed the consumer.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeAudio;
test_pkt->header_.set_sequence(1000);
test_pkt->header_.set_timestamp(5000);
test_pkt->header_.set_ssrc(track_factory.audio_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
}
// Verify the audio packet is sent out
if (true) {
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 2);
// The packet should create a cached track for this ssrc.
EXPECT_EQ(play_stream->cache_ssrc1_, track_factory.audio_ssrc_);
EXPECT_TRUE(play_stream->cache_track1_ != NULL);
// The packet should be in the nack ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track1_->rtp_queue_->at(1000);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.audio_ssrc_);
}
// Push a screen share packet to the source to feed the consumer.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeVideo;
test_pkt->header_.set_sequence(1000);
test_pkt->header_.set_timestamp(5000);
test_pkt->header_.set_ssrc(track_factory.screen_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
}
// Verify the screen share packet is sent out
if (true) {
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 3);
// The packet should create a cached track for this ssrc.
EXPECT_EQ(play_stream->cache_ssrc2_, track_factory.screen_ssrc_);
EXPECT_TRUE(play_stream->cache_track2_ != NULL);
// The packet should be in the nack ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track2_->rtp_queue_->at(1000);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.screen_ssrc_);
}
// Receive a video packet again, to verify the hit cache track.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeVideo;
test_pkt->header_.set_sequence(1001);
test_pkt->header_.set_timestamp(5001);
test_pkt->header_.set_ssrc(track_factory.video_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 4);
// Check NACK ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track0_->rtp_queue_->at(1001);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.video_ssrc_);
}
// Receive a audio packet again, to verify the hit cache track.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeAudio;
test_pkt->header_.set_sequence(1001);
test_pkt->header_.set_timestamp(5001);
test_pkt->header_.set_ssrc(track_factory.audio_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 5);
// Check NACK ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track1_->rtp_queue_->at(1001);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.audio_ssrc_);
}
// Receive a screen share packet again, to verify the hit cache track.
if (true) {
SrsUniquePtr<SrsRtpPacket> test_pkt(new SrsRtpPacket());
test_pkt->frame_type_ = SrsFrameTypeVideo;
test_pkt->header_.set_sequence(1001);
test_pkt->header_.set_timestamp(5001);
test_pkt->header_.set_ssrc(track_factory.screen_ssrc_);
// Push packet to source - this will feed all consumers including the play stream's consumer
HELPER_EXPECT_SUCCESS(play_stream->source_->on_rtp(test_pkt.get()));
// Wait for coroutine to process the packet
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
// Check sender should have received the packet
EXPECT_EQ(mock_sender.send_packet_count_, 6);
// Check NACK ring buffer.
SrsRtpPacket *pkt = play_stream->cache_track2_->rtp_queue_->at(1001);
EXPECT_TRUE(pkt != NULL);
EXPECT_EQ(pkt->header_.get_ssrc(), track_factory.screen_ssrc_);
}
// Stop the play stream
play_stream->stop();
// Clean up - set to NULL to avoid double-free
play_stream->config_ = NULL;
play_stream->rtc_sources_ = NULL;
play_stream->stat_ = NULL;
}