Skip to content

Commit fcd6009

Browse files
committed
add sdp manipulator
1 parent 6f7ff20 commit fcd6009

File tree

9 files changed

+412
-14
lines changed

9 files changed

+412
-14
lines changed

.github/workflows/ci.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
include:
22-
- cmd: "./gradlew :kmp-webrtc:testDebugUnitTest"
23-
os: macos-latest
24-
- cmd: "./gradlew :example:androidApp:assembleDebug"
25-
os: macos-latest
26-
- cmd: "./scripts/build_ios_demo.sh"
27-
os: macos-latest
22+
- os: macos-latest
23+
cmd: "./gradlew :kmp-webrtc:testDebugUnitTest"
24+
- os: macos-latest
25+
cmd: "./gradlew :example:androidApp:assembleDebug"
26+
- os: macos-latest
2827
dep: "brew update && brew install cocoapods xcodegen"
29-
- cmd: "./scripts/build_mac_demo.sh"
30-
os: macos-latest
28+
cmd: "./scripts/build_ios_demo.sh"
29+
- os: macos-latest
3130
dep: "brew update && brew install cocoapods xcodegen"
32-
- cmd: ".\\scripts\\setup_windows.bat \"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.43.34808\\bin\\Hostx64\\x64\\lib.exe\" && cd example\\winApp && msbuild winApp.vcxproj /t:Build /p:Configuration=Release /p:Platform=x64"
33-
os: windows-latest
34-
- cmd: "./gradlew :example:webApp:jsBrowserDistribution"
35-
os: macos-latest
31+
cmd: "./scripts/build_mac_demo.sh"
32+
- os: windows-latest
33+
cmd: ".\\scripts\\setup_windows.bat \"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.43.34808\\bin\\Hostx64\\x64\\lib.exe\" && cd example\\winApp && msbuild winApp.vcxproj /t:Build /p:Configuration=Release /p:Platform=x64"
34+
- os: macos-latest
35+
cmd: "./gradlew :example:webApp:jsBrowserDistribution"
3636
runs-on: ${{ matrix.os }}
3737
permissions:
3838
pull-requests: write

