御风灰灰
发布于 2024-07-11 / 11 阅读
0
0

webrtc预置编码器

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 */);
  }

评论