Skip to content

Commit 6e93dd7

Browse files
authored
AI: WebRTC: Support optional msid attribute per RFC 8830. v7.0.126 (#4570) (#4572)
Fix issue #4570 by supporting optional `msid` attribute in WebRTC SDP negotiation, enabling compatibility with libdatachannel and other clients that don't include msid information. SRS failed to negotiate WebRTC connections from libdatachannel clients because: - libdatachannel SDP lacks `a=ssrc:XX msid:stream_id track_id` attributes - SRS required msid information to create track descriptions - According to RFC 8830, the msid attribute and its appdata (track_id) are **optional** If diligently look at the SDP generated by libdatachannel: ``` a=ssrc:42 cname:video-send a=ssrc:43 cname:audio-send ``` It's deliberately missing the `a=ssrc:XX msid:stream_id track_id` line, comparing that with this one: ``` a=ssrc:42 cname:video-send a=ssrc:42 msid:stream_id video_track_id a=ssrc:43 cname:audio-send a=ssrc:43 msid:stream_id audio_track_id ``` In such a situation, to keep compatible with libdatachannel, if no msid line in sdp, SRS comprehensively and consistently uses: * app/stream as stream_id, such as live/livestream * type=video|audio, cname, and ssrc as track_id, such as track-video-video-send-43
1 parent 3f2539d commit 6e93dd7

File tree

11 files changed

+543
-41
lines changed

11 files changed

+543
-41
lines changed

.vscode/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ cmake --build $HOME/git/srs/trunk/cmake/build
4040
4141
## macOS: SRS UTest
4242

43+
The most straightforward way is to select a test name like `WorkflowRtcManuallyVerifyForPublisher`,
44+
then select `Debug gtest (macOS CodeLLDB)` and run the debug.
45+
46+
Or you can use the following way to run specified test from the test panel.
47+
4348
Install the following extensions:
4449

4550
- C++ TestMate

.vscode/launch.json

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5-
"name": "Launch SRS with conf/console.conf",
5+
"name": "Debug SRS with conf/console.conf",
66
"type": "cppdbg",
77
"request": "launch",
88
"program": "${workspaceFolder}/trunk/cmake/build/srs",
@@ -30,7 +30,7 @@
3030
}
3131
},
3232
{
33-
"name": "Launch SRS with conf/rtc.conf",
33+
"name": "Debug SRS with conf/rtc.conf",
3434
"type": "cppdbg",
3535
"request": "launch",
3636
"program": "${workspaceFolder}/trunk/cmake/build/srs",
@@ -58,40 +58,41 @@
5858
}
5959
},
6060
{
61-
"name": "Launch SRS with console.conf",
62-
"type": "cppdbg",
61+
"name": "Debug srs-proxy",
62+
"type": "go",
63+
"request": "launch",
64+
"mode": "auto",
65+
"cwd": "${workspaceFolder}/proxy",
66+
"program": "${workspaceFolder}/proxy"
67+
},
68+
{
69+
"name": "Debug SRS (macOS, CodeLLDB) console.conf",
70+
"type": "lldb",
6371
"request": "launch",
6472
"program": "${workspaceFolder}/trunk/cmake/build/srs",
6573
"args": ["-c", "console.conf"],
66-
"stopAtEntry": false,
6774
"cwd": "${workspaceFolder}/trunk",
68-
"environment": [],
69-
"externalConsole": false,
70-
"linux": {
71-
"MIMode": "gdb"
72-
},
73-
"osx": {
74-
"MIMode": "lldb"
75-
},
76-
"setupCommands": [
77-
{
78-
"description": "Enable pretty-printing for gdb",
79-
"text": "-enable-pretty-printing",
80-
"ignoreFailures": true
81-
}
75+
"stopOnEntry": false,
76+
"terminal": "integrated",
77+
"initCommands": [
78+
"command script import lldb.formatters.cpp.libcxx"
8279
],
8380
"preLaunchTask": "build",
84-
"logging": {
85-
"engineLogging": true
86-
}
81+
"env": {},
82+
"sourceLanguages": ["cpp"]
8783
},
8884
{
89-
"name": "Launch srs-proxy",
90-
"type": "go",
85+
"name": "Debug gtest (macOS CodeLLDB)",
86+
"type": "lldb",
9187
"request": "launch",
92-
"mode": "auto",
93-
"cwd": "${workspaceFolder}/proxy",
94-
"program": "${workspaceFolder}/proxy"
95-
}
88+
"program": "${workspaceFolder}/trunk/cmake/build/utest",
89+
"args": ["--gtest_filter=*${selectedText}*"],
90+
"cwd": "${workspaceFolder}/trunk",
91+
"terminal": "integrated",
92+
"initCommands": [
93+
"command script import lldb.formatters.cpp.libcxx"
94+
],
95+
"sourceLanguages": ["cpp"]
96+
}
9697
]
9798
}

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-11-11, AI: WebRTC: Support optional msid attribute per RFC 8830. v7.0.126 (#4570)
1011
* v7.0, 2025-11-11, AI: SRT: Stop TS parsing after codec detection. v7.0.125 (#4569)
1112
* v7.0, 2025-11-09, AI: WebRTC: Support G.711 (PCMU/PCMA) audio codec for WebRTC. v7.0.124 (#4075)
1213
* v7.0, 2025-11-08, AI: WebRTC: Support VP9 codec for WebRTC-to-WebRTC streaming. v7.0.123 (#4548)

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3601,12 +3601,25 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
36013601
for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) {
36023602
const SrsSSRCInfo &ssrc_info = remote_media_desc.ssrc_infos_.at(j);
36033603

3604+
// Generate msid because it's optional in sdp.
3605+
string msid_tracker = ssrc_info.msid_tracker_;
3606+
if (msid_tracker.empty()) {
3607+
msid_tracker = srs_fmt_sprintf("track-%s-%s-%d",
3608+
track_desc->type_.c_str(), ssrc_info.cname_.c_str(), ssrc_info.ssrc_);
3609+
}
3610+
3611+
// Generate msid because it's optional in sdp.
3612+
string msid = ssrc_info.msid_;
3613+
if (msid.empty()) {
3614+
msid = req->app_ + "/" + req->stream_;
3615+
}
3616+
36043617
// ssrc have same track id, will be description in the same track description.
3605-
if (track_id != ssrc_info.msid_tracker_) {
3618+
if (track_id != msid_tracker) {
36063619
SrsRtcTrackDescription *track_desc_copy = track_desc->copy();
36073620
track_desc_copy->ssrc_ = ssrc_info.ssrc_;
3608-
track_desc_copy->id_ = ssrc_info.msid_tracker_;
3609-
track_desc_copy->msid_ = ssrc_info.msid_;
3621+
track_desc_copy->id_ = msid_tracker;
3622+
track_desc_copy->msid_ = msid;
36103623

36113624
if (remote_media_desc.is_audio() && !stream_desc->audio_track_desc_) {
36123625
stream_desc->audio_track_desc_ = track_desc_copy;
@@ -3616,7 +3629,7 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
36163629
srs_freep(track_desc_copy);
36173630
}
36183631
}
3619-
track_id = ssrc_info.msid_tracker_;
3632+
track_id = msid_tracker;
36203633
}
36213634

36223635
// set track fec_ssrc and rtx_ssrc

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 125
12+
#define VERSION_REVISION 126
1313

1414
#endif

trunk/src/protocol/srs_protocol_sdp.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,10 @@ vector<SrsMediaPayloadType> SrsMediaDesc::find_media_with_encoding_name(const st
375375
transform(encoding_name.begin(), encoding_name.end(), upper_name.begin(), ::toupper);
376376

377377
for (size_t i = 0; i < payload_types_.size(); ++i) {
378-
if (payload_types_[i].encoding_name_ == std::string(lower_name.c_str()) ||
379-
payload_types_[i].encoding_name_ == std::string(upper_name.c_str())) {
380-
payloads.push_back(payload_types_[i]);
378+
SrsMediaPayloadType payload = payload_types_[i];
379+
if (payload.encoding_name_ == std::string(lower_name.c_str()) ||
380+
payload.encoding_name_ == std::string(upper_name.c_str())) {
381+
payloads.push_back(payload);
381382
}
382383
}
383384

trunk/src/utest/srs_utest_ai12.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,6 +2039,116 @@ VOID TEST(SrsRtcPublisherNegotiatorTest, TypicalUseScenario)
20392039
EXPECT_EQ("video", video_sdp.media_descs_[0].type_);
20402040
}
20412041

2042+
VOID TEST(SrsRtcPublisherNegotiatorTest, LibdatachannelUseScenario)
2043+
{
2044+
srs_error_t err;
2045+
2046+
// Create SrsRtcPublisherNegotiator
2047+
SrsUniquePtr<SrsRtcPublisherNegotiator> negotiator(new SrsRtcPublisherNegotiator());
2048+
2049+
// Create mock request for initialization
2050+
SrsUniquePtr<MockRtcConnectionRequest> mock_request(new MockRtcConnectionRequest("test.vhost", "live", "stream1"));
2051+
2052+
// Create mock RTC user config with remote SDP
2053+
SrsUniquePtr<SrsRtcUserConfig> ruc(new SrsRtcUserConfig());
2054+
ruc->req_ = mock_request->copy();
2055+
ruc->publish_ = true;
2056+
ruc->dtls_ = true;
2057+
ruc->srtp_ = true;
2058+
ruc->audio_before_video_ = true;
2059+
2060+
// SDP from issue 4570 - libdatachannel format with video first, then audio
2061+
ruc->remote_sdp_str_ =
2062+
"v=0\r\n"
2063+
"o=- rtc 4158491451 0 IN IP4 127.0.0.1\r\n"
2064+
"s=-\r\n"
2065+
"t=0 0\r\n"
2066+
"a=group:BUNDLE video audio\r\n"
2067+
"a=group:LS video audio\r\n"
2068+
"a=msid-semantic:WMS *\r\n"
2069+
"a=ice-options:ice2,trickle\r\n"
2070+
"a=fingerprint:sha-256 28:37:F7:18:77:FC:46:33:6F:B2:0F:12:83:C2:BF:5C:61:5E:96:EB:4B:B9:97:81:92:7C:82:10:97:B8:8E:60\r\n"
2071+
"m=video 56144 UDP/TLS/RTP/SAVPF 96 97\r\n"
2072+
"c=IN IP4 172.24.64.1\r\n"
2073+
"a=mid:video\r\n"
2074+
"a=sendonly\r\n"
2075+
"a=ssrc:42 cname:video-send\r\n"
2076+
"a=rtcp-mux\r\n"
2077+
"a=rtpmap:96 H264/90000\r\n"
2078+
"a=rtcp-fb:96 nack\r\n"
2079+
"a=rtcp-fb:96 nack pli\r\n"
2080+
"a=rtcp-fb:96 goog-remb\r\n"
2081+
"a=fmtp:96 profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1\r\n"
2082+
"a=rtpmap:97 RTX/90000\r\n"
2083+
"a=fmtp:97 apt=96\r\n"
2084+
"a=setup:actpass\r\n"
2085+
"a=ice-ufrag:fEw/\r\n"
2086+
"a=ice-pwd:jBua8YGWQKc/Vn6Y9EZ9+0\r\n"
2087+
"a=candidate:1 1 UDP 2122317823 172.24.64.1 56144 typ host\r\n"
2088+
"a=candidate:2 1 UDP 2122315767 10.0.0.94 56144 typ host\r\n"
2089+
"a=candidate:3 1 UDP 1686189695 111.43.134.137 56144 typ srflx raddr 0.0.0.0 rport 0\r\n"
2090+
"a=end-of-candidates\r\n"
2091+
"m=audio 56144 UDP/TLS/RTP/SAVPF 111\r\n"
2092+
"c=IN IP4 172.24.64.1\r\n"
2093+
"a=mid:audio\r\n"
2094+
"a=sendonly\r\n"
2095+
"a=ssrc:43 cname:audio-send\r\n"
2096+
"a=rtcp-mux\r\n"
2097+
"a=rtpmap:111 opus/48000/2\r\n"
2098+
"a=fmtp:111 minptime=10;maxaveragebitrate=98000;stereo=1;sprop-stereo=1;useinbandfec=1\r\n"
2099+
"a=setup:actpass\r\n"
2100+
"a=ice-ufrag:fEw/\r\n"
2101+
"a=ice-pwd:jBua8YGWQKc/Vn6Y9EZ9+0\r\n";
2102+
2103+
// Parse the remote SDP
2104+
HELPER_EXPECT_SUCCESS(ruc->remote_sdp_.parse(ruc->remote_sdp_str_));
2105+
2106+
// Create stream description for negotiation output
2107+
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
2108+
2109+
// Test negotiate_publish_capability - typical WebRTC publisher negotiation
2110+
HELPER_EXPECT_SUCCESS(negotiator->negotiate_publish_capability(ruc.get(), stream_desc.get()));
2111+
2112+
// Verify that stream description was populated with audio and video tracks
2113+
EXPECT_TRUE(stream_desc->audio_track_desc_ != NULL);
2114+
EXPECT_FALSE(stream_desc->video_track_descs_.empty());
2115+
EXPECT_EQ("audio", stream_desc->audio_track_desc_->type_);
2116+
EXPECT_EQ("video", stream_desc->video_track_descs_[0]->type_);
2117+
2118+
// Test generate_publish_local_sdp - create answer SDP
2119+
SrsSdp local_sdp;
2120+
HELPER_EXPECT_SUCCESS(negotiator->generate_publish_local_sdp(
2121+
ruc->req_, local_sdp, stream_desc.get(),
2122+
ruc->remote_sdp_.is_unified(), ruc->audio_before_video_));
2123+
2124+
// Verify that local SDP was generated with media descriptions
2125+
EXPECT_FALSE(local_sdp.media_descs_.empty());
2126+
2127+
// Find audio and video media descriptions
2128+
bool has_audio = false, has_video = false;
2129+
for (size_t i = 0; i < local_sdp.media_descs_.size(); i++) {
2130+
if (local_sdp.media_descs_[i].type_ == "audio")
2131+
has_audio = true;
2132+
if (local_sdp.media_descs_[i].type_ == "video")
2133+
has_video = true;
2134+
}
2135+
EXPECT_TRUE(has_audio);
2136+
EXPECT_TRUE(has_video);
2137+
2138+
// Test individual SDP generation methods
2139+
SrsSdp audio_sdp, video_sdp;
2140+
HELPER_EXPECT_SUCCESS(negotiator->generate_publish_local_sdp_for_audio(audio_sdp, stream_desc.get()));
2141+
HELPER_EXPECT_SUCCESS(negotiator->generate_publish_local_sdp_for_video(video_sdp, stream_desc.get(), true));
2142+
2143+
// Verify audio SDP generation
2144+
EXPECT_FALSE(audio_sdp.media_descs_.empty());
2145+
EXPECT_EQ("audio", audio_sdp.media_descs_[0].type_);
2146+
2147+
// Verify video SDP generation
2148+
EXPECT_FALSE(video_sdp.media_descs_.empty());
2149+
EXPECT_EQ("video", video_sdp.media_descs_[0].type_);
2150+
}
2151+
20422152
VOID TEST(SrsRtcConnectionTest, InitializeTypicalScenario)
20432153
{
20442154
srs_error_t err;

0 commit comments

Comments
 (0)