Skip to content

Commit 732569f

Browse files
committed
WHIP: Fix bug for converting WHIP to RTMP/HLS. v5.0.208 (#3920)
1. When converting RTC to RTMP, it is necessary to synchronize the audio and video timestamps. When the synchronization status changes, whether it is unsynchronized or synchronized, print logs to facilitate troubleshooting of such issues. 2. Chrome uses the STAP-A packet, which means a single RTP packet contains SPS/PPS information. OBS WHIP, on the other hand, sends SPS and PPS in separate RTP packets. Therefore, SPS and PPS are in two independent RTP packets, and SRS needs to cache these two packets. --------- Co-authored-by: john <[email protected]>
1 parent ba150be commit 732569f

File tree

7 files changed

+96
-31
lines changed

7 files changed

+96
-31
lines changed

trunk/conf/full.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,10 @@ rtc_server {
430430
# * Retrieve server IP automatically, from all network interfaces.
431431
# $CANDIDATE Read the IP from ENV variable, use * if not set.
432432
# x.x.x.x A specified IP address or DNS name, use * if 0.0.0.0.
433+
# You can also set the candidate by the query string eip, note that you can also set the UDP port,
434+
# for example:
435+
# http://locahost:1985/rtc/v1/whip/?app=live&stream=livestream&eip=192.168.3.11
436+
# http://locahost:1985/rtc/v1/whip/?app=live&stream=livestream&eip=192.168.3.11:18000
433437
# @remark For Firefox, the candidate MUST be IP, MUST NOT be DNS name, see https://bugzilla.mozilla.org/show_bug.cgi?id=1239006
434438
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
435439
# Overwrite by env SRS_RTC_SERVER_CANDIDATE

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="v5-changes"></a>
88

99
## SRS 5.0 Changelog
10+
* v5.0, 2024-02-06, Merge [#3920](https://github.com/ossrs/srs/pull/3920): WHIP: Fix bug for converting WHIP to RTMP/HLS. v5.0.208 (#3920)
1011
* v5.0, 2024-02-05, Merge [#3925](https://github.com/ossrs/srs/pull/3925): RTC: Fix video and audio track pt_ is not change in player before publisher. v5.0.207 (#3925)
1112
* v5.0, 2024-02-05, Merge [#3923](https://github.com/ossrs/srs/pull/3923): Configure: print enabled/disable sanitizer. v5.0.206 (#3923)
1213
* v5.0, 2023-12-30, Merge [#3916](https://github.com/ossrs/srs/pull/3916): Enhancing the compatibility of options.sh. v5.0.204 (#3916)

trunk/src/app/srs_app_rtc_source.cpp

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,13 +1332,18 @@ SrsRtmpFromRtcBridge::SrsRtmpFromRtcBridge(SrsLiveSource *src)
13321332
rtp_key_frame_ts_ = -1;
13331333
header_sn_ = 0;
13341334
memset(cache_video_pkts_, 0, sizeof(cache_video_pkts_));
1335+
rtp_key_frame_ts_ = -1;
1336+
sync_state_ = -1;
1337+
obs_whip_sps_ = obs_whip_pps_ = NULL;
13351338
}
13361339

13371340
SrsRtmpFromRtcBridge::~SrsRtmpFromRtcBridge()
13381341
{
13391342
srs_freep(codec_);
13401343
srs_freep(format);
13411344
clear_cached_video();
1345+
srs_freep(obs_whip_sps_);
1346+
srs_freep(obs_whip_pps_);
13421347
}
13431348

13441349
srs_error_t SrsRtmpFromRtcBridge::initialize(SrsRequest* r)
@@ -1392,8 +1397,18 @@ srs_error_t SrsRtmpFromRtcBridge::on_rtp(SrsRtpPacket *pkt)
13921397

13931398
// Have no received any sender report, can't calculate avsync_time,
13941399
// discard it to avoid timestamp problem in live source
1400+
const SrsRtpHeader& h = pkt->header;
13951401
if (pkt->get_avsync_time() <= 0) {
1402+
if (sync_state_ < 0) {
1403+
srs_trace("RTC: Discard no-sync %s, ssrc=%u, seq=%u, ts=%u, state=%d", pkt->is_audio() ? "Audio" : "Video",
1404+
h.get_ssrc(), h.get_sequence(), h.get_timestamp(), sync_state_);
1405+
sync_state_ = 0;
1406+
}
13961407
return err;
1408+
} else if (sync_state_ < 1) {
1409+
srs_trace("RTC: Accept sync %s, ssrc=%u, seq=%u, ts=%u, state=%d", pkt->is_audio() ? "Audio" : "Video",
1410+
h.get_ssrc(), h.get_sequence(), h.get_timestamp(), sync_state_);
1411+
sync_state_ = 2;
13971412
}
13981413

13991414
if (pkt->is_audio()) {
@@ -1521,41 +1536,70 @@ srs_error_t SrsRtmpFromRtcBridge::packet_video_key_frame(SrsRtpPacket* pkt)
15211536
{
15221537
srs_error_t err = srs_success;
15231538

1524-
// TODO: handle sps and pps in 2 rtp packets
1539+
// For OBS WHIP, it uses RTP Raw packet with SPS/PPS/IDR frame. Note that not all
1540+
// raw payload is SPS/PPS.
1541+
bool has_sps_pps_in_raw_payload = false;
1542+
SrsRtpRawPayload* raw_payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload());
1543+
if (raw_payload) {
1544+
if (pkt->nalu_type == SrsAvcNaluTypeSPS) {
1545+
has_sps_pps_in_raw_payload = true;
1546+
srs_freep(obs_whip_sps_);
1547+
obs_whip_sps_ = pkt->copy();
1548+
} else if (pkt->nalu_type == SrsAvcNaluTypePPS) {
1549+
has_sps_pps_in_raw_payload = true;
1550+
srs_freep(obs_whip_pps_);
1551+
obs_whip_pps_ = pkt->copy();
1552+
}
1553+
// Ignore if one of OBS WHIP SPS/PPS is not ready.
1554+
if (has_sps_pps_in_raw_payload && (!obs_whip_sps_ || !obs_whip_pps_)) {
1555+
return err;
1556+
}
1557+
}
1558+
1559+
// Generally, there will be SPS+PPS+IDR in a STAP-A packet.
15251560
SrsRtpSTAPPayload* stap_payload = dynamic_cast<SrsRtpSTAPPayload*>(pkt->payload());
1526-
if (stap_payload) {
1527-
SrsSample* sps = stap_payload->get_sps();
1528-
SrsSample* pps = stap_payload->get_pps();
1529-
if (NULL == sps || NULL == pps) {
1561+
1562+
// Handle SPS/PPS in cache or STAP-A packet.
1563+
if (stap_payload || has_sps_pps_in_raw_payload) {
1564+
// Get the SPS/PPS from cache or STAP-A packet.
1565+
SrsSample* sps = stap_payload ? stap_payload->get_sps() : NULL;
1566+
if (!sps && obs_whip_sps_) sps = dynamic_cast<SrsRtpRawPayload*>(obs_whip_sps_->payload())->sample_;
1567+
SrsSample* pps = stap_payload ? stap_payload->get_pps() : NULL;
1568+
if (!pps && obs_whip_pps_) pps = dynamic_cast<SrsRtpRawPayload*>(obs_whip_pps_->payload())->sample_;
1569+
if (!sps || !pps) {
15301570
return srs_error_new(ERROR_RTC_RTP_MUXER, "no sps or pps in stap-a rtp. sps: %p, pps:%p", sps, pps);
1531-
} else {
1532-
// h264 raw to h264 packet.
1533-
std::string sh;
1534-
SrsRawH264Stream* avc = new SrsRawH264Stream();
1535-
SrsAutoFree(SrsRawH264Stream, avc);
1571+
}
15361572

1537-
if ((err = avc->mux_sequence_header(string(sps->bytes, sps->size), string(pps->bytes, pps->size), sh)) != srs_success) {
1538-
return srs_error_wrap(err, "mux sequence header");
1539-
}
1573+
// Reset SPS/PPS cache, ensuring that the next SPS/PPS will be handled when both are received.
1574+
SrsAutoFree(SrsRtpPacket, obs_whip_sps_);
1575+
SrsAutoFree(SrsRtpPacket, obs_whip_pps_);
15401576

1541-
// h264 packet to flv packet.
1542-
char* flv = NULL;
1543-
int nb_flv = 0;
1544-
if ((err = avc->mux_avc2flv(sh, SrsVideoAvcFrameTypeKeyFrame, SrsVideoAvcFrameTraitSequenceHeader, pkt->get_avsync_time(),
1545-
pkt->get_avsync_time(), &flv, &nb_flv)) != srs_success) {
1546-
return srs_error_wrap(err, "avc to flv");
1547-
}
1577+
// h264 raw to h264 packet.
1578+
std::string sh;
1579+
SrsRawH264Stream* avc = new SrsRawH264Stream();
1580+
SrsAutoFree(SrsRawH264Stream, avc);
15481581

1549-
SrsMessageHeader header;
1550-
header.initialize_video(nb_flv, pkt->get_avsync_time(), 1);
1551-
SrsCommonMessage rtmp;
1552-
if ((err = rtmp.create(&header, flv, nb_flv)) != srs_success) {
1553-
return srs_error_wrap(err, "create rtmp");
1554-
}
1582+
if ((err = avc->mux_sequence_header(string(sps->bytes, sps->size), string(pps->bytes, pps->size), sh)) != srs_success) {
1583+
return srs_error_wrap(err, "mux sequence header");
1584+
}
15551585

1556-
if ((err = source_->on_video(&rtmp)) != srs_success) {
1557-
return err;
1558-
}
1586+
// h264 packet to flv packet.
1587+
char* flv = NULL;
1588+
int nb_flv = 0;
1589+
if ((err = avc->mux_avc2flv(sh, SrsVideoAvcFrameTypeKeyFrame, SrsVideoAvcFrameTraitSequenceHeader, pkt->get_avsync_time(),
1590+
pkt->get_avsync_time(), &flv, &nb_flv)) != srs_success) {
1591+
return srs_error_wrap(err, "avc to flv");
1592+
}
1593+
1594+
SrsMessageHeader header;
1595+
header.initialize_video(nb_flv, pkt->get_avsync_time(), 1);
1596+
SrsCommonMessage rtmp;
1597+
if ((err = rtmp.create(&header, flv, nb_flv)) != srs_success) {
1598+
return srs_error_wrap(err, "create rtmp");
1599+
}
1600+
1601+
if ((err = source_->on_video(&rtmp)) != srs_success) {
1602+
return err;
15591603
}
15601604
}
15611605

@@ -1600,7 +1644,7 @@ srs_error_t SrsRtmpFromRtcBridge::packet_video_key_frame(SrsRtpPacket* pkt)
16001644
if (-1 == sn) {
16011645
if (check_frame_complete(header_sn_, tail_sn)) {
16021646
if ((err = packet_video_rtmp(header_sn_, tail_sn)) != srs_success) {
1603-
err = srs_error_wrap(err, "fail to packet key frame");
1647+
err = srs_error_wrap(err, "fail to packet frame");
16041648
}
16051649
}
16061650
} else if (-2 == sn) {

trunk/src/app/srs_app_rtc_source.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ class SrsRtmpFromRtcBridge : public ISrsRtcSourceBridge
313313
uint16_t header_sn_;
314314
uint16_t lost_sn_;
315315
int64_t rtp_key_frame_ts_;
316+
private:
317+
// The state for timestamp sync state. -1 for init. 0 not sync. 1 sync.
318+
int sync_state_;
319+
private:
320+
// For OBS WHIP, send SPS/PPS in dedicated RTP packet.
321+
SrsRtpPacket* obs_whip_sps_;
322+
SrsRtpPacket* obs_whip_pps_;
316323
public:
317324
SrsRtmpFromRtcBridge(SrsLiveSource *src);
318325
virtual ~SrsRtmpFromRtcBridge();

trunk/src/core/srs_core_version5.hpp

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

1010
#define VERSION_MAJOR 5
1111
#define VERSION_MINOR 0
12-
#define VERSION_REVISION 207
12+
#define VERSION_REVISION 208
1313

1414
#endif

trunk/src/kernel/srs_kernel_rtc_rtp.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,12 +970,14 @@ SrsRtpRawPayload::SrsRtpRawPayload()
970970
{
971971
payload = NULL;
972972
nn_payload = 0;
973+
sample_ = new SrsSample();
973974

974975
++_srs_pps_objs_rraw->sugar;
975976
}
976977

977978
SrsRtpRawPayload::~SrsRtpRawPayload()
978979
{
980+
srs_freep(sample_);
979981
}
980982

981983
uint64_t SrsRtpRawPayload::nb_bytes()
@@ -1007,6 +1009,9 @@ srs_error_t SrsRtpRawPayload::decode(SrsBuffer* buf)
10071009
payload = buf->head();
10081010
nn_payload = buf->left();
10091011

1012+
sample_->bytes = payload;
1013+
sample_->size = nn_payload;
1014+
10101015
return srs_success;
10111016
}
10121017

@@ -1016,6 +1021,7 @@ ISrsRtpPayloader* SrsRtpRawPayload::copy()
10161021

10171022
cp->payload = payload;
10181023
cp->nn_payload = nn_payload;
1024+
cp->sample_ = sample_->copy();
10191025

10201026
return cp;
10211027
}

trunk/src/kernel/srs_kernel_rtc_rtp.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ class SrsRtpRawPayload : public ISrsRtpPayloader
347347
// @remark We only refer to the memory, user must free its bytes.
348348
char* payload;
349349
int nn_payload;
350+
public:
351+
// Use the whole RAW RTP payload as a sample.
352+
SrsSample* sample_;
350353
public:
351354
SrsRtpRawPayload();
352355
virtual ~SrsRtpRawPayload();

0 commit comments

Comments
 (0)