kmp-webrtc/src/commonMain/kotlin/com/piasy/kmp/webrtc/PeerConnectionClientFactory.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ abstract class PeerConnectionClientFactory(
7575
const val DIR_RECV_ONLY = 2
7676
const val DIR_INACTIVE = 3
7777

78+
const val VIDEO_CODEC_VP8 = 1
79+
const val VIDEO_CODEC_VP9 = 2
80+
const val VIDEO_CODEC_H264_BASELINE = 3
81+
const val VIDEO_CODEC_H264_HIGH_PROFILE = 4
82+
const val VIDEO_CODEC_H265 = 5
83+
const val VIDEO_CODEC_AV1 = 6
84+
7885
const val VIDEO_CAPTURE_IMPL_SYSTEM_CAMERA = 1
7986
const val VIDEO_CAPTURE_IMPL_SCREEN = 2
8087
const val VIDEO_CAPTURE_IMPL_FILE = 3
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package com.piasy.kmp.webrtc.utils
2+
3+
import com.piasy.kmp.webrtc.PeerConnectionClientFactory
4+
5+
/**
6+
* Created by Piasy{github.com/Piasy} on 2019-11-29.
7+
*/
8+
object SdpManipulator {
9+
private val DISABLE_CVO_FILTER: (String) -> Boolean = {
10+
!it.contains("urn:3gpp:video-orientation")
11+
}
12+
13+
fun preferCodecs(
14+
sdp: String,
15+
codecTypes: List<Int>,
16+
video: Boolean
17+
): String {
18+
val hasAv1x = sdp.contains("AV1X/")
19+
val av1CodecName = if (hasAv1x) "AV1X" else "AV1"
20+
21+
val preferredCodecs = LinkedHashSet<String>()
22+
for (codec in codecTypes) {
23+
preferredCodecs.add(codecName(codec, av1CodecName))
24+
}
25+
preferredCodecs.add("red")
26+
preferredCodecs.add("ulpfec")
27+
preferredCodecs.add("flexfec-03")
28+
29+
return preferCodec(sdp, preferredCodecs, video)
30+
}
31+
32+
fun disableCVO(sdp: String): String {
33+
val lines = sdp.split("(\r\n|\n)".toRegex())
34+
.dropLastWhile { it.isEmpty() }
35+
.filter(DISABLE_CVO_FILTER)
36+
return joinString(lines.filter(DISABLE_CVO_FILTER), "\r\n", true)
37+
}
38+
39+
private fun codecName(codec: Int, av1CodecName: String = "AV1") = when (codec) {
40+
PeerConnectionClientFactory.VIDEO_CODEC_VP8 -> "VP8"
41+
PeerConnectionClientFactory.VIDEO_CODEC_VP9 -> "VP9"
42+
PeerConnectionClientFactory.VIDEO_CODEC_H264_BASELINE, PeerConnectionClientFactory.VIDEO_CODEC_H264_HIGH_PROFILE -> "H264"
43+
PeerConnectionClientFactory.VIDEO_CODEC_H265 -> "H265"
44+
PeerConnectionClientFactory.VIDEO_CODEC_AV1 -> av1CodecName
45+
else -> "UNKNOWN"
46+
}
47+
48+
private fun preferCodec(
49+
originalSdp: String,
50+
preferredCodecs: LinkedHashSet<String>,
51+
video: Boolean
52+
): String {
53+
val lines = originalSdp.split("(\r\n|\n)".toRegex())
54+
.dropLastWhile { it.isEmpty() }
55+
val newLines = ArrayList<String>()
56+
57+
var audioMLineIndex = -1
58+
var videoMLineIndex = -1
59+
var processingAudioSection = false
60+
var processingVideoSection = false
61+
// <codecName, payloadType>
62+
val preferredPayloadTypes = HashMap<String, ArrayList<String>>()
63+
for (i in lines.indices) {
64+
val line = lines[i]
65+
// we only check it for rtpmap/rtcp-fb/fmtp, and they only exist in audio/video section.
66+
val processingRightSection =
67+
(video && processingVideoSection) || (!video && processingAudioSection)
68+
if (line.startsWith("a=rtpmap:")) {
69+
val payloadType = line.split(" ")[0].split(":")[1]
70+
val codecName = line.split(" ")[1].split("/")[0]
71+
// match our preferred codec?
72+
val codecPreferred = preferredCodecs.contains(codecName)
73+
// is rtx for our preferred codec?
74+
val rtxPreferred = codecName == "rtx"
75+
&& containsValue(preferredPayloadTypes, lines[i + 1].split("apt=")[1])
76+
if (codecPreferred || rtxPreferred) {
77+
putEntry(preferredPayloadTypes, codecName, payloadType)
78+
} else if (processingRightSection) {
79+
continue
80+
}
81+
} else if (line.startsWith("a=rtcp-fb:") || line.startsWith("a=fmtp:")) {
82+
val payloadType = line.split(" ")[0].split(":")[1]
83+
if (processingRightSection && !containsValue(preferredPayloadTypes, payloadType)) {
84+
continue
85+
}
86+
} else if (line.startsWith("m=audio")) {
87+
audioMLineIndex = newLines.size
88+
processingAudioSection = true
89+
processingVideoSection = false
90+
} else if (line.startsWith("m=video")) {
91+
videoMLineIndex = newLines.size
92+
processingAudioSection = false
93+
processingVideoSection = true
94+
}
95+
newLines.add(line)
96+
}
97+
98+
if (!video && audioMLineIndex != -1) {
99+
newLines[audioMLineIndex] = changeMLine(
100+
newLines[audioMLineIndex],
101+
preferredCodecs,
102+
preferredPayloadTypes
103+
)
104+
}
105+
if (video && videoMLineIndex != -1) {
106+
newLines[videoMLineIndex] = changeMLine(
107+
newLines[videoMLineIndex],
108+
preferredCodecs,
109+
preferredPayloadTypes
110+
)
111+
}
112+
return joinString(newLines.filter(DISABLE_CVO_FILTER), "\r\n", true)
113+
}
114+
115+
private fun containsValue(
116+
payloadTypes: HashMap<String, ArrayList<String>>,
117+
value: String
118+
): Boolean {
119+
for (v in payloadTypes.values) {
120+
for (s in v) {
121+
if (s == value) {
122+
return true
123+
}
124+
}
125+
}
126+
return false
127+
}
128+
129+
private fun putEntry(
130+
payloadTypes: HashMap<String, ArrayList<String>>,
131+
key: String,
132+
value: String
133+
) {
134+
var payload = payloadTypes[key]
135+
if (payload == null) {
136+
payload = ArrayList()
137+
payloadTypes[key] = payload
138+
}
139+
payload.add(value)
140+
}
141+
142+
private fun changeMLine(
143+
mLine: String,
144+
preferredCodecs: LinkedHashSet<String>,
145+
preferredPayloadTypes: HashMap<String, ArrayList<String>>
146+
): String {
147+
val oldMLineParts = mLine.split(" ")
148+
val mLineHeader = oldMLineParts.subList(0, 3)
149+
150+
val newMLineParts = ArrayList(mLineHeader)
151+
for (preferredCodec in preferredCodecs) {
152+
val payload = preferredPayloadTypes[preferredCodec]
153+
if (payload != null) {
154+
newMLineParts.addAll(payload)
155+
}
156+
}
157+
val rtxPayload = preferredPayloadTypes["rtx"]
158+
if (rtxPayload != null) {
159+
newMLineParts.addAll(rtxPayload)
160+
}
161+
return joinString(newMLineParts, " ", false)
162+
}
163+
164+
private fun joinString(
165+
strings: List<String>,
166+
delimiter: String,
167+
delimiterAtEnd: Boolean
168+
): String {
169+
val iterator = strings.iterator()
170+
if (!iterator.hasNext()) {
171+
return ""
172+
}
173+
val builder = StringBuilder(iterator.next())
174+
while (iterator.hasNext()) {
175+
builder.append(delimiter)
176+
.append(iterator.next())
177+
}
178+
if (delimiterAtEnd) {
179+
builder.append(delimiter)
180+
}
181+
return builder.toString()
182+
}
183+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.piasy.kmp.webrtc.utils
2+
3+
import com.piasy.kmp.webrtc.PeerConnectionClientFactory
4+
import kotlin.test.Test
5+
import kotlin.test.assertTrue
6+
7+
/**
8+
* Created by Piasy{github.com/Piasy} on 2022/3/12.
9+
*/
10+
internal class SdpManipulatorTest {
11+
12+
@Test
13+
fun preferCodecs() {
14+
val orgSdp =
15+
"v=0\r\no=- 3115672123855550998 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:iNP9\r\na=ice-pwd:SS8UJE9t4rUv33Yh7/fuEILj\r\na=ice-options:trickle\r\na=fingerprint:sha-256 10:E4:9F:9D:10:50:BA:5B:C4:7A:C6:F2:70:CC:AF:5B:19:04:9F:D1:A7:F1:8B:B4:0B:61:00:FE:D4:6D:1C:2E\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:112 telephone-event/32000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 122 102 121 127 120 125 107 108 109 124 119 123 117 35 36 114 115 116 62 118 37\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:iNP9\r\na=ice-pwd:SS8UJE9t4rUv33Yh7/fuEILj\r\na=ice-options:trickle\r\na=fingerprint:sha-256 10:E4:9F:9D:10:50:BA:5B:C4:7A:C6:F2:70:CC:AF:5B:19:04:9F:D1:A7:F1:8B:B4:0B:61:00:FE:D4:6D:1C:2E\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 profile-id=2\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:122 VP9/90000\r\na=rtcp-fb:122 goog-remb\r\na=rtcp-fb:122 transport-cc\r\na=rtcp-fb:122 ccm fir\r\na=rtcp-fb:122 nack\r\na=rtcp-fb:122 nack pli\r\na=fmtp:122 profile-id=1\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 transport-cc\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:121 rtx/90000\r\na=fmtp:121 apt=102\r\na=rtpmap:127 H264/90000\r\na=rtcp-fb:127 goog-remb\r\na=rtcp-fb:127 transport-cc\r\na=rtcp-fb:127 ccm fir\r\na=rtcp-fb:127 nack\r\na=rtcp-fb:127 nack pli\r\na=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:120 rtx/90000\r\na=fmtp:120 apt=127\r\na=rtpmap:125 H264/90000\r\na=rtcp-fb:125 goog-remb\r\na=rtcp-fb:125 transport-cc\r\na=rtcp-fb:125 ccm fir\r\na=rtcp-fb:125 nack\r\na=rtcp-fb:125 nack pli\r\na=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:107 rtx/90000\r\na=fmtp:107 apt=125\r\na=rtpmap:108 H264/90000\r\na=rtcp-fb:108 goog-remb\r\na=rtcp-fb:108 transport-cc\r\na=rtcp-fb:108 ccm fir\r\na=rtcp-fb:108 nack\r\na=rtcp-fb:108 nack pli\r\na=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\na=rtpmap:109 rtx/90000\r\na=fmtp:109 apt=108\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 transport-cc\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:119 rtx/90000\r\na=fmtp:119 apt=124\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 transport-cc\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f\r\na=rtpmap:117 rtx/90000\r\na=fmtp:117 apt=123\r\na=rtpmap:35 AV1/90000\r\na=rtcp-fb:35 goog-remb\r\na=rtcp-fb:35 transport-cc\r\na=rtcp-fb:35 ccm fir\r\na=rtcp-fb:35 nack\r\na=rtcp-fb:35 nack pli\r\na=rtpmap:36 rtx/90000\r\na=fmtp:36 apt=35\r\na=rtpmap:114 H264/90000\r\na=rtcp-fb:114 goog-remb\r\na=rtcp-fb:114 transport-cc\r\na=rtcp-fb:114 ccm fir\r\na=rtcp-fb:114 nack\r\na=rtcp-fb:114 nack pli\r\na=fmtp:114 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032\r\na=rtpmap:115 rtx/90000\r\na=fmtp:115 apt=114\r\na=rtpmap:116 red/90000\r\na=rtpmap:62 rtx/90000\r\na=fmtp:62 apt=116\r\na=rtpmap:118 ulpfec/90000\r\na=rtpmap:37 flexfec-03/90000\r\na=rtcp-fb:37 goog-remb\r\na=rtcp-fb:37 transport-cc\r\na=fmtp:37 repair-window=10000000"
16+
val refinedSdp =
17+
SdpManipulator.preferCodecs(orgSdp, listOf(PeerConnectionClientFactory.VIDEO_CODEC_H264_BASELINE), true)
18+
19+
val lines = refinedSdp.split("(\r\n|\n)".toRegex())
20+
.dropLastWhile { it.isEmpty() }
21+
var videoMLine = ""
22+
for (line in lines) {
23+
if (line.contains("m=video")) {
24+
videoMLine = line
25+
break
26+
}
27+
}
28+
if (videoMLine == "") {
29+
assertTrue(false)
30+
} else {
31+
assertTrue(videoMLine.contains("37"))
32+
}
33+
}
34+
}

kmp-webrtc/src/cppCommon/kotlin/com/piasy/kmp/webrtc/utils/CppUtils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.piasy.kmp.webrtc.utils
22

33
import com.piasy.kmp.webrtc.CppPeerConnectionClient
44
import com.piasy.kmp.webrtc.PeerConnectionClientCallback
5+
import com.piasy.kmp.webrtc.PeerConnectionClientFactory
56
import com.piasy.kmp.webrtc.data.IceCandidate
67
import com.piasy.kmp.webrtc.data.IceServer
78
import com.piasy.kmp.webrtc.data.RtcStatsReport
@@ -26,6 +27,10 @@ fun logInfo(log: String) {
2627
Logging.info("CppUtils", log)
2728
}
2829

30+
fun preferCodec(sdp: String, codec: Int): String {
31+
return SdpManipulator.preferCodecs(sdp, listOf(codec), true)
32+
}
33+
2934
fun emptyIceServers() = emptyList<IceServer>()
3035

3136
private class CppPcClientCallback(private val callback: WebRTC.PCClientCallback, private val opaque: COpaquePointer?) :

scripts/build_ios_demo.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
./scripts/setup_apple.sh
5+
./scripts/setup_apple_demo.sh
66

77
pushd example/iosApp
88
xcodebuild -workspace iosApp.xcworkspace \

scripts/build_mac_demo.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
./scripts/setup_apple.sh
5+
./scripts/setup_apple_demo.sh
66

77
pushd example/macApp
88
xcodebuild -workspace macApp.xcworkspace \

0 commit comments

Comments
 (0)