diff --git a/.vscode/launch.json b/.vscode/launch.json
index fd54a400c..c3f1eee3e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -57,6 +57,34 @@
"engineLogging": true
}
},
+ {
+ "name": "Launch SRS with console.conf",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/trunk/cmake/build/srs",
+ "args": ["-c", "console.conf"],
+ "stopAtEntry": false,
+ "cwd": "${workspaceFolder}/trunk",
+ "environment": [],
+ "externalConsole": false,
+ "linux": {
+ "MIMode": "gdb"
+ },
+ "osx": {
+ "MIMode": "lldb"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ],
+ "preLaunchTask": "build",
+ "logging": {
+ "engineLogging": true
+ }
+ },
{
"name": "Launch srs-proxy",
"type": "go",
diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md
index 0e4b70e72..b7f589062 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-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)
* v7.0, 2025-10-31, Merge [#4547](https://github.com/ossrs/srs/pull/4547): Add ignore configuration for cursor. v7.0.115 (#4547)
diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp
index f549cc80c..26d29eabd 100644
--- a/trunk/src/app/srs_app_rtc_conn.cpp
+++ b/trunk/src/app/srs_app_rtc_conn.cpp
@@ -1208,6 +1208,8 @@ SrsRtcPublishStream::SrsRtcPublishStream(ISrsExecRtcAsyncTask *exec, ISrsExpire
pt_to_drop_ = 0;
nn_audio_frames_ = 0;
+ nn_rtp_pkts_ = 0;
+ format_ = new SrsRtcFormat();
twcc_enabled_ = false;
twcc_id_ = 0;
twcc_fb_count_ = 0;
@@ -1260,6 +1262,7 @@ SrsRtcPublishStream::~SrsRtcPublishStream()
srs_freep(pli_worker_);
srs_freep(twcc_epp_);
srs_freep(pli_epp_);
+ srs_freep(format_);
srs_freep(req_);
// update the statistic when client coveried.
@@ -1284,6 +1287,10 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript
req_ = r->copy();
+ if ((err = format_->initialize(req_)) != srs_success) {
+ return srs_error_wrap(err, "initialize format");
+ }
+
if ((err = timer_rtcp_->initialize()) != srs_success) {
return srs_error_wrap(err, "initialize timer rtcp");
}
@@ -1670,6 +1677,15 @@ srs_error_t SrsRtcPublishStream::do_on_rtp_plaintext(SrsRtpPacket *&pkt, SrsBuff
return srs_error_new(ERROR_RTC_RTP, "unknown ssrc=%u", ssrc);
}
+ // Report codec information to statistics on first RTP packet.
+ if ((err = format_->on_rtp_packet(track, is_audio)) != srs_success) {
+ srs_warn("RTC: format packet err %s", srs_error_desc(err).c_str());
+ srs_freep(err);
+ }
+
+ // Update RTP packet statistics.
+ update_rtp_packet_stats(is_audio);
+
// Consume packet by track.
if ((err = track->on_rtp(source_, pkt)) != srs_success) {
return srs_error_wrap(err, "audio track, SSRC=%u, SEQ=%u", ssrc, pkt->header_.get_sequence());
@@ -1690,6 +1706,26 @@ srs_error_t SrsRtcPublishStream::do_on_rtp_plaintext(SrsRtpPacket *&pkt, SrsBuff
return err;
}
+void SrsRtcPublishStream::update_rtp_packet_stats(bool is_audio)
+{
+ srs_error_t err = srs_success;
+
+ // Count RTP packets for statistics.
+ ++nn_rtp_pkts_;
+ if (is_audio) {
+ ++nn_audio_frames_;
+ }
+
+ // Update the stat for frames, counting RTP packets as frames.
+ if (nn_rtp_pkts_ > 288) {
+ if ((err = stat_->on_video_frames(req_, (int)nn_rtp_pkts_)) != srs_success) {
+ srs_warn("RTC: stat frames err %s", srs_error_desc(err).c_str());
+ srs_freep(err);
+ }
+ nn_rtp_pkts_ = 0;
+ }
+}
+
srs_error_t SrsRtcPublishStream::check_send_nacks()
{
srs_error_t err = srs_success;
diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp
index ccaa41c9d..101abd8e3 100644
--- a/trunk/src/app/srs_app_rtc_conn.hpp
+++ b/trunk/src/app/srs_app_rtc_conn.hpp
@@ -74,6 +74,7 @@ class ISrsCoroutine;
class ISrsDtlsCertificate;
class SrsRtcRecvTrack;
class ISrsRtcPlayStream;
+class ISrsRtcFormat;
const uint8_t kSR = 200;
const uint8_t kRR = 201;
@@ -542,6 +543,8 @@ SRS_DECLARE_PRIVATE: // clang-format on
SRS_DECLARE_PRIVATE: // clang-format on
SrsContextId cid_;
uint64_t nn_audio_frames_;
+ int nn_rtp_pkts_;
+ ISrsRtcFormat *format_;
ISrsRtcPliWorker *pli_worker_;
SrsErrorPithyPrint *twcc_epp_;
@@ -619,6 +622,7 @@ public:
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
srs_error_t do_on_rtp_plaintext(SrsRtpPacket *&pkt, SrsBuffer *buf);
+ void update_rtp_packet_stats(bool is_audio);
public:
srs_error_t check_send_nacks();
diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp
index 71fe59dec..e2e20e213 100644
--- a/trunk/src/app/srs_app_rtc_source.cpp
+++ b/trunk/src/app/srs_app_rtc_source.cpp
@@ -3279,6 +3279,11 @@ std::string SrsRtcRecvTrack::get_track_id()
return track_desc_->id_;
}
+SrsRtcTrackDescription *SrsRtcRecvTrack::get_track_desc()
+{
+ return track_desc_;
+}
+
srs_error_t SrsRtcRecvTrack::on_nack(SrsRtpPacket **ppkt)
{
srs_error_t err = srs_success;
@@ -3774,3 +3779,147 @@ uint32_t SrsRtcSSRCGenerator::generate_ssrc()
return ++ssrc_num_;
}
+
+ISrsRtcFormat::ISrsRtcFormat()
+{
+}
+
+ISrsRtcFormat::~ISrsRtcFormat()
+{
+}
+
+SrsRtcFormat::SrsRtcFormat()
+{
+ req_ = NULL;
+ video_codec_reported_ = false;
+ audio_codec_reported_ = false;
+
+ stat_ = _srs_stat;
+}
+
+SrsRtcFormat::~SrsRtcFormat()
+{
+ req_ = NULL;
+
+ stat_ = NULL;
+}
+
+srs_error_t SrsRtcFormat::initialize(ISrsRequest *req)
+{
+ req_ = req;
+ return srs_success;
+}
+
+srs_error_t SrsRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
+{
+ srs_error_t err = srs_success;
+
+ SrsRtcTrackDescription *track_desc = track ? track->get_track_desc() : NULL;
+ if (!req_ || !track_desc || !track_desc->media_) {
+ return err;
+ }
+
+ if (is_audio) {
+ // Only report once
+ if (audio_codec_reported_) {
+ return err;
+ }
+ audio_codec_reported_ = true;
+
+ SrsCodecPayload *media = track_desc->media_;
+ SrsAudioCodecId codec_id = (SrsAudioCodecId)media->codec(false);
+
+ // Parse channels and sample rate from track description
+ if (codec_id == SrsAudioCodecIdOpus) {
+ SrsAudioPayload *audio_media = dynamic_cast(media);
+ if (!audio_media) {
+ return err;
+ }
+
+ // Get sample rate from media payload
+ SrsAudioSampleRate sample_rate = (SrsAudioSampleRate)audio_media->sample_;
+
+ // Get channels from audio payload
+ SrsAudioChannels channels = (SrsAudioChannels)audio_media->channel_;
+
+ if ((err = stat_->on_audio_info(req_, codec_id, sample_rate, channels,
+ SrsAacObjectTypeReserved)) != srs_success) {
+ return srs_error_wrap(err, "stat audio info");
+ }
+ srs_trace("RTC: parsed %s codec, sample_rate=%dHz, channels=%d",
+ srs_audio_codec_id2str(codec_id).c_str(), sample_rate, channels);
+ }
+ } else {
+ // Only report once
+ if (video_codec_reported_) {
+ return err;
+ }
+ video_codec_reported_ = true;
+
+ SrsCodecPayload *media = track_desc->media_;
+ SrsVideoCodecId codec_id = (SrsVideoCodecId)media->codec(true);
+
+ // Parse profile and level from track description
+ if (codec_id == SrsVideoCodecIdAVC) {
+ SrsVideoPayload *video_media = dynamic_cast(media);
+ if (!video_media || video_media->h264_param_.profile_level_id_.empty()) {
+ return err;
+ }
+
+ // Parse profile_level_id hex string (e.g., "42e01f")
+ // Format: PPCCLL where PP=profile_idc, CC=constraint_set, LL=level_idc
+ std::string profile_level_id = video_media->h264_param_.profile_level_id_;
+ if (profile_level_id.length() != 6) {
+ return err;
+ }
+
+ // Decode hex string to bytes
+ // srs_hex_decode_string expects size to be the hex string length (6 chars = 3 bytes)
+ uint8_t bytes[3];
+ int hex_len = (int)profile_level_id.length();
+ int r0 = srs_hex_decode_string(bytes, profile_level_id.c_str(), hex_len);
+ if (r0 != (int)sizeof(bytes)) {
+ srs_trace("RTC: failed to decode profile_level_id hex string: %s, r0=%d", profile_level_id.c_str(), r0);
+ video_codec_reported_ = true;
+ return err;
+ }
+
+ // Extract profile and level from the decoded bytes
+ SrsAvcProfile profile = (SrsAvcProfile)bytes[0];
+ SrsAvcLevel level = (SrsAvcLevel)bytes[2];
+
+ if ((err = stat_->on_video_info(req_, codec_id, profile, level, 0, 0)) != srs_success) {
+ return srs_error_wrap(err, "stat video info");
+ }
+ srs_trace("RTC: parsed %s codec, profile=%s, level=%s",
+ srs_video_codec_id2str(codec_id).c_str(),
+ srs_avc_profile2str(profile).c_str(),
+ srs_avc_level2str(level).c_str());
+ } else if (codec_id == SrsVideoCodecIdHEVC) {
+ SrsVideoPayload *video_media = dynamic_cast(media);
+ if (!video_media || video_media->h265_param_.profile_id_.empty() ||
+ video_media->h265_param_.level_id_.empty()) {
+ return err;
+ }
+
+ // Parse HEVC profile_id and level_id from SDP parameters
+ // profile_id is a decimal string (e.g., "1" for Main profile)
+ // level_id is a decimal string (e.g., "93" for Level 3.1)
+ int profile_id = atoi(video_media->h265_param_.profile_id_.c_str());
+ int level_id = atoi(video_media->h265_param_.level_id_.c_str());
+
+ SrsHevcProfile profile = (SrsHevcProfile)profile_id;
+ SrsHevcLevel level = (SrsHevcLevel)level_id;
+
+ if ((err = stat_->on_video_info(req_, codec_id, profile, level, 0, 0)) != srs_success) {
+ return srs_error_wrap(err, "stat video info");
+ }
+ srs_trace("RTC: parsed %s codec, profile=%s, level=%d",
+ srs_video_codec_id2str(codec_id).c_str(),
+ srs_hevc_profile2str(profile).c_str(),
+ level_id);
+ }
+ }
+
+ return err;
+}
diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp
index dba5e8b03..6804a4390 100644
--- a/trunk/src/app/srs_app_rtc_source.hpp
+++ b/trunk/src/app/srs_app_rtc_source.hpp
@@ -45,6 +45,7 @@ class ISrsRtcConsumer;
class ISrsCircuitBreaker;
class ISrsRtcPublishStream;
class ISrsAppFactory;
+class ISrsStatistic;
// Firefox defaults as 109, Chrome is 111.
const int kAudioPayloadType = 111;
@@ -899,6 +900,7 @@ public:
bool set_track_status(bool active);
bool get_track_status();
std::string get_track_id();
+ SrsRtcTrackDescription *get_track_desc();
public:
// Note that we can set the pkt to NULL to avoid copy, for example, if the NACK cache the pkt and
@@ -1144,4 +1146,37 @@ public:
uint32_t generate_ssrc();
};
+// The interface for RTC format.
+class ISrsRtcFormat
+{
+public:
+ ISrsRtcFormat();
+ virtual ~ISrsRtcFormat();
+
+public:
+ virtual srs_error_t initialize(ISrsRequest *req) = 0;
+ virtual srs_error_t on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio) = 0;
+};
+
+// Lightweight format parser for RTC streams to extract codec information
+// from RTP packets and update statistics.
+class SrsRtcFormat : public ISrsRtcFormat
+{
+public:
+ SrsRtcFormat();
+ virtual ~SrsRtcFormat();
+
+public:
+ virtual srs_error_t initialize(ISrsRequest *req);
+ virtual srs_error_t on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio);
+
+// clang-format off
+SRS_DECLARE_PRIVATE: // clang-format on
+ ISrsRequest *req_;
+ ISrsStatistic *stat_;
+ // Track whether we've already reported codec info to avoid duplicate updates
+ bool video_codec_reported_;
+ bool audio_codec_reported_;
+};
+
#endif
diff --git a/trunk/src/app/srs_app_srt_conn.cpp b/trunk/src/app/srs_app_srt_conn.cpp
index a2a958291..3888bd116 100644
--- a/trunk/src/app/srs_app_srt_conn.cpp
+++ b/trunk/src/app/srs_app_srt_conn.cpp
@@ -554,6 +554,7 @@ srs_error_t SrsMpegtsSrtConn::do_publishing()
SrsUniquePtr pprint(SrsPithyPrint::create_srt_publish());
int nb_packets = 0;
+ int nb_frames = 0;
// Max udp packet size equal to 1500.
char buf[1500];
@@ -585,6 +586,15 @@ srs_error_t SrsMpegtsSrtConn::do_publishing()
}
++nb_packets;
+ ++nb_frames;
+
+ // Update the stat for frames every 100 packets, counting SRT packets as frames.
+ if (nb_frames > 288) {
+ if ((err = stat_->on_video_frames(req_, nb_frames)) != srs_success) {
+ return srs_error_wrap(err, "srt: stat frames");
+ }
+ nb_frames = 0;
+ }
if ((err = on_srt_packet(buf, nb)) != srs_success) {
return srs_error_wrap(err, "srt: process packet");
diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp
index dabdd8f26..2e1bb4054 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 117
+#define VERSION_REVISION 118
#endif
\ No newline at end of file
diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp
index e0f5c801b..4bf83cefd 100644
--- a/trunk/src/kernel/srs_kernel_utility.cpp
+++ b/trunk/src/kernel/srs_kernel_utility.cpp
@@ -653,16 +653,19 @@ srs_error_t SrsPath::mkdir_all(string dir)
}
// fromHexChar converts a hex character into its value and a success flag.
-uint8_t srs_from_hex_char(uint8_t c)
+int srs_from_hex_char(uint8_t c, uint8_t *out)
{
if ('0' <= c && c <= '9') {
- return c - '0';
+ *out = c - '0';
+ return 0;
}
if ('a' <= c && c <= 'f') {
- return c - 'a' + 10;
+ *out = c - 'a' + 10;
+ return 0;
}
if ('A' <= c && c <= 'F') {
- return c - 'A' + 10;
+ *out = c - 'A' + 10;
+ return 0;
}
return -1;
@@ -702,18 +705,18 @@ char *srs_hex_encode_to_string_lowercase(char *des, const u_int8_t *src, int len
int srs_hex_decode_string(uint8_t *data, const char *p, int size)
{
- if (size <= 0 || (size % 2) == 1) {
+ if (!p || size <= 0 || (size % 2) == 1) {
return -1;
}
for (int i = 0; i < (int)size / 2; i++) {
- uint8_t a = srs_from_hex_char(p[i * 2]);
- if (a == (uint8_t)-1) {
+ uint8_t a = 0;
+ if (srs_from_hex_char(p[i * 2], &a) == -1) {
return -1;
}
- uint8_t b = srs_from_hex_char(p[i * 2 + 1]);
- if (b == (uint8_t)-1) {
+ uint8_t b = 0;
+ if (srs_from_hex_char(p[i * 2 + 1], &b) == -1) {
return -1;
}
diff --git a/trunk/src/utest/srs_utest_ai24.cpp b/trunk/src/utest/srs_utest_ai24.cpp
index 8825384d8..12b6db543 100644
--- a/trunk/src/utest/srs_utest_ai24.cpp
+++ b/trunk/src/utest/srs_utest_ai24.cpp
@@ -1093,6 +1093,67 @@ VOID TEST(KernelUtilityTest, StringsDumpsHexWithString)
EXPECT_TRUE(empty_result.empty());
}
+// Test: srs_hex_decode_string decodes hex string to bytes
+VOID TEST(KernelUtilityTest, HexDecodeString)
+{
+ // Test normal case: decode valid hex string
+ if (true) {
+ std::string hex_str = "42e01f";
+ uint8_t data[3];
+
+ int result = srs_hex_decode_string(data, hex_str.c_str(), (int)hex_str.length());
+
+ EXPECT_EQ(3, result);
+ EXPECT_EQ(0x42, data[0]);
+ EXPECT_EQ(0xe0, data[1]);
+ EXPECT_EQ(0x1f, data[2]);
+ }
+
+ // Test uppercase hex string
+ if (true) {
+ std::string hex_str = "ABCDEF";
+ uint8_t data[3];
+
+ int result = srs_hex_decode_string(data, hex_str.c_str(), (int)hex_str.length());
+
+ EXPECT_EQ(3, result);
+ EXPECT_EQ(0xAB, data[0]);
+ EXPECT_EQ(0xCD, data[1]);
+ EXPECT_EQ(0xEF, data[2]);
+ }
+
+ // Test mixed case hex string
+ if (true) {
+ std::string hex_str = "aB12Cd";
+ uint8_t data[3];
+
+ int result = srs_hex_decode_string(data, hex_str.c_str(), (int)hex_str.length());
+
+ EXPECT_EQ(3, result);
+ EXPECT_EQ(0xAB, data[0]);
+ EXPECT_EQ(0x12, data[1]);
+ EXPECT_EQ(0xCD, data[2]);
+ }
+
+ // Test error case: NULL pointer
+ if (true) {
+ uint8_t data[3];
+ EXPECT_EQ(-1, srs_hex_decode_string(data, NULL, 6));
+ }
+
+ // Test error case: odd length (not pairs of hex digits)
+ if (true) {
+ uint8_t data[3];
+ EXPECT_EQ(-1, srs_hex_decode_string(data, "abc", 3));
+ }
+
+ // Test error case: invalid hex character
+ if (true) {
+ uint8_t data[3];
+ EXPECT_EQ(-1, srs_hex_decode_string(data, "abcg", 4));
+ }
+}
+
// Test: srs_is_boolean checks if string is "true" or "false"
VOID TEST(AppUtilityTest, IsBoolean)
{
diff --git a/trunk/src/utest/srs_utest_manual_mock.cpp b/trunk/src/utest/srs_utest_manual_mock.cpp
index 9bb9ff6ed..2d3a077d4 100644
--- a/trunk/src/utest/srs_utest_manual_mock.cpp
+++ b/trunk/src/utest/srs_utest_manual_mock.cpp
@@ -547,6 +547,45 @@ void MockRtcPacketSender::set_send_packet_error(srs_error_t err)
send_packet_error_ = err;
}
+// MockRtcFormat implementation
+MockRtcFormat::MockRtcFormat()
+{
+ initialize_error_ = srs_success;
+ on_rtp_packet_error_ = srs_success;
+ initialize_count_ = 0;
+ on_rtp_packet_count_ = 0;
+}
+
+MockRtcFormat::~MockRtcFormat()
+{
+ srs_freep(initialize_error_);
+ srs_freep(on_rtp_packet_error_);
+}
+
+srs_error_t MockRtcFormat::initialize(ISrsRequest *req)
+{
+ initialize_count_++;
+ return srs_error_copy(initialize_error_);
+}
+
+srs_error_t MockRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
+{
+ on_rtp_packet_count_++;
+ return srs_error_copy(on_rtp_packet_error_);
+}
+
+void MockRtcFormat::set_initialize_error(srs_error_t err)
+{
+ srs_freep(initialize_error_);
+ initialize_error_ = srs_error_copy(err);
+}
+
+void MockRtcFormat::set_on_rtp_packet_error(srs_error_t err)
+{
+ srs_freep(on_rtp_packet_error_);
+ on_rtp_packet_error_ = srs_error_copy(err);
+}
+
// Mock RTC packet receiver implementation
MockRtcPacketReceiver::MockRtcPacketReceiver()
{
diff --git a/trunk/src/utest/srs_utest_manual_mock.hpp b/trunk/src/utest/srs_utest_manual_mock.hpp
index 95b9ad473..dbdd005bb 100644
--- a/trunk/src/utest/srs_utest_manual_mock.hpp
+++ b/trunk/src/utest/srs_utest_manual_mock.hpp
@@ -263,6 +263,26 @@ public:
void set_send_packet_error(srs_error_t err);
};
+// Mock RTC format for testing
+class MockRtcFormat : public ISrsRtcFormat
+{
+public:
+ srs_error_t initialize_error_;
+ srs_error_t on_rtp_packet_error_;
+ int initialize_count_;
+ int on_rtp_packet_count_;
+
+public:
+ MockRtcFormat();
+ virtual ~MockRtcFormat();
+
+public:
+ virtual srs_error_t initialize(ISrsRequest *req);
+ virtual srs_error_t on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio);
+ void set_initialize_error(srs_error_t err);
+ void set_on_rtp_packet_error(srs_error_t err);
+};
+
// Mock app config for testing
class MockAppConfig : public ISrsAppConfig
{
diff --git a/trunk/src/utest/srs_utest_workflow_rtc_publishstream.cpp b/trunk/src/utest/srs_utest_workflow_rtc_publishstream.cpp
index 1d8060a97..e874a80d9 100644
--- a/trunk/src/utest/srs_utest_workflow_rtc_publishstream.cpp
+++ b/trunk/src/utest/srs_utest_workflow_rtc_publishstream.cpp
@@ -45,6 +45,7 @@ VOID TEST(BasicWorkflowRtcPublishStreamTest, ManuallyVerify)
MockExpire mock_expire;
MockRtcPacketReceiver mock_receiver;
MockRtcTrackDescriptionFactory track_factory;
+ MockRtcFormat mock_format;
SrsContextId cid;
cid.set_value("test-publish-stream-cid");
@@ -57,6 +58,10 @@ VOID TEST(BasicWorkflowRtcPublishStreamTest, ManuallyVerify)
publish_stream->config_ = &mock_config;
publish_stream->rtc_sources_ = &mock_rtc_sources;
publish_stream->stat_ = &mock_stat;
+
+ // Replace the real format_ with mock format
+ srs_freep(publish_stream->format_);
+ publish_stream->format_ = &mock_format;
}
// Create stream description with audio and video tracks
@@ -96,4 +101,8 @@ VOID TEST(BasicWorkflowRtcPublishStreamTest, ManuallyVerify)
// Stop the publish stream
publish_stream->stop();
+
+ // Before destroying publish_stream, set format_ to NULL to prevent double-free
+ // since mock_format is a stack variable
+ publish_stream->format_ = NULL;
}