Skip to content

Commit d9113f7

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 d9113f7

11 files changed

+396
-9
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/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: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3774,3 +3774,146 @@ uint32_t SrsRtcSSRCGenerator::generate_ssrc()
37743774

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

trunk/src/app/srs_app_rtc_source.hpp

Lines changed: 34 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;
@@ -1144,4 +1145,37 @@ class SrsRtcSSRCGenerator
11441145
uint32_t generate_ssrc();
11451146
};
11461147

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -653,16 +653,19 @@ srs_error_t SrsPath::mkdir_all(string dir)
653653
}
654654

655655
// fromHexChar converts a hex character into its value and a success flag.
656-
uint8_t srs_from_hex_char(uint8_t c)
656+
int srs_from_hex_char(uint8_t c, uint8_t *out)
657657
{
658658
if ('0' <= c && c <= '9') {
659-
return c - '0';
659+
*out = c - '0';
660+
return 0;
660661
}
661662
if ('a' <= c && c <= 'f') {
662-
return c - 'a' + 10;
663+
*out = c - 'a' + 10;
664+
return 0;
663665
}
664666
if ('A' <= c && c <= 'F') {
665-
return c - 'A' + 10;
667+
*out = c - 'A' + 10;
668+
return 0;
666669
}
667670

668671
return -1;
@@ -702,18 +705,18 @@ char *srs_hex_encode_to_string_lowercase(char *des, const u_int8_t *src, int len
702705

703706
int srs_hex_decode_string(uint8_t *data, const char *p, int size)
704707
{
705-
if (size <= 0 || (size % 2) == 1) {
708+
if (!p || size <= 0 || (size % 2) == 1) {
706709
return -1;
707710
}
708711

709712
for (int i = 0; i < (int)size / 2; i++) {
710-
uint8_t a = srs_from_hex_char(p[i * 2]);
711-
if (a == (uint8_t)-1) {
713+
uint8_t a = 0;
714+
if (srs_from_hex_char(p[i * 2], &a) == -1) {
712715
return -1;
713716
}
714717

715-
uint8_t b = srs_from_hex_char(p[i * 2 + 1]);
716-
if (b == (uint8_t)-1) {
718+
uint8_t b = 0;
719+
if (srs_from_hex_char(p[i * 2 + 1], &b) == -1) {
717720
return -1;
718721
}
719722

0 commit comments

Comments
 (0)