1. 预置原理
webrtc在未进行明确指定时,根据sdp信息中的payloadtype进行协商,谁在前面就协商到谁。如下所示从96开始都为payloadtype。
m=video 9 UDP/TLS/RTP/SAVPF 96 97 39 40 98 99 127 103 104 105 106 107 108
1.1 sdp范例
v=0
o=- 1706948182897011195 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 102 0 8 13 110 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Lp3d
a=ice-pwd:RrQTQJ2kg/Hqc6YJrd7JS40y
a=ice-options:trickle renomination
a=fingerprint:sha-256 3A:7C:F6:79:87:B1:FC:B7:B0:A7:7B:A1:62:03:3E:87:2D:96:6F:5E:C5:34:21:CE:45:30:EF:54:82:0A:C0:27
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:- 101
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:126 telephone-event/8000
a=ssrc:3890393808 cname:Tmf1PhzntBW13sKk
a=ssrc:3890393808 msid:- 101
m=video 9 UDP/TLS/RTP/SAVPF 96 97 39 40 98 99 127 103 104 105 106 107 108
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Lp3d
a=ice-pwd:RrQTQJ2kg/Hqc6YJrd7JS40y
a=ice-options:trickle renomination
a=fingerprint:sha-256 3A:7C:F6:79:87:B1:FC:B7:B0:A7:7B:A1:62:03:3E:87:2D:96:6F:5E:C5:34:21:CE:45:30:EF:54:82:0A:C0:27
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:- 102
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:39 AV1/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:103 rtx/90000
a=fmtp:103 apt=127
a=rtpmap:104 H265/90000
a=rtcp-fb:104 goog-remb
a=rtcp-fb:104 transport-cc
a=rtcp-fb:104 ccm fir
a=rtcp-fb:104 nack
a=rtcp-fb:104 nack pli
a=rtpmap:105 rtx/90000
a=fmtp:105 apt=104
a=rtpmap:106 red/90000
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=106
a=rtpmap:108 ulpfec/90000
a=ssrc-group:FID 473779701 3685598583
a=ssrc:473779701 cname:Tmf1PhzntBW13sKk
a=ssrc:473779701 msid:- 102
a=ssrc:3685598583 cname:Tmf1PhzntBW13sKk
a=ssrc:3685598583 msid:- 102
1.2 Type与编解码器对应关系
payloadType | 编码器名称 |
---|---|
96 | VP8 |
97 | rtx |
39 | AV1 |
40 | rtx |
98 | VP9 |
99 | rtx |
127 | H264 |
103 | rtx |
104 | H265 |
105 | rtx |
106 | red |
107 | rtx |
108 | rtx |
2. 修改代码
2.1 python
def findMediaDescriptionLine(isAudio:bool,sdpLines):
mediaDescription = "m=audio " if isAudio else "m=video "
for i in range(len(sdpLines)):
if sdpLines[i].startswith(mediaDescription):
return i
return -1
def movePayloadTypesToFront(preferredPayloadTypes,mLine):
origLineParts = mLine.split(" ")
if len(origLineParts)<3:
return None
header = origLineParts[0:3]
unpreferredPayloadTypes=origLineParts[3:]
unpreferredPayloadTypes = [n for i, n in enumerate(unpreferredPayloadTypes) if n not in preferredPayloadTypes]
newLineParts=[]
newLineParts.extend(header)
newLineParts.extend(preferredPayloadTypes)
newLineParts.extend(unpreferredPayloadTypes)
return " ".join(newLineParts)
def preferCodec(sdp:str,codec:str,isAudio:bool):
lines = sdp.split("\r\n")
mLineIndex = findMediaDescriptionLine(isAudio, lines);
if mLineIndex == -1:
print(f"No mediaDescription line, so can't prefer {codec}")
return sdp
codecPayloadTypes=[]
for line in lines:
m = re.search("^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$",line)
if m:
codecPayloadTypes.append(m.group(1))
if len(codecPayloadTypes)==0:
return sdp
newMLine = movePayloadTypesToFront(codecPayloadTypes, lines[mLineIndex])
if not newMLine:
return sdp
print(f"newMLine:{newMLine} mLineIndex:{mLineIndex} len:{len(lines)}")
lines[mLineIndex] = newMLine
return "\r\n".join(lines)
# offer为sdp数据
print(preferCodec(offer,"H265",False))
2.2 JAVA
摘取自webrtc的官方demo
/** Returns the line number containing "m=audio|video", or -1 if no such line exists. */
private static int findMediaDescriptionLine(boolean isAudio, String[] sdpLines) {
final String mediaDescription = isAudio ? "m=audio " : "m=video ";
for (int i = 0; i < sdpLines.length; ++i) {
if (sdpLines[i].startsWith(mediaDescription)) {
return i;
}
}
return -1;
}
private static String joinString(
Iterable<? extends CharSequence> s, String delimiter, boolean delimiterAtEnd) {
Iterator<? extends CharSequence> iter = s.iterator();
if (!iter.hasNext()) {
return "";
}
StringBuilder buffer = new StringBuilder(iter.next());
while (iter.hasNext()) {
buffer.append(delimiter).append(iter.next());
}
if (delimiterAtEnd) {
buffer.append(delimiter);
}
return buffer.toString();
}
private static @Nullable String movePayloadTypesToFront(
List<String> preferredPayloadTypes, String mLine) {
// The format of the media description line should be: m=<media> <port> <proto> <fmt> ...
final List<String> origLineParts = Arrays.asList(mLine.split(" "));
if (origLineParts.size() <= 3) {
Log.e(TAG, "Wrong SDP media description format: " + mLine);
return null;
}
final List<String> header = origLineParts.subList(0, 3);
final List<String> unpreferredPayloadTypes =
new ArrayList<>(origLineParts.subList(3, origLineParts.size()));
unpreferredPayloadTypes.removeAll(preferredPayloadTypes);
// Reconstruct the line with `preferredPayloadTypes` moved to the beginning of the payload
// types.
final List<String> newLineParts = new ArrayList<>();
newLineParts.addAll(header);
newLineParts.addAll(preferredPayloadTypes);
newLineParts.addAll(unpreferredPayloadTypes);
return joinString(newLineParts, " ", false /* delimiterAtEnd */);
}
private static String preferCodec(String sdp, String codec, boolean isAudio) {
final String[] lines = sdp.split("\r\n");
final int mLineIndex = findMediaDescriptionLine(isAudio, lines);
if (mLineIndex == -1) {
Log.w(TAG, "No mediaDescription line, so can't prefer " + codec);
return sdp;
}
// A list with all the payload types with name `codec`. The payload types are integers in the
// range 96-127, but they are stored as strings here.
final List<String> codecPayloadTypes = new ArrayList<>();
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
final Pattern codecPattern = Pattern.compile("^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$");
for (String line : lines) {
Matcher codecMatcher = codecPattern.matcher(line);
if (codecMatcher.matches()) {
codecPayloadTypes.add(codecMatcher.group(1));
}
}
if (codecPayloadTypes.isEmpty()) {
Log.w(TAG, "No payload types with name " + codec);
return sdp;
}
final String newMLine = movePayloadTypesToFront(codecPayloadTypes, lines[mLineIndex]);
if (newMLine == null) {
return sdp;
}
Log.d(TAG, "Change media description from: " + lines[mLineIndex] + " to " + newMLine);
lines[mLineIndex] = newMLine;
return joinString(Arrays.asList(lines), "\r\n", true /* delimiterAtEnd */);
}