Skip to content

Commit 1a96abc

Browse files
authored
AI: API: Add audio_frames and video_frames to HTTP API. v7.0.122 (#4559) (#4564)
This PR adds separate audio and video frame counting to the HTTP API (`/api/v1/streams/`) for better stream observability. The API now reports three frame fields: - `frames` - Total frames (video + audio) - `video_frames` - Video frames/packets only - `audio_frames` - Audio frames/packets only This enhancement provides better visibility into stream composition and helps detect issues with CBR/VBR streams, audio/video sync problems, and codec-specific behavior. **Before:** ```json { "streams": [ { "frames": 0, // video frames. } ] } ``` **After:** ```json { "streams": [ { "frames": 6912, // video frames. "audio_frames": 5678, // audio frames. "video_frames": 1234, // video frames. } ] } ``` Frame Counting Strategy - All protocols report frames every N frames to balance accuracy and performance - Frames are counted at the protocol-specific message/packet level: - RTMP: Counts RTMP messages (video/audio) - WebRTC: Counts RTP packets (video/audio) - SRT: Counts MPEG-TS messages (H.264/HEVC/AAC)
1 parent f392f9a commit 1a96abc

28 files changed

+214
-28
lines changed

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-08, AI: API: Add audio_frames and video_frames to HTTP API. v7.0.122 (#4559)
1011
* v7.0, 2025-11-07, AI: WHIP: Return detailed HTTP error responses with proper status codes. v7.0.121 (#4502)
1112
* v7.0, 2025-11-07, AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
1213
* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850)

trunk/src/app/srs_app_recv_thread.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ SrsPublishRecvThread::SrsPublishRecvThread(ISrsRtmpServer *rtmp_sdk, ISrsRequest
299299
recv_error_ = srs_success;
300300
nb_msgs_ = 0;
301301
video_frames_ = 0;
302+
audio_frames_ = 0;
302303
error_ = srs_cond_new();
303304

304305
req_ = _req;
@@ -349,6 +350,11 @@ uint64_t SrsPublishRecvThread::nb_video_frames()
349350
return video_frames_;
350351
}
351352

353+
uint64_t SrsPublishRecvThread::nb_audio_frames()
354+
{
355+
return audio_frames_;
356+
}
357+
352358
srs_error_t SrsPublishRecvThread::error_code()
353359
{
354360
return srs_error_copy(recv_error_);
@@ -396,6 +402,8 @@ srs_error_t SrsPublishRecvThread::consume(SrsRtmpCommonMessage *msg)
396402

397403
if (msg->header_.is_video()) {
398404
video_frames_++;
405+
} else if (msg->header_.is_audio()) {
406+
audio_frames_++;
399407
}
400408

401409
// log to show the time of recv thread.

trunk/src/app/srs_app_recv_thread.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ class SrsPublishRecvThread : public ISrsPublishRecvThread
190190
int64_t nb_msgs_;
191191
// The video frames we got.
192192
uint64_t video_frames_;
193+
// The audio frames we got.
194+
uint64_t audio_frames_;
193195
// For mr(merged read),
194196
bool mr_;
195197
int mr_fd_;
@@ -219,6 +221,7 @@ class SrsPublishRecvThread : public ISrsPublishRecvThread
219221
virtual srs_error_t wait(srs_utime_t tm);
220222
virtual int64_t nb_msgs();
221223
virtual uint64_t nb_video_frames();
224+
virtual uint64_t nb_audio_frames();
222225
virtual srs_error_t error_code();
223226
virtual void set_cid(SrsContextId v);
224227
virtual SrsContextId get_cid();

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ SrsRtcPublishStream::SrsRtcPublishStream(ISrsExecRtcAsyncTask *exec, ISrsExpire
12111211
pt_to_drop_ = 0;
12121212

12131213
nn_audio_frames_ = 0;
1214-
nn_rtp_pkts_ = 0;
1214+
nn_video_frames_ = 0;
12151215
format_ = new SrsRtcFormat();
12161216
twcc_enabled_ = false;
12171217
twcc_id_ = 0;
@@ -1714,18 +1714,28 @@ void SrsRtcPublishStream::update_rtp_packet_stats(bool is_audio)
17141714
srs_error_t err = srs_success;
17151715

17161716
// Count RTP packets for statistics.
1717-
++nn_rtp_pkts_;
17181717
if (is_audio) {
17191718
++nn_audio_frames_;
1719+
} else {
1720+
++nn_video_frames_;
1721+
}
1722+
1723+
// Update the stat for video frames, counting RTP packets as frames.
1724+
if (nn_video_frames_ > 288) {
1725+
if ((err = stat_->on_video_frames(req_, nn_video_frames_)) != srs_success) {
1726+
srs_warn("RTC: stat video frames err %s", srs_error_desc(err).c_str());
1727+
srs_freep(err);
1728+
}
1729+
nn_video_frames_ = 0;
17201730
}
17211731

1722-
// Update the stat for frames, counting RTP packets as frames.
1723-
if (nn_rtp_pkts_ > 288) {
1724-
if ((err = stat_->on_video_frames(req_, (int)nn_rtp_pkts_)) != srs_success) {
1725-
srs_warn("RTC: stat frames err %s", srs_error_desc(err).c_str());
1732+
// Update the stat for audio frames periodically.
1733+
if (nn_audio_frames_ > 288) {
1734+
if ((err = stat_->on_audio_frames(req_, nn_audio_frames_)) != srs_success) {
1735+
srs_warn("RTC: stat audio frames err %s", srs_error_desc(err).c_str());
17261736
srs_freep(err);
17271737
}
1728-
nn_rtp_pkts_ = 0;
1738+
nn_audio_frames_ = 0;
17291739
}
17301740
}
17311741

trunk/src/app/srs_app_rtc_conn.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,8 @@ class SrsRtcPublishStream : public ISrsRtcPublishStream
542542
// clang-format off
543543
SRS_DECLARE_PRIVATE: // clang-format on
544544
SrsContextId cid_;
545-
uint64_t nn_audio_frames_;
546-
int nn_rtp_pkts_;
545+
int nn_audio_frames_;
546+
int nn_video_frames_;
547547
ISrsRtcFormat *format_;
548548
ISrsRtcPliWorker *pli_worker_;
549549
SrsErrorPithyPrint *twcc_epp_;

trunk/src/app/srs_app_rtmp_conn.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ srs_error_t SrsRtmpConn::do_publishing(SrsSharedPtr<SrsLiveSource> source, SrsPu
988988

989989
int64_t nb_msgs = 0;
990990
uint64_t nb_frames = 0;
991+
uint64_t nb_audio_frames = 0;
991992
while (true) {
992993
if ((err = trd_->pull()) != srs_success) {
993994
return srs_error_wrap(err, "rtmp: thread quit");
@@ -1029,6 +1030,12 @@ srs_error_t SrsRtmpConn::do_publishing(SrsSharedPtr<SrsLiveSource> source, SrsPu
10291030
}
10301031
nb_frames = rtrd->nb_video_frames();
10311032

1033+
// Update the stat for audio frames.
1034+
if ((err = stat_->on_audio_frames(req, (int)(rtrd->nb_audio_frames() - nb_audio_frames))) != srs_success) {
1035+
return srs_error_wrap(err, "rtmp: stat audio frames");
1036+
}
1037+
nb_audio_frames = rtrd->nb_audio_frames();
1038+
10321039
// reportable
10331040
if (pprint->can_print()) {
10341041
kbps_->sample();

trunk/src/app/srs_app_srt_conn.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,6 @@ 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;
558557

559558
// Max udp packet size equal to 1500.
560559
char buf[1500];
@@ -586,15 +585,6 @@ srs_error_t SrsMpegtsSrtConn::do_publishing()
586585
}
587586

588587
++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-
}
598588

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

trunk/src/app/srs_app_srt_source.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,8 @@ SrsSrtFormat::SrsSrtFormat()
941941
format_ = new SrsRtmpFormat();
942942
video_codec_reported_ = false;
943943
audio_codec_reported_ = false;
944+
nn_video_frames_ = 0;
945+
nn_audio_frames_ = 0;
944946

945947
stat_ = _srs_stat;
946948
}
@@ -988,12 +990,45 @@ srs_error_t SrsSrtFormat::on_srt_packet(SrsSrtPacket *pkt)
988990
return err;
989991
}
990992

993+
void SrsSrtFormat::update_ts_message_stats(bool is_audio)
994+
{
995+
srs_error_t err = srs_success;
996+
997+
// Count TS messages for statistics.
998+
if (is_audio) {
999+
++nn_audio_frames_;
1000+
} else {
1001+
++nn_video_frames_;
1002+
}
1003+
1004+
// Update the stat for video frames, counting TS messages as frames.
1005+
if (nn_video_frames_ > 288) {
1006+
if ((err = stat_->on_video_frames(req_, nn_video_frames_)) != srs_success) {
1007+
srs_warn("SRT: stat video frames err %s", srs_error_desc(err).c_str());
1008+
srs_freep(err);
1009+
}
1010+
nn_video_frames_ = 0;
1011+
}
1012+
1013+
// Update the stat for audio frames periodically.
1014+
if (nn_audio_frames_ > 288) {
1015+
if ((err = stat_->on_audio_frames(req_, nn_audio_frames_)) != srs_success) {
1016+
srs_warn("SRT: stat audio frames err %s", srs_error_desc(err).c_str());
1017+
srs_freep(err);
1018+
}
1019+
nn_audio_frames_ = 0;
1020+
}
1021+
}
1022+
9911023
srs_error_t SrsSrtFormat::on_ts_message(SrsTsMessage *msg)
9921024
{
9931025
srs_error_t err = srs_success;
9941026

9951027
// Only parse video and audio messages
9961028
if (msg->channel_->stream_ == SrsTsStreamVideoH264 || msg->channel_->stream_ == SrsTsStreamVideoHEVC) {
1029+
// Update statistics for video frames
1030+
update_ts_message_stats(false);
1031+
9971032
if (video_codec_reported_) {
9981033
return err;
9991034
}
@@ -1016,6 +1051,9 @@ srs_error_t SrsSrtFormat::on_ts_message(SrsTsMessage *msg)
10161051
c->width_, c->height_);
10171052
}
10181053
} else if (msg->channel_->stream_ == SrsTsStreamAudioAAC) {
1054+
// Update statistics for audio frames
1055+
update_ts_message_stats(true);
1056+
10191057
if (audio_codec_reported_) {
10201058
return err;
10211059
}

trunk/src/app/srs_app_srt_source.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ class SrsSrtFormat : public ISrsSrtFormat, public ISrsTsHandler
179179

180180
// clang-format off
181181
SRS_DECLARE_PRIVATE: // clang-format on
182+
void update_ts_message_stats(bool is_audio);
182183
srs_error_t parse_video_codec(SrsTsMessage *msg);
183184
srs_error_t parse_audio_codec(SrsTsMessage *msg);
184185

@@ -191,6 +192,9 @@ class SrsSrtFormat : public ISrsSrtFormat, public ISrsTsHandler
191192
// Track whether we've already reported codec info to avoid duplicate updates
192193
bool video_codec_reported_;
193194
bool audio_codec_reported_;
195+
// Frame counters for statistics reporting
196+
int nn_video_frames_;
197+
int nn_audio_frames_;
194198
};
195199

196200
// Collect and build SRT TS packet to AV frames.

trunk/src/app/srs_app_statistic.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,15 @@ SrsStatisticStream::SrsStatisticStream()
9797
kbps_ = new SrsKbps();
9898

9999
nb_clients_ = 0;
100-
frames_ = new SrsPps();
100+
video_frames_ = new SrsPps();
101+
audio_frames_ = new SrsPps();
101102
}
102103

103104
SrsStatisticStream::~SrsStatisticStream()
104105
{
105106
srs_freep(kbps_);
106-
srs_freep(frames_);
107+
srs_freep(video_frames_);
108+
srs_freep(audio_frames_);
107109
}
108110

109111
srs_error_t SrsStatisticStream::dumps(SrsJsonObject *obj)
@@ -118,7 +120,9 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject *obj)
118120
obj->set("url", SrsJsonAny::str(url_.c_str()));
119121
obj->set("live_ms", SrsJsonAny::integer(srsu2ms(srs_time_now_cached())));
120122
obj->set("clients", SrsJsonAny::integer(nb_clients_));
121-
obj->set("frames", SrsJsonAny::integer(frames_->sugar_));
123+
obj->set("frames", SrsJsonAny::integer(video_frames_->sugar_ + audio_frames_->sugar_));
124+
obj->set("audio_frames", SrsJsonAny::integer(audio_frames_->sugar_));
125+
obj->set("video_frames", SrsJsonAny::integer(video_frames_->sugar_));
122126
obj->set("send_bytes", SrsJsonAny::integer(kbps_->get_send_bytes()));
123127
obj->set("recv_bytes", SrsJsonAny::integer(kbps_->get_recv_bytes()));
124128

@@ -395,7 +399,19 @@ srs_error_t SrsStatistic::on_video_frames(ISrsRequest *req, int nb_frames)
395399
SrsStatisticVhost *vhost = create_vhost(req);
396400
SrsStatisticStream *stream = create_stream(vhost, req);
397401

398-
stream->frames_->sugar_ += nb_frames;
402+
stream->video_frames_->sugar_ += nb_frames;
403+
404+
return err;
405+
}
406+
407+
srs_error_t SrsStatistic::on_audio_frames(ISrsRequest *req, int nb_frames)
408+
{
409+
srs_error_t err = srs_success;
410+
411+
SrsStatisticVhost *vhost = create_vhost(req);
412+
SrsStatisticStream *stream = create_stream(vhost, req);
413+
414+
stream->audio_frames_->sugar_ += nb_frames;
399415

400416
return err;
401417
}
@@ -542,7 +558,8 @@ void SrsStatistic::kbps_sample()
542558
for (it = streams_.begin(); it != streams_.end(); it++) {
543559
SrsStatisticStream *stream = it->second;
544560
stream->kbps_->sample();
545-
stream->frames_->update();
561+
stream->video_frames_->update();
562+
stream->audio_frames_->update();
546563
}
547564
}
548565
if (true) {

0 commit comments

Comments
 (0)