AI: WebRTC: Fix audio-only WHIP publish without SSRC. v7.0.132 (#4570) (#4599)

This commit is contained in:
OSSRS-AI 2025-12-03 09:00:16 -05:00 committed by GitHub
parent f47e3ab458
commit 00494d5f87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 1 deletions

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## SRS 7.0 Changelog
* v7.0, 2025-12-03, AI: WebRTC: Fix audio-only WHIP publish without SSRC. v7.0.132 (#4570)
* v7.0, 2025-11-30, SRT: Support default_mode config for short streamid format. v7.0.131
* v7.0, 2025-11-28, SRT: Fix player not exiting when publisher disconnects. v7.0.130 (#4591)
* v7.0, 2025-11-27, Merge [#4588](https://github.com/ossrs/srs/pull/4588): RTMP: Ignore FMLE start packet after flash publish. v7.0.129 (#4588)

View File

@ -3632,6 +3632,26 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
track_id = msid_tracker;
}
// Handle case where no SSRC info is present in the offer (e.g., libdatachannel audio-only).
// We still need to create track description to generate proper SDP answer.
// See https://github.com/paullouisageneau/libdatachannel which may not include SSRC.
// See https://github.com/ossrs/srs/issues/4570#issuecomment-3604598513
if (remote_media_desc.ssrc_infos_.empty()) {
SrsRtcTrackDescription *track_desc_copy = track_desc->copy();
// Generate synthetic values since no SSRC info provided.
track_desc_copy->ssrc_ = 0;
track_desc_copy->id_ = srs_fmt_sprintf("track-%s-%s", track_desc->type_.c_str(), remote_media_desc.mid_.c_str());
track_desc_copy->msid_ = req->app_ + "/" + req->stream_;
if (remote_media_desc.is_audio() && !stream_desc->audio_track_desc_) {
stream_desc->audio_track_desc_ = track_desc_copy;
} else if (remote_media_desc.is_video()) {
stream_desc->video_track_descs_.push_back(track_desc_copy);
} else {
srs_freep(track_desc_copy);
}
}
// set track fec_ssrc and rtx_ssrc
for (int j = 0; j < (int)remote_media_desc.ssrc_groups_.size(); ++j) {
const SrsSSRCGroup &ssrc_group = remote_media_desc.ssrc_groups_.at(j);

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 131
#define VERSION_REVISION 132
#endif

View File

@ -2149,6 +2149,91 @@ VOID TEST(SrsRtcPublisherNegotiatorTest, LibdatachannelUseScenario)
EXPECT_EQ("video", video_sdp.media_descs_[0].type_);
}
// Test audio-only libdatachannel scenario WITHOUT SSRC info.
// This test demonstrates the bug where libdatachannel fails with:
// "Remote description has no ICE user fragment"
// Root cause: When the offer SDP has no a=ssrc: line, stream_desc->audio_track_desc_
// is never set, so generate_publish_local_sdp_for_audio() doesn't add the m=audio
// section to the answer SDP.
VOID TEST(SrsRtcPublisherNegotiatorTest, LibdatachannelAudioOnlyWithoutSsrc)
{
srs_error_t err;
// Create SrsRtcPublisherNegotiator
SrsUniquePtr<SrsRtcPublisherNegotiator> negotiator(new SrsRtcPublisherNegotiator());
// Create mock request for initialization
SrsUniquePtr<MockRtcConnectionRequest> mock_request(new MockRtcConnectionRequest("test.vhost", "live", "voice_stream"));
// Create mock RTC user config with remote SDP
SrsUniquePtr<SrsRtcUserConfig> ruc(new SrsRtcUserConfig());
ruc->req_ = mock_request->copy();
ruc->publish_ = true;
ruc->dtls_ = true;
ruc->srtp_ = true;
ruc->audio_before_video_ = true;
// Audio-only SDP from libdatachannel - NO SSRC LINE (this is the key difference!)
// This matches the actual user-reported SDP that causes the bug
ruc->remote_sdp_str_ =
"v=0\r\n"
"o=rtc 4107523824 0 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=group:BUNDLE audio\r\n"
"a=group:LS audio\r\n"
"a=msid-semantic:WMS *\r\n"
"a=ice-options:ice2,trickle\r\n"
"a=fingerprint:sha-256 C3:22:A4:0D:46:6C:8C:3E:3B:05:59:63:C3:8A:43:97:30:4C:3E:5F:01:BA:C9:77:AC:10:89:A7:83:BA:21:08\r\n"
"m=audio 36954 UDP/TLS/RTP/SAVPF 111\r\n"
"c=IN IP4 192.168.1.100\r\n"
"a=mid:audio\r\n"
"a=sendonly\r\n"
"a=rtcp-mux\r\n"
"a=rtpmap:111 opus/48000/2\r\n"
"a=fmtp:111 minptime=10;maxaveragebitrate=96000;stereo=1;sprop-stereo=1;useinbandfec=1\r\n"
"a=setup:actpass\r\n"
"a=ice-ufrag:rUic\r\n"
"a=ice-pwd:76ZWO/4FkRx6r2nMUF8yeH\r\n"
// NOTE: No a=ssrc: line here - this is the bug trigger!
"a=candidate:1 1 UDP 2114977791 192.168.1.100 36954 typ host\r\n"
"a=end-of-candidates\r\n";
// Parse the remote SDP
HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_));
// Verify only audio media description is present
EXPECT_EQ(1u, ruc->remote_sdp_.media_descs_.size());
EXPECT_EQ("audio", ruc->remote_sdp_.media_descs_[0].type_);
// Verify NO SSRC info in the parsed SDP (this is the bug condition)
EXPECT_TRUE(ruc->remote_sdp_.media_descs_[0].ssrc_infos_.empty());
// Create stream description for negotiation output
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
// Test negotiate_publish_capability - this should work but audio_track_desc_ will be NULL
HELPER_EXPECT_SUCCESS(negotiator->negotiate_publish_capability(ruc.get(), stream_desc.get()));
// BUG: audio_track_desc_ is NULL because there's no SSRC info in the offer
// This causes generate_publish_local_sdp_for_audio() to not add m=audio section
EXPECT_TRUE(stream_desc->audio_track_desc_ != NULL) << "BUG: audio_track_desc_ should not be NULL for audio-only SDP without SSRC";
EXPECT_TRUE(stream_desc->video_track_descs_.empty());
// Test generate_publish_local_sdp - create answer SDP
SrsSdp local_sdp;
HELPER_EXPECT_SUCCESS(negotiator->generate_publish_local_sdp(
ruc->req_, local_sdp, stream_desc.get(),
ruc->remote_sdp_.is_unified(), ruc->audio_before_video_));
// BUG: local_sdp.media_descs_ is empty because audio_track_desc_ was NULL
// This causes the answer SDP to have no m=audio line, which makes libdatachannel fail
EXPECT_EQ(1u, local_sdp.media_descs_.size()) << "BUG: Answer SDP should have m=audio section";
if (!local_sdp.media_descs_.empty()) {
EXPECT_EQ("audio", local_sdp.media_descs_[0].type_);
}
}
VOID TEST(SrsRtcConnectionTest, InitializeTypicalScenario)
{
srs_error_t err;