diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index d0aefc36d5..bf55386908 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -430,6 +430,10 @@ rtc_server { # * Retrieve server IP automatically, from all network interfaces. # $CANDIDATE Read the IP from ENV variable, use * if not set. # x.x.x.x A specified IP address or DNS name, use * if 0.0.0.0. + # You can also set the candidate by the query string eip, note that you can also set the UDP port, + # for example: + # http://locahost:1985/rtc/v1/whip/?app=live&stream=livestream&eip=192.168.3.11 + # http://locahost:1985/rtc/v1/whip/?app=live&stream=livestream&eip=192.168.3.11:18000 # @remark For Firefox, the candidate MUST be IP, MUST NOT be DNS name, see https://bugzilla.mozilla.org/show_bug.cgi?id=1239006 # @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate # Overwrite by env SRS_RTC_SERVER_CANDIDATE diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 93b9859b15..c233f864a6 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* 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) * 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) * v5.0, 2024-02-05, Merge [#3923](https://github.com/ossrs/srs/pull/3923): Configure: print enabled/disable sanitizer. v5.0.206 (#3923) * v5.0, 2023-12-30, Merge [#3916](https://github.com/ossrs/srs/pull/3916): Enhancing the compatibility of options.sh. v5.0.204 (#3916) diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index 510157eb72..792f15dde0 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -1332,6 +1332,9 @@ SrsRtmpFromRtcBridge::SrsRtmpFromRtcBridge(SrsLiveSource *src) rtp_key_frame_ts_ = -1; header_sn_ = 0; memset(cache_video_pkts_, 0, sizeof(cache_video_pkts_)); + rtp_key_frame_ts_ = -1; + sync_state_ = -1; + obs_whip_sps_ = obs_whip_pps_ = NULL; } SrsRtmpFromRtcBridge::~SrsRtmpFromRtcBridge() @@ -1339,6 +1342,8 @@ SrsRtmpFromRtcBridge::~SrsRtmpFromRtcBridge() srs_freep(codec_); srs_freep(format); clear_cached_video(); + srs_freep(obs_whip_sps_); + srs_freep(obs_whip_pps_); } srs_error_t SrsRtmpFromRtcBridge::initialize(SrsRequest* r) @@ -1392,8 +1397,18 @@ srs_error_t SrsRtmpFromRtcBridge::on_rtp(SrsRtpPacket *pkt) // Have no received any sender report, can't calculate avsync_time, // discard it to avoid timestamp problem in live source + const SrsRtpHeader& h = pkt->header; if (pkt->get_avsync_time() <= 0) { + if (sync_state_ < 0) { + srs_trace("RTC: Discard no-sync %s, ssrc=%u, seq=%u, ts=%u, state=%d", pkt->is_audio() ? "Audio" : "Video", + h.get_ssrc(), h.get_sequence(), h.get_timestamp(), sync_state_); + sync_state_ = 0; + } return err; + } else if (sync_state_ < 1) { + srs_trace("RTC: Accept sync %s, ssrc=%u, seq=%u, ts=%u, state=%d", pkt->is_audio() ? "Audio" : "Video", + h.get_ssrc(), h.get_sequence(), h.get_timestamp(), sync_state_); + sync_state_ = 2; } if (pkt->is_audio()) { @@ -1521,41 +1536,70 @@ srs_error_t SrsRtmpFromRtcBridge::packet_video_key_frame(SrsRtpPacket* pkt) { srs_error_t err = srs_success; - // TODO: handle sps and pps in 2 rtp packets + // For OBS WHIP, it uses RTP Raw packet with SPS/PPS/IDR frame. Note that not all + // raw payload is SPS/PPS. + bool has_sps_pps_in_raw_payload = false; + SrsRtpRawPayload* raw_payload = dynamic_cast(pkt->payload()); + if (raw_payload) { + if (pkt->nalu_type == SrsAvcNaluTypeSPS) { + has_sps_pps_in_raw_payload = true; + srs_freep(obs_whip_sps_); + obs_whip_sps_ = pkt->copy(); + } else if (pkt->nalu_type == SrsAvcNaluTypePPS) { + has_sps_pps_in_raw_payload = true; + srs_freep(obs_whip_pps_); + obs_whip_pps_ = pkt->copy(); + } + // Ignore if one of OBS WHIP SPS/PPS is not ready. + if (has_sps_pps_in_raw_payload && (!obs_whip_sps_ || !obs_whip_pps_)) { + return err; + } + } + + // Generally, there will be SPS+PPS+IDR in a STAP-A packet. SrsRtpSTAPPayload* stap_payload = dynamic_cast(pkt->payload()); - if (stap_payload) { - SrsSample* sps = stap_payload->get_sps(); - SrsSample* pps = stap_payload->get_pps(); - if (NULL == sps || NULL == pps) { + + // Handle SPS/PPS in cache or STAP-A packet. + if (stap_payload || has_sps_pps_in_raw_payload) { + // Get the SPS/PPS from cache or STAP-A packet. + SrsSample* sps = stap_payload ? stap_payload->get_sps() : NULL; + if (!sps && obs_whip_sps_) sps = dynamic_cast(obs_whip_sps_->payload())->sample_; + SrsSample* pps = stap_payload ? stap_payload->get_pps() : NULL; + if (!pps && obs_whip_pps_) pps = dynamic_cast(obs_whip_pps_->payload())->sample_; + if (!sps || !pps) { return srs_error_new(ERROR_RTC_RTP_MUXER, "no sps or pps in stap-a rtp. sps: %p, pps:%p", sps, pps); - } else { - // h264 raw to h264 packet. - std::string sh; - SrsRawH264Stream* avc = new SrsRawH264Stream(); - SrsAutoFree(SrsRawH264Stream, avc); + } - if ((err = avc->mux_sequence_header(string(sps->bytes, sps->size), string(pps->bytes, pps->size), sh)) != srs_success) { - return srs_error_wrap(err, "mux sequence header"); - } + // Reset SPS/PPS cache, ensuring that the next SPS/PPS will be handled when both are received. + SrsAutoFree(SrsRtpPacket, obs_whip_sps_); + SrsAutoFree(SrsRtpPacket, obs_whip_pps_); - // h264 packet to flv packet. - char* flv = NULL; - int nb_flv = 0; - if ((err = avc->mux_avc2flv(sh, SrsVideoAvcFrameTypeKeyFrame, SrsVideoAvcFrameTraitSequenceHeader, pkt->get_avsync_time(), - pkt->get_avsync_time(), &flv, &nb_flv)) != srs_success) { - return srs_error_wrap(err, "avc to flv"); - } + // h264 raw to h264 packet. + std::string sh; + SrsRawH264Stream* avc = new SrsRawH264Stream(); + SrsAutoFree(SrsRawH264Stream, avc); - SrsMessageHeader header; - header.initialize_video(nb_flv, pkt->get_avsync_time(), 1); - SrsCommonMessage rtmp; - if ((err = rtmp.create(&header, flv, nb_flv)) != srs_success) { - return srs_error_wrap(err, "create rtmp"); - } + if ((err = avc->mux_sequence_header(string(sps->bytes, sps->size), string(pps->bytes, pps->size), sh)) != srs_success) { + return srs_error_wrap(err, "mux sequence header"); + } - if ((err = source_->on_video(&rtmp)) != srs_success) { - return err; - } + // h264 packet to flv packet. + char* flv = NULL; + int nb_flv = 0; + if ((err = avc->mux_avc2flv(sh, SrsVideoAvcFrameTypeKeyFrame, SrsVideoAvcFrameTraitSequenceHeader, pkt->get_avsync_time(), + pkt->get_avsync_time(), &flv, &nb_flv)) != srs_success) { + return srs_error_wrap(err, "avc to flv"); + } + + SrsMessageHeader header; + header.initialize_video(nb_flv, pkt->get_avsync_time(), 1); + SrsCommonMessage rtmp; + if ((err = rtmp.create(&header, flv, nb_flv)) != srs_success) { + return srs_error_wrap(err, "create rtmp"); + } + + if ((err = source_->on_video(&rtmp)) != srs_success) { + return err; } } @@ -1600,7 +1644,7 @@ srs_error_t SrsRtmpFromRtcBridge::packet_video_key_frame(SrsRtpPacket* pkt) if (-1 == sn) { if (check_frame_complete(header_sn_, tail_sn)) { if ((err = packet_video_rtmp(header_sn_, tail_sn)) != srs_success) { - err = srs_error_wrap(err, "fail to packet key frame"); + err = srs_error_wrap(err, "fail to packet frame"); } } } else if (-2 == sn) { diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 691494a188..e4fa643efb 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -313,6 +313,13 @@ class SrsRtmpFromRtcBridge : public ISrsRtcSourceBridge uint16_t header_sn_; uint16_t lost_sn_; int64_t rtp_key_frame_ts_; +private: + // The state for timestamp sync state. -1 for init. 0 not sync. 1 sync. + int sync_state_; +private: + // For OBS WHIP, send SPS/PPS in dedicated RTP packet. + SrsRtpPacket* obs_whip_sps_; + SrsRtpPacket* obs_whip_pps_; public: SrsRtmpFromRtcBridge(SrsLiveSource *src); virtual ~SrsRtmpFromRtcBridge(); diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index 3e92bb67da..3f4420ee52 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 207 +#define VERSION_REVISION 208 #endif diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp index a135632dac..aaec9a8882 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp @@ -970,12 +970,14 @@ SrsRtpRawPayload::SrsRtpRawPayload() { payload = NULL; nn_payload = 0; + sample_ = new SrsSample(); ++_srs_pps_objs_rraw->sugar; } SrsRtpRawPayload::~SrsRtpRawPayload() { + srs_freep(sample_); } uint64_t SrsRtpRawPayload::nb_bytes() @@ -1007,6 +1009,9 @@ srs_error_t SrsRtpRawPayload::decode(SrsBuffer* buf) payload = buf->head(); nn_payload = buf->left(); + sample_->bytes = payload; + sample_->size = nn_payload; + return srs_success; } @@ -1016,6 +1021,7 @@ ISrsRtpPayloader* SrsRtpRawPayload::copy() cp->payload = payload; cp->nn_payload = nn_payload; + cp->sample_ = sample_->copy(); return cp; } diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp index 843379c1ef..1fe6123bf5 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp @@ -347,6 +347,9 @@ class SrsRtpRawPayload : public ISrsRtpPayloader // @remark We only refer to the memory, user must free its bytes. char* payload; int nn_payload; +public: + // Use the whole RAW RTP payload as a sample. + SrsSample* sample_; public: SrsRtpRawPayload(); virtual ~SrsRtpRawPayload();