Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions trunk/doc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions trunk/src/app/srs_app_rtc_conn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion trunk/src/core/srs_core_version7.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 131
#define VERSION_REVISION 132

#endif
85 changes: 85 additions & 0 deletions trunk/src/utest/srs_utest_ai12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading