Skip to content

Commit 00494d5

Browse files
authored
AI: WebRTC: Fix audio-only WHIP publish without SSRC. v7.0.132 (#4570) (#4599)
1 parent f47e3ab commit 00494d5

File tree

4 files changed

+107
-1
lines changed

4 files changed

+107
-1
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-12-03, AI: WebRTC: Fix audio-only WHIP publish without SSRC. v7.0.132 (#4570)
1011
* v7.0, 2025-11-30, SRT: Support default_mode config for short streamid format. v7.0.131
1112
* v7.0, 2025-11-28, SRT: Fix player not exiting when publisher disconnects. v7.0.130 (#4591)
1213
* 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)

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3632,6 +3632,26 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
36323632
track_id = msid_tracker;
36333633
}
36343634

3635+
// Handle case where no SSRC info is present in the offer (e.g., libdatachannel audio-only).
3636+
// We still need to create track description to generate proper SDP answer.
3637+
// See https://github.com/paullouisageneau/libdatachannel which may not include SSRC.
3638+
// See https://github.com/ossrs/srs/issues/4570#issuecomment-3604598513
3639+
if (remote_media_desc.ssrc_infos_.empty()) {
3640+
SrsRtcTrackDescription *track_desc_copy = track_desc->copy();
3641+
// Generate synthetic values since no SSRC info provided.
3642+
track_desc_copy->ssrc_ = 0;
3643+
track_desc_copy->id_ = srs_fmt_sprintf("track-%s-%s", track_desc->type_.c_str(), remote_media_desc.mid_.c_str());
3644+
track_desc_copy->msid_ = req->app_ + "/" + req->stream_;
3645+
3646+
if (remote_media_desc.is_audio() && !stream_desc->audio_track_desc_) {
3647+
stream_desc->audio_track_desc_ = track_desc_copy;
3648+
} else if (remote_media_desc.is_video()) {
3649+
stream_desc->video_track_descs_.push_back(track_desc_copy);
3650+
} else {
3651+
srs_freep(track_desc_copy);
3652+
}
3653+
}
3654+
36353655
// set track fec_ssrc and rtx_ssrc
36363656
for (int j = 0; j < (int)remote_media_desc.ssrc_groups_.size(); ++j) {
36373657
const SrsSSRCGroup &ssrc_group = remote_media_desc.ssrc_groups_.at(j);

trunk/src/core/srs_core_version7.hpp

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

1010
#define VERSION_MAJOR 7
1111
#define VERSION_MINOR 0
12-
#define VERSION_REVISION 131
12+
#define VERSION_REVISION 132
1313

1414
#endif

trunk/src/utest/srs_utest_ai12.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,91 @@ VOID TEST(SrsRtcPublisherNegotiatorTest, LibdatachannelUseScenario)
21492149
EXPECT_EQ("video", video_sdp.media_descs_[0].type_);
21502150
}
21512151

2152+
// Test audio-only libdatachannel scenario WITHOUT SSRC info.
2153+
// This test demonstrates the bug where libdatachannel fails with:
2154+
// "Remote description has no ICE user fragment"
2155+
// Root cause: When the offer SDP has no a=ssrc: line, stream_desc->audio_track_desc_
2156+
// is never set, so generate_publish_local_sdp_for_audio() doesn't add the m=audio
2157+
// section to the answer SDP.
2158+
VOID TEST(SrsRtcPublisherNegotiatorTest, LibdatachannelAudioOnlyWithoutSsrc)
2159+
{
2160+
srs_error_t err;
2161+
2162+
// Create SrsRtcPublisherNegotiator
2163+
SrsUniquePtr<SrsRtcPublisherNegotiator> negotiator(new SrsRtcPublisherNegotiator());
2164+
2165+
// Create mock request for initialization
2166+
SrsUniquePtr<MockRtcConnectionRequest> mock_request(new MockRtcConnectionRequest("test.vhost", "live", "voice_stream"));
2167+
2168+
// Create mock RTC user config with remote SDP
2169+
SrsUniquePtr<SrsRtcUserConfig> ruc(new SrsRtcUserConfig());
2170+
ruc->req_ = mock_request->copy();
2171+
ruc->publish_ = true;
2172+
ruc->dtls_ = true;
2173+
ruc->srtp_ = true;
2174+
ruc->audio_before_video_ = true;
2175+
2176+
// Audio-only SDP from libdatachannel - NO SSRC LINE (this is the key difference!)
2177+
// This matches the actual user-reported SDP that causes the bug
2178+
ruc->remote_sdp_str_ =
2179+
"v=0\r\n"
2180+
"o=rtc 4107523824 0 IN IP4 127.0.0.1\r\n"
2181+
"s=-\r\n"
2182+
"t=0 0\r\n"
2183+
"a=group:BUNDLE audio\r\n"
2184+
"a=group:LS audio\r\n"
2185+
"a=msid-semantic:WMS *\r\n"
2186+
"a=ice-options:ice2,trickle\r\n"
2187+
"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"
2188+
"m=audio 36954 UDP/TLS/RTP/SAVPF 111\r\n"
2189+
"c=IN IP4 192.168.1.100\r\n"
2190+
"a=mid:audio\r\n"
2191+
"a=sendonly\r\n"
2192+
"a=rtcp-mux\r\n"
2193+
"a=rtpmap:111 opus/48000/2\r\n"
2194+
"a=fmtp:111 minptime=10;maxaveragebitrate=96000;stereo=1;sprop-stereo=1;useinbandfec=1\r\n"
2195+
"a=setup:actpass\r\n"
2196+
"a=ice-ufrag:rUic\r\n"
2197+
"a=ice-pwd:76ZWO/4FkRx6r2nMUF8yeH\r\n"
2198+
// NOTE: No a=ssrc: line here - this is the bug trigger!
2199+
"a=candidate:1 1 UDP 2114977791 192.168.1.100 36954 typ host\r\n"
2200+
"a=end-of-candidates\r\n";
2201+
2202+
// Parse the remote SDP
2203+
HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_));
2204+
2205+
// Verify only audio media description is present
2206+
EXPECT_EQ(1u, ruc->remote_sdp_.media_descs_.size());
2207+
EXPECT_EQ("audio", ruc->remote_sdp_.media_descs_[0].type_);
2208+
2209+
// Verify NO SSRC info in the parsed SDP (this is the bug condition)
2210+
EXPECT_TRUE(ruc->remote_sdp_.media_descs_[0].ssrc_infos_.empty());
2211+
2212+
// Create stream description for negotiation output
2213+
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
2214+
2215+
// Test negotiate_publish_capability - this should work but audio_track_desc_ will be NULL
2216+
HELPER_EXPECT_SUCCESS(negotiator->negotiate_publish_capability(ruc.get(), stream_desc.get()));
2217+
2218+
// BUG: audio_track_desc_ is NULL because there's no SSRC info in the offer
2219+
// This causes generate_publish_local_sdp_for_audio() to not add m=audio section
2220+
EXPECT_TRUE(stream_desc->audio_track_desc_ != NULL) << "BUG: audio_track_desc_ should not be NULL for audio-only SDP without SSRC";
2221+
EXPECT_TRUE(stream_desc->video_track_descs_.empty());
2222+
2223+
// Test generate_publish_local_sdp - create answer SDP
2224+
SrsSdp local_sdp;
2225+
HELPER_EXPECT_SUCCESS(negotiator->generate_publish_local_sdp(
2226+
ruc->req_, local_sdp, stream_desc.get(),
2227+
ruc->remote_sdp_.is_unified(), ruc->audio_before_video_));
2228+
2229+
// BUG: local_sdp.media_descs_ is empty because audio_track_desc_ was NULL
2230+
// This causes the answer SDP to have no m=audio line, which makes libdatachannel fail
2231+
EXPECT_EQ(1u, local_sdp.media_descs_.size()) << "BUG: Answer SDP should have m=audio section";
2232+
if (!local_sdp.media_descs_.empty()) {
2233+
EXPECT_EQ("audio", local_sdp.media_descs_[0].type_);
2234+
}
2235+
}
2236+
21522237
VOID TEST(SrsRtcConnectionTest, InitializeTypicalScenario)
21532238
{
21542239
srs_error_t err;

0 commit comments

Comments
 (0)