Skip to content

Commit dc8b2a8

Browse files
ossrs-aiwinlinvip
authored andcommitted
AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
1 parent eb9fca8 commit dc8b2a8

13 files changed

+405
-10
lines changed

.vscode/launch.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@
5757
"engineLogging": true
5858
}
5959
},
60+
{
61+
"name": "Launch SRS with console.conf",
62+
"type": "cppdbg",
63+
"request": "launch",
64+
"program": "${workspaceFolder}/trunk/cmake/build/srs",
65+
"args": ["-c", "console.conf"],
66+
"stopAtEntry": false,
67+
"cwd": "${workspaceFolder}/trunk",
68+
"environment": [],
69+
"externalConsole": false,
70+
"linux": {
71+
"MIMode": "gdb"
72+
},
73+
"osx": {
74+
"MIMode": "lldb"
75+
},
76+
"setupCommands": [
77+
{
78+
"description": "Enable pretty-printing for gdb",
79+
"text": "-enable-pretty-printing",
80+
"ignoreFailures": true
81+
}
82+
],
83+
"preLaunchTask": "build",
84+
"logging": {
85+
"engineLogging": true
86+
}
87+
},
6088
{
6189
"name": "Launch srs-proxy",
6290
"type": "go",

trunk/doc/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The changelog for SRS.
77
<a name="v7-changes"></a>
88

99
## SRS 7.0 Changelog
10+
* v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
1011
* v7.0, 2025-11-04, AI: SRT: Report video/audio codec info and frame stats in HTTP API. v7.0.117 (#4554)
1112
* 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)
1213
* v7.0, 2025-10-31, Merge [#4547](https://github.com/ossrs/srs/pull/4547): Add ignore configuration for cursor. v7.0.115 (#4547)

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,8 @@ SrsRtcPublishStream::SrsRtcPublishStream(ISrsExecRtcAsyncTask *exec, ISrsExpire
12081208
pt_to_drop_ = 0;
12091209

12101210
nn_audio_frames_ = 0;
1211+
nn_rtp_pkts_ = 0;
1212+
format_ = new SrsRtcFormat();
12111213
twcc_enabled_ = false;
12121214
twcc_id_ = 0;
12131215
twcc_fb_count_ = 0;
@@ -1260,6 +1262,7 @@ SrsRtcPublishStream::~SrsRtcPublishStream()
12601262
srs_freep(pli_worker_);
12611263
srs_freep(twcc_epp_);
12621264
srs_freep(pli_epp_);
1265+
srs_freep(format_);
12631266
srs_freep(req_);
12641267

12651268
// update the statistic when client coveried.
@@ -1284,6 +1287,10 @@ srs_error_t SrsRtcPublishStream::initialize(ISrsRequest *r, SrsRtcSourceDescript
12841287

12851288
req_ = r->copy();
12861289

1290+
if ((err = format_->initialize(req_)) != srs_success) {
1291+
return srs_error_wrap(err, "initialize format");
1292+
}
1293+
12871294
if ((err = timer_rtcp_->initialize()) != srs_success) {
12881295
return srs_error_wrap(err, "initialize timer rtcp");
12891296
}
@@ -1670,6 +1677,15 @@ srs_error_t SrsRtcPublishStream::do_on_rtp_plaintext(SrsRtpPacket *&pkt, SrsBuff
16701677
return srs_error_new(ERROR_RTC_RTP, "unknown ssrc=%u", ssrc);
16711678
}
16721679

1680+
// Report codec information to statistics on first RTP packet.
1681+
if ((err = format_->on_rtp_packet(track, is_audio)) != srs_success) {
1682+
srs_warn("RTC: format packet err %s", srs_error_desc(err).c_str());
1683+
srs_freep(err);
1684+
}
1685+
1686+
// Update RTP packet statistics.
1687+
update_rtp_packet_stats(is_audio);
1688+
16731689
// Consume packet by track.
16741690
if ((err = track->on_rtp(source_, pkt)) != srs_success) {
16751691
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
16901706
return err;
16911707
}
16921708

1709+
void SrsRtcPublishStream::update_rtp_packet_stats(bool is_audio)
1710+
{
1711+
srs_error_t err = srs_success;
1712+
1713+
// Count RTP packets for statistics.
1714+
++nn_rtp_pkts_;
1715+
if (is_audio) {
1716+
++nn_audio_frames_;
1717+
}
1718+
1719+
// Update the stat for frames, counting RTP packets as frames.
1720+
if (nn_rtp_pkts_ > 288) {
1721+
if ((err = stat_->on_video_frames(req_, (int)nn_rtp_pkts_)) != srs_success) {
1722+
srs_warn("RTC: stat frames err %s", srs_error_desc(err).c_str());
1723+
srs_freep(err);
1724+
}
1725+
nn_rtp_pkts_ = 0;
1726+
}
1727+
}
1728+
16931729
srs_error_t SrsRtcPublishStream::check_send_nacks()
16941730
{
16951731
srs_error_t err = srs_success;

trunk/src/app/srs_app_rtc_conn.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class ISrsCoroutine;
7474
class ISrsDtlsCertificate;
7575
class SrsRtcRecvTrack;
7676
class ISrsRtcPlayStream;
77+
class ISrsRtcFormat;
7778

7879
const uint8_t kSR = 200;
7980
const uint8_t kRR = 201;
@@ -542,6 +543,8 @@ class SrsRtcPublishStream : public ISrsRtcPublishStream
542543
SRS_DECLARE_PRIVATE: // clang-format on
543544
SrsContextId cid_;
544545
uint64_t nn_audio_frames_;
546+
int nn_rtp_pkts_;
547+
ISrsRtcFormat *format_;
545548
ISrsRtcPliWorker *pli_worker_;
546549
SrsErrorPithyPrint *twcc_epp_;
547550

@@ -619,6 +622,7 @@ class SrsRtcPublishStream : public ISrsRtcPublishStream
619622
// clang-format off
620623
SRS_DECLARE_PRIVATE: // clang-format on
621624
srs_error_t do_on_rtp_plaintext(SrsRtpPacket *&pkt, SrsBuffer *buf);
625+
void update_rtp_packet_stats(bool is_audio);
622626

623627
public:
624628
srs_error_t check_send_nacks();

trunk/src/app/srs_app_rtc_source.cpp

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3279,6 +3279,11 @@ std::string SrsRtcRecvTrack::get_track_id()
32793279
return track_desc_->id_;
32803280
}
32813281

3282+
SrsRtcTrackDescription *SrsRtcRecvTrack::get_track_desc()
3283+
{
3284+
return track_desc_;
3285+
}
3286+
32823287
srs_error_t SrsRtcRecvTrack::on_nack(SrsRtpPacket **ppkt)
32833288
{
32843289
srs_error_t err = srs_success;
@@ -3774,3 +3779,147 @@ uint32_t SrsRtcSSRCGenerator::generate_ssrc()
37743779

37753780
return ++ssrc_num_;
37763781
}
3782+
3783+
ISrsRtcFormat::ISrsRtcFormat()
3784+
{
3785+
}
3786+
3787+
ISrsRtcFormat::~ISrsRtcFormat()
3788+
{
3789+
}
3790+
3791+
SrsRtcFormat::SrsRtcFormat()
3792+
{
3793+
req_ = NULL;
3794+
video_codec_reported_ = false;
3795+
audio_codec_reported_ = false;
3796+
3797+
stat_ = _srs_stat;
3798+
}
3799+
3800+
SrsRtcFormat::~SrsRtcFormat()
3801+
{
3802+
req_ = NULL;
3803+
3804+
stat_ = NULL;
3805+
}
3806+
3807+
srs_error_t SrsRtcFormat::initialize(ISrsRequest *req)
3808+
{
3809+
req_ = req;
3810+
return srs_success;
3811+
}
3812+
3813+
srs_error_t SrsRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
3814+
{
3815+
srs_error_t err = srs_success;
3816+
3817+
SrsRtcTrackDescription *track_desc = track ? track->get_track_desc() : NULL;
3818+
if (!req_ || !track_desc || !track_desc->media_) {
3819+
return err;
3820+
}
3821+
3822+
if (is_audio) {
3823+
// Only report once
3824+
if (audio_codec_reported_) {
3825+
return err;
3826+
}
3827+
audio_codec_reported_ = true;
3828+
3829+
SrsCodecPayload *media = track_desc->media_;
3830+
SrsAudioCodecId codec_id = (SrsAudioCodecId)media->codec(false);
3831+
3832+
// Parse channels and sample rate from track description
3833+
if (codec_id == SrsAudioCodecIdOpus) {
3834+
SrsAudioPayload *audio_media = dynamic_cast<SrsAudioPayload *>(media);
3835+
if (!audio_media) {
3836+
return err;
3837+
}
3838+
3839+
// Get sample rate from media payload
3840+
SrsAudioSampleRate sample_rate = (SrsAudioSampleRate)audio_media->sample_;
3841+
3842+
// Get channels from audio payload
3843+
SrsAudioChannels channels = (SrsAudioChannels)audio_media->channel_;
3844+
3845+
if ((err = stat_->on_audio_info(req_, codec_id, sample_rate, channels,
3846+
SrsAacObjectTypeReserved)) != srs_success) {
3847+
return srs_error_wrap(err, "stat audio info");
3848+
}
3849+
srs_trace("RTC: parsed %s codec, sample_rate=%dHz, channels=%d",
3850+
srs_audio_codec_id2str(codec_id).c_str(), sample_rate, channels);
3851+
}
3852+
} else {
3853+
// Only report once
3854+
if (video_codec_reported_) {
3855+
return err;
3856+
}
3857+
video_codec_reported_ = true;
3858+
3859+
SrsCodecPayload *media = track_desc->media_;
3860+
SrsVideoCodecId codec_id = (SrsVideoCodecId)media->codec(true);
3861+
3862+
// Parse profile and level from track description
3863+
if (codec_id == SrsVideoCodecIdAVC) {
3864+
SrsVideoPayload *video_media = dynamic_cast<SrsVideoPayload *>(media);
3865+
if (!video_media || video_media->h264_param_.profile_level_id_.empty()) {
3866+
return err;
3867+
}
3868+
3869+
// Parse profile_level_id hex string (e.g., "42e01f")
3870+
// Format: PPCCLL where PP=profile_idc, CC=constraint_set, LL=level_idc
3871+
std::string profile_level_id = video_media->h264_param_.profile_level_id_;
3872+
if (profile_level_id.length() != 6) {
3873+
return err;
3874+
}
3875+
3876+
// Decode hex string to bytes
3877+
// srs_hex_decode_string expects size to be the hex string length (6 chars = 3 bytes)
3878+
uint8_t bytes[3];
3879+
int hex_len = (int)profile_level_id.length();
3880+
int r0 = srs_hex_decode_string(bytes, profile_level_id.c_str(), hex_len);
3881+
if (r0 != (int)sizeof(bytes)) {
3882+
srs_trace("RTC: failed to decode profile_level_id hex string: %s, r0=%d", profile_level_id.c_str(), r0);
3883+
video_codec_reported_ = true;
3884+
return err;
3885+
}
3886+
3887+
// Extract profile and level from the decoded bytes
3888+
SrsAvcProfile profile = (SrsAvcProfile)bytes[0];
3889+
SrsAvcLevel level = (SrsAvcLevel)bytes[2];
3890+
3891+
if ((err = stat_->on_video_info(req_, codec_id, profile, level, 0, 0)) != srs_success) {
3892+
return srs_error_wrap(err, "stat video info");
3893+
}
3894+
srs_trace("RTC: parsed %s codec, profile=%s, level=%s",
3895+
srs_video_codec_id2str(codec_id).c_str(),
3896+
srs_avc_profile2str(profile).c_str(),
3897+
srs_avc_level2str(level).c_str());
3898+
} else if (codec_id == SrsVideoCodecIdHEVC) {
3899+
SrsVideoPayload *video_media = dynamic_cast<SrsVideoPayload *>(media);
3900+
if (!video_media || video_media->h265_param_.profile_id_.empty() ||
3901+
video_media->h265_param_.level_id_.empty()) {
3902+
return err;
3903+
}
3904+
3905+
// Parse HEVC profile_id and level_id from SDP parameters
3906+
// profile_id is a decimal string (e.g., "1" for Main profile)
3907+
// level_id is a decimal string (e.g., "93" for Level 3.1)
3908+
int profile_id = atoi(video_media->h265_param_.profile_id_.c_str());
3909+
int level_id = atoi(video_media->h265_param_.level_id_.c_str());
3910+
3911+
SrsHevcProfile profile = (SrsHevcProfile)profile_id;
3912+
SrsHevcLevel level = (SrsHevcLevel)level_id;
3913+
3914+
if ((err = stat_->on_video_info(req_, codec_id, profile, level, 0, 0)) != srs_success) {
3915+
return srs_error_wrap(err, "stat video info");
3916+
}
3917+
srs_trace("RTC: parsed %s codec, profile=%s, level=%d",
3918+
srs_video_codec_id2str(codec_id).c_str(),
3919+
srs_hevc_profile2str(profile).c_str(),
3920+
level_id);
3921+
}
3922+
}
3923+
3924+
return err;
3925+
}

trunk/src/app/srs_app_rtc_source.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class ISrsRtcConsumer;
4545
class ISrsCircuitBreaker;
4646
class ISrsRtcPublishStream;
4747
class ISrsAppFactory;
48+
class ISrsStatistic;
4849

4950
// Firefox defaults as 109, Chrome is 111.
5051
const int kAudioPayloadType = 111;
@@ -899,6 +900,7 @@ class SrsRtcRecvTrack
899900
bool set_track_status(bool active);
900901
bool get_track_status();
901902
std::string get_track_id();
903+
SrsRtcTrackDescription *get_track_desc();
902904

903905
public:
904906
// 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 @@ class SrsRtcSSRCGenerator
11441146
uint32_t generate_ssrc();
11451147
};
11461148

1149+
// The interface for RTC format.
1150+
class ISrsRtcFormat
1151+
{
1152+
public:
1153+
ISrsRtcFormat();
1154+
virtual ~ISrsRtcFormat();
1155+
1156+
public:
1157+
virtual srs_error_t initialize(ISrsRequest *req) = 0;
1158+
virtual srs_error_t on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio) = 0;
1159+
};
1160+
1161+
// Lightweight format parser for RTC streams to extract codec information
1162+
// from RTP packets and update statistics.
1163+
class SrsRtcFormat : public ISrsRtcFormat
1164+
{
1165+
public:
1166+
SrsRtcFormat();
1167+
virtual ~SrsRtcFormat();
1168+
1169+
public:
1170+
virtual srs_error_t initialize(ISrsRequest *req);
1171+
virtual srs_error_t on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio);
1172+
1173+
// clang-format off
1174+
SRS_DECLARE_PRIVATE: // clang-format on
1175+
ISrsRequest *req_;
1176+
ISrsStatistic *stat_;
1177+
// Track whether we've already reported codec info to avoid duplicate updates
1178+
bool video_codec_reported_;
1179+
bool audio_codec_reported_;
1180+
};
1181+
11471182
#endif

trunk/src/app/srs_app_srt_conn.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ srs_error_t SrsMpegtsSrtConn::do_publishing()
554554
SrsUniquePtr<SrsPithyPrint> pprint(SrsPithyPrint::create_srt_publish());
555555

556556
int nb_packets = 0;
557+
int nb_frames = 0;
557558

558559
// Max udp packet size equal to 1500.
559560
char buf[1500];
@@ -585,6 +586,15 @@ srs_error_t SrsMpegtsSrtConn::do_publishing()
585586
}
586587

587588
++nb_packets;
589+
++nb_frames;
590+
591+
// Update the stat for frames every 100 packets, counting SRT packets as frames.
592+
if (nb_frames > 288) {
593+
if ((err = stat_->on_video_frames(req_, nb_frames)) != srs_success) {
594+
return srs_error_wrap(err, "srt: stat frames");
595+
}
596+
nb_frames = 0;
597+
}
588598

589599
if ((err = on_srt_packet(buf, nb)) != srs_success) {
590600
return srs_error_wrap(err, "srt: process packet");

trunk/src/core/srs_core_version7.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99

1010
#define VERSION_MAJOR 7
1111
#define VERSION_MINOR 0
12-
#define VERSION_REVISION 117
12+
#define VERSION_REVISION 118
1313

1414
#endif

0 commit comments

Comments
 (0)