mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-06-12 19:24:22 +08:00
fix(webrtc): fix GStreamer WHEP interop (#4720)
## Summary This PR fixes GStreamer interoperability issues during WebRTC/WHEP negotiation with ZLMediaServer. GStreamer could fail to establish the connection for two separate reasons: 1. ZLMediaServer generated a non-compliant ICE `ufrag`. The generated value contained `_`, which is not a valid ICE character, so GStreamer rejected the SDP. 2. ZLMediaServer did not correctly handle `bundle-only` offers and could answer an accepted bundled m-line with `port=0`, which caused the later WHEP negotiation to fail. ## Changes - Generate ICE `ufrag` values using ICE-compliant characters only. - Preserve and handle `a=bundle-only` correctly during SDP parsing and answer generation. - Return `port=9` instead of `port=0` for accepted bundled m-lines. - Add regression coverage for `bundle-only` SDP handling. - URL-encode `delete_webrtc` query parameters in the returned `Location` header so ICE-safe identifiers remain round-trippable over HTTP. ## Validation - Built with WebRTC and SCTP enabled. - Added regression test: `test_webrtc_regression` - Verified: - ICE-safe identifier round-trip through `delete_webrtc` - `bundle-only` SDP answer generation
This commit is contained in:
@@ -799,6 +799,7 @@ void RtcSession::loadFrom(const string &str) {
|
||||
rtc_media.fingerprint = sdp.getItemClass<SdpAttrFingerprint>('a', "fingerprint");
|
||||
}
|
||||
rtc_media.ice_lite = media.getItem('a', "ice-lite").operator bool();
|
||||
rtc_media.bundle_only = media.getItem('a', "bundle-only").operator bool();
|
||||
auto ice_options = media.getItemClass<SdpAttrIceOption>('a', "ice-options");
|
||||
rtc_media.ice_trickle = ice_options.trickle;
|
||||
rtc_media.ice_renomination = ice_options.renomination;
|
||||
@@ -1650,14 +1651,23 @@ static RtpDirection matchDirection(RtpDirection offer_direction, RtpDirection su
|
||||
}
|
||||
}
|
||||
|
||||
static DtlsRole mathDtlsRole(DtlsRole role) {
|
||||
switch (role) {
|
||||
case DtlsRole::actpass:
|
||||
case DtlsRole::active: return DtlsRole::passive;
|
||||
case DtlsRole::passive: return DtlsRole::active;
|
||||
default: CHECK(0, "invalid role:", getDtlsRoleString(role)); return DtlsRole::passive;
|
||||
}
|
||||
}
|
||||
static DtlsRole mathDtlsRole(DtlsRole role) {
|
||||
switch (role) {
|
||||
case DtlsRole::actpass:
|
||||
case DtlsRole::active: return DtlsRole::passive;
|
||||
case DtlsRole::passive: return DtlsRole::active;
|
||||
default: CHECK(0, "invalid role:", getDtlsRoleString(role)); return DtlsRole::passive;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t getAnswerMediaPort(const RtcMedia &offer_media) {
|
||||
// RFC 8843: bundle-only m-lines may use port=0 in the offer but still need a
|
||||
// real port placeholder in the accepted answer.
|
||||
if (!offer_media.port && offer_media.bundle_only) {
|
||||
return 9;
|
||||
}
|
||||
return offer_media.port;
|
||||
}
|
||||
|
||||
void RtcConfigure::createMediaOffer(const std::shared_ptr<RtcSession> &ret) const {
|
||||
int index = 0;
|
||||
@@ -1824,13 +1834,14 @@ void RtcConfigure::matchMedia(const std::shared_ptr<RtcSession> &ret, const RtcM
|
||||
|
||||
RETRY:
|
||||
|
||||
if (offer_media.type == TrackApplication) {
|
||||
RtcMedia answer_media = offer_media;
|
||||
answer_media.role = mathDtlsRole(offer_media.role);
|
||||
answer_media.ice_ufrag = configure.ice_ufrag;
|
||||
answer_media.ice_pwd = configure.ice_pwd;
|
||||
answer_media.fingerprint = configure.fingerprint;
|
||||
answer_media.ice_lite = configure.ice_lite;
|
||||
if (offer_media.type == TrackApplication) {
|
||||
RtcMedia answer_media = offer_media;
|
||||
answer_media.port = getAnswerMediaPort(offer_media);
|
||||
answer_media.role = mathDtlsRole(offer_media.role);
|
||||
answer_media.ice_ufrag = configure.ice_ufrag;
|
||||
answer_media.ice_pwd = configure.ice_pwd;
|
||||
answer_media.fingerprint = configure.fingerprint;
|
||||
answer_media.ice_lite = configure.ice_lite;
|
||||
#ifdef ENABLE_SCTP
|
||||
answer_media.candidate = configure.candidate;
|
||||
#else
|
||||
@@ -1867,14 +1878,14 @@ RETRY:
|
||||
// All codecs for this media in the offer are not supported
|
||||
continue;
|
||||
}
|
||||
RtcMedia answer_media;
|
||||
answer_media.type = offer_media.type;
|
||||
answer_media.mid = offer_media.mid;
|
||||
answer_media.proto = offer_media.proto;
|
||||
answer_media.port = offer_media.port;
|
||||
answer_media.addr = offer_media.addr;
|
||||
answer_media.bandwidth = offer_media.bandwidth;
|
||||
answer_media.rtcp_addr = offer_media.rtcp_addr;
|
||||
RtcMedia answer_media;
|
||||
answer_media.type = offer_media.type;
|
||||
answer_media.mid = offer_media.mid;
|
||||
answer_media.proto = offer_media.proto;
|
||||
answer_media.port = getAnswerMediaPort(offer_media);
|
||||
answer_media.addr = offer_media.addr;
|
||||
answer_media.bandwidth = offer_media.bandwidth;
|
||||
answer_media.rtcp_addr = offer_media.rtcp_addr;
|
||||
answer_media.rtcp_mux = offer_media.rtcp_mux && configure.rtcp_mux;
|
||||
answer_media.rtcp_rsize = offer_media.rtcp_rsize && configure.rtcp_rsize;
|
||||
answer_media.ice_trickle = offer_media.ice_trickle && configure.ice_trickle;
|
||||
|
||||
@@ -652,6 +652,9 @@ public:
|
||||
bool rtcp_rsize { false };
|
||||
SdpAttrRtcp rtcp_addr;
|
||||
|
||||
//////// bundle ////////
|
||||
bool bundle_only { false };
|
||||
|
||||
//////// ice ////////
|
||||
bool ice_trickle { false };
|
||||
bool ice_lite { false };
|
||||
|
||||
@@ -137,7 +137,12 @@ static std::string getServerPrefix() {
|
||||
// 拷贝tcp端口 [AUTO-TRANSLATED:23191878]
|
||||
// Copy tcp port
|
||||
memcpy(buf + 6, &(reinterpret_cast<sockaddr_in *>(&addr)->sin_port), 2);
|
||||
auto ret = encodeBase64(string(buf, 8)) + '_';
|
||||
// RFC 5245 §15.4: ice-char = ALPHA / DIGIT / "+" / "/"
|
||||
auto ret = encodeBase64(string(buf, 8));
|
||||
// Remove base64 '=' padding (not a valid ice-char)
|
||||
ret.erase(std::remove(ret.begin(), ret.end(), '='), ret.end());
|
||||
// Use '/' separator instead of '_' (not a valid ice-char)
|
||||
ret += '/';
|
||||
InfoL << "MediaServer(" << host << ":" << udp_port << ":" << tcp_port << ") prefix: " << ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user