WebRTC 是一套基于 Web 的实时通信解决方案,通过浏览器内置的 API 来支持音视频通道的搭建。

简而言之,先在信令通道协商出彼此的媒体和通信参数, 再通过媒体通道来传输音视频媒体数据。

JavaScrpt 中用到的三个主要的对象有:

  • MediaStream 获取和渲染音频和视频流
  • RTCPeerConnection 支持音频和视频媒体数据通信
  • RTCDataChannel 支持应用级的数据通信

对于媒体传输层,WebRTC 规定了用 ICE/STUN/TURN 来连通,用 DTLS 来协商 SRTP 密钥,用 SRTP 来传输媒体数据, 用 SCTP 来传输应用数据。

而在信令层,WebRTC 并未指定,各个应用可以用自己喜欢的信令协议来进行媒体协商,一般都是用 SDP 来通过 HTTP, WebSocket 或 SIP 协议承载具体的媒体会话描述。

如果我们要进行视频聊天, 最基本的呼叫流程大致如下:

WebRTC flow

  1. 收集本地的媒体源(麦克风,摄像头)作为 MediaStream 媒体流
  2. 两个对端彼此创建信令通道,交换会话描述信息 SDP
  3. 通过信令通过来交换彼此的会话描述信息 SDP
  4. 通过 ICE/STUN/TURN 协议,协商出可连通的 Candidate Pair(候选者对) 来创建 PeerConnection
  5. PeerConnection 创建好后,通过SRTP来封装音视频数据进行传输

简单来说通信的双方需要了解两块信息

  1. ICE 候选者 ICE Candidates:包括可用来通信的地址信息
  2. 会话描述信息 Session Description: 包括媒体种类,编码,格式等等。

ICE

ICE的全称是" Interactive Connectivity Establishment " 即交互式连接的建立: 一个用于网络地址转换穿越的协议

通过一个例子来说明整个流程

  1. Local Peer Connection
  • https://github.com/walterfan/webrtc_primer/blob/main/examples/local_peer_connection.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title> WebRTC Examples</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.4.0/adapter.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js" ></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.0/bootbox.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>
<script type="text/javascript" src="js/util.js" ></script>
<script>$(function() {$(".navbar-static-top").load("navbar.html", function() {$(".navbar-static-top li.dropdown").addClass("active");$(".navbar-static-top a[href='local_peer_connection.html']").parent().addClass("active");});$(".footer").load("footer.html");//-----------------------------------------------------------//});
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.4.0/cerulean/bootstrap.min.css" type="text/css"/>
<link rel="stylesheet" href="css/main.css" type="text/css"/>
<link rel="stylesheet" href="css/demo.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css"/>
</head>
<body><!-- <a href="https://github.com/walterfan/webrtc-primer"><img style="position: absolute; top: 0; left: 0; border: 0; z-index: 1001;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_darkblue_121621.png" alt="Fork me on GitHub"></a>-->
<nav class="navbar navbar-default navbar-static-top">
</nav><div class="container"><div class="row"><div class="col-lg-12"><div class="page-header"><h1>WebRTC example of Peer Connection </h1></div><div class="container" id="details"><div class="row"><div class="col-lg-12"><p>Click the button to open or close connection</p><div><button class="btn btn-default" autocomplete="off" id="startButton">Start Video</button><button class="btn btn-default" autocomplete="off" id="stopButton">Stop Video</button><button class="btn btn-default" autocomplete="off" id="callButton">Call</button><button class="btn btn-default" autocomplete="off" id="hangupButton">Hangup</button></div><br/></div><div class="col-lg-12"><div class="col-lg-6"><video id="localVideo" autoplay></video></div><div class="col-lg-6"><video id="remoteVideo" autoplay></video></div></div><div><div class="box"><span>SDP Semantics:</span><select id="sdpSemantics"><option selected value="">Default</option><option value="unified-plan">Unified Plan</option><option value="plan-b">Plan B</option></select></div><div><button class="btn btn-default" autocomplete="off" id="sdpButton">Display SDP</button><textarea id="output"></textarea><code><pre>interface RTCOfferAnswerOptions {voiceActivityDetection?: boolean;}interface RTCOfferOptions extends RTCOfferAnswerOptions {iceRestart?: boolean;offerToReceiveAudio?: boolean;offerToReceiveVideo?: boolean;}</pre></code></div></div></div><div class="note"><div id="logDiv"><ul id="logContent"></ul></div></div></div></div></div><hr><div class="footer"></div>
</div>
<script type="text/javascript" src="js/local_peer_connection_demo.js"></script>
</body>
</html>
  • https://github.com/walterfan/webrtc_primer/blob/main/examples/js/local_peer_connection_demo.js
'use strict';const startButton = document.getElementById('startButton');
const stopButton = document.getElementById('stopButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');const sdpButton = document.getElementById('sdpButton');
const outputTextarea = document.querySelector('textarea#output');stopButton.disabled = true;
callButton.disabled = true;
hangupButton.disabled = true;startButton.addEventListener('click', start);
stopButton.addEventListener('click', stop);
callButton.addEventListener('click', call);
hangupButton.addEventListener('click', hangup);sdpButton.addEventListener('click', displaySdp);let startTime;
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo')localVideo.addEventListener('loadedmetadata', function() {console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);
});remoteVideo.addEventListener('loadedmetadata', function() {console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);
});remoteVideo.addEventListener('resize', () => {console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);// We'll use the first onsize callback as an indication that video has started// playing out.if (startTime) {const elapsedTime = window.performance.now() - startTime;console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');startTime = null;}
});let localStream;
let pc1;
let pc2;const offerOptions = {offerToReceiveAudio: 1,offerToReceiveVideo: 1,iceRestart:true,voiceActivityDetection: true
};function getName(pc) {return (pc === pc1) ? 'pc1' : 'pc2';
}function getOtherPc(pc) {return (pc === pc1) ? pc2 : pc1;
}//start the video streamasync function start() {console.log('Requesting local stream');startButton.disabled = true;stopButton.disabled = false;try {const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});weblog('Received local stream');localVideo.srcObject = stream;localStream = stream;callButton.disabled = false;} catch (e) {alert(`getUserMedia() error: ${e.name}`);}
}function stop(e) {const stream = localVideo.srcObject;const tracks = stream.getTracks();e.target.disabled = true;startButton.disabled = false;callButton.disabled = true;tracks.forEach(function(track) {track.stop();});localVideo.srcObject = null;}function getSelectedSdpSemantics() {const sdpSemanticsSelect = document.querySelector('#sdpSemantics');const option = sdpSemanticsSelect.options[sdpSemanticsSelect.selectedIndex];return option.value === '' ? {} : {sdpSemantics: option.value};}//call the remote peerasync function call() {callButton.disabled = true;hangupButton.disabled = false;weblog('Starting call');startTime = window.performance.now();const videoTracks = localStream.getVideoTracks();const audioTracks = localStream.getAudioTracks();if (videoTracks.length > 0) {weblog(`Using video device: ${videoTracks[0].label}`);}if (audioTracks.length > 0) {weblog(`Using audio device: ${audioTracks[0].label}`);}const configuration = getSelectedSdpSemantics();weblog('RTCPeerConnection configuration:', configuration);pc1 = new RTCPeerConnection(configuration);weblog('Created local peer connection object pc1');pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));pc2 = new RTCPeerConnection(configuration);weblog('Created remote peer connection object pc2');pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));pc2.addEventListener('track', gotRemoteStream);localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));weblog('Added local stream to pc1');try {weblog('pc1 createOffer start');const offer = await pc1.createOffer(offerOptions);await onCreateOfferSuccess(offer);} catch (e) {onCreateSessionDescriptionError(e);}}function onCreateSessionDescriptionError(error) {console.log(`Failed to create session description: ${error.toString()}`);}async function onCreateOfferSuccess(desc) {weblog(`Offer from pc1\n${desc.sdp}`);weblog('pc1 setLocalDescription start');try {await pc1.setLocalDescription(desc);onSetLocalSuccess(pc1);} catch (e) {onSetSessionDescriptionError();}weblog('pc2 setRemoteDescription start');try {await pc2.setRemoteDescription(desc);onSetRemoteSuccess(pc2);} catch (e) {onSetSessionDescriptionError();}weblog('pc2 createAnswer start');// Since the 'remote' side has no media stream we need// to pass in the right constraints in order for it to// accept the incoming offer of audio and video.try {const answer = await pc2.createAnswer();await onCreateAnswerSuccess(answer);} catch (e) {onCreateSessionDescriptionError(e);}}function onSetLocalSuccess(pc) {weblog(`${getName(pc)} setLocalDescription complete`);}function onSetRemoteSuccess(pc) {weblog(`${getName(pc)} setRemoteDescription complete`);}function onSetSessionDescriptionError(error) {weblog(`Failed to set session description: ${error.toString()}`);}function gotRemoteStream(e) {if (remoteVideo.srcObject !== e.streams[0]) {remoteVideo.srcObject = e.streams[0];weblog('pc2 received remote stream');}}async function onCreateAnswerSuccess(desc) {weblog(`Answer from pc2:\n${desc.sdp}`);weblog('pc2 setLocalDescription start');try {await pc2.setLocalDescription(desc);onSetLocalSuccess(pc2);} catch (e) {onSetSessionDescriptionError(e);}console.log('pc1 setRemoteDescription start');try {await pc1.setRemoteDescription(desc);onSetRemoteSuccess(pc1);} catch (e) {onSetSessionDescriptionError(e);}}async function onIceCandidate(pc, event) {try {await (getOtherPc(pc).addIceCandidate(event.candidate));onAddIceCandidateSuccess(pc);} catch (e) {onAddIceCandidateError(pc, e);}console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);}function onAddIceCandidateSuccess(pc) {weblog(`${getName(pc)} addIceCandidate success`);}function onAddIceCandidateError(pc, error) {weblog(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);}function onIceStateChange(pc, event) {if (pc) {weblog(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);weblog('ICE state change event: ', event);}}function hangup() {weblog('Ending call');pc1.close();pc2.close();pc1 = null;pc2 = null;hangupButton.disabled = true;callButton.disabled = false;}async function displaySdp() {const configuration = getSelectedSdpSemantics();let peerConnection = new RTCPeerConnection(configuration);const offer = await peerConnection.createOffer(offerOptions);await peerConnection.setLocalDescription(offer);outputTextarea.value = offer.sdp;}

整个程序实现可以在这里访问https://www.fanyamin.com/webrtc/examples/local_peer_connection.html

我在页面了把整个连接的步骤打印了出来

[49.461] Received local stream
[72.743] Starting call
[72.743] Using video device: USB Video Device (046d:081d)
[72.743] Using audio device: 默认 - 麦克风 (USB Audio Device) (046d:081d)
[72.743] RTCPeerConnection configuration:
[72.745] Created local peer connection object pc1
[72.746] Created remote peer connection object pc2
[72.746] Added local stream to pc1
[72.747] pc1 createOffer start
[72.765] Offer from pc1 v=0 o=- 8212739043455445815 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE 0 1 a=msid-semantic: WMS xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:nTn7 a=ice-pwd:1XyHlJ5xBTJ7NBuU5Y5mBqCn a=ice-options:trickle a=fingerprint:sha-256 51:62:BB:13:05:A7:38:05:47:78:BA:70:A6:A7:64:29:6C:45:00:AC:B3:7F:92:45:80:F5:5A:4B:10:7A:36:42 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=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=sendrecv a=msid:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ 2cbf5334-0e08-4985-817f-a666c35b633b a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 a=ssrc:2931786518 cname:JVCgjci5cC/oQHwi a=ssrc:2931786518 msid:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ 2cbf5334-0e08-4985-817f-a666c35b633b a=ssrc:2931786518 mslabel:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ a=ssrc:2931786518 label:2cbf5334-0e08-4985-817f-a666c35b633b m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:nTn7 a=ice-pwd:1XyHlJ5xBTJ7NBuU5Y5mBqCn a=ice-options:trickle a=fingerprint:sha-256 51:62:BB:13:05:A7:38:05:47:78:BA:70:A6:A7:64:29:6C:45:00:AC:B3:7F:92:45:80:F5:5A:4B:10:7A:36:42 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:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay a=extmap:11 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:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=sendrecv a=msid:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ 906550e7-7a71-4c6a-a7ca-8f81fa0efe6c 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: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:100 VP9/90000 a=rtcp-fb:100 goog-remb a=rtcp-fb:100 transport-cc a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=fmtp:100 profile-id=2 a=rtpmap:101 rtx/90000 a=fmtp:101 apt=100 a=rtpmap:102 H264/90000 a=rtcp-fb:102 goog-remb a=rtcp-fb:102 transport-cc a=rtcp-fb:102 ccm fir a=rtcp-fb:102 nack a=rtcp-fb:102 nack pli a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f a=rtpmap:121 rtx/90000 a=fmtp:121 apt=102 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=0;profile-level-id=42001f a=rtpmap:120 rtx/90000 a=fmtp:120 apt=127 a=rtpmap:125 H264/90000 a=rtcp-fb:125 goog-remb a=rtcp-fb:125 transport-cc a=rtcp-fb:125 ccm fir a=rtcp-fb:125 nack a=rtcp-fb:125 nack pli a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f a=rtpmap:107 rtx/90000 a=fmtp:107 apt=125 a=rtpmap:108 H264/90000 a=rtcp-fb:108 goog-remb a=rtcp-fb:108 transport-cc a=rtcp-fb:108 ccm fir a=rtcp-fb:108 nack a=rtcp-fb:108 nack pli a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=rtpmap:109 rtx/90000 a=fmtp:109 apt=108 a=rtpmap:124 H264/90000 a=rtcp-fb:124 goog-remb a=rtcp-fb:124 transport-cc a=rtcp-fb:124 ccm fir a=rtcp-fb:124 nack a=rtcp-fb:124 nack pli a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f a=rtpmap:119 rtx/90000 a=fmtp:119 apt=124 a=rtpmap:123 H264/90000 a=rtcp-fb:123 goog-remb a=rtcp-fb:123 transport-cc a=rtcp-fb:123 ccm fir a=rtcp-fb:123 nack a=rtcp-fb:123 nack pli a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f a=rtpmap:118 rtx/90000 a=fmtp:118 apt=123 a=rtpmap:114 red/90000 a=rtpmap:115 rtx/90000 a=fmtp:115 apt=114 a=rtpmap:116 ulpfec/90000 a=ssrc-group:FID 818575976 4014440443 a=ssrc:818575976 cname:JVCgjci5cC/oQHwi a=ssrc:818575976 msid:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ 906550e7-7a71-4c6a-a7ca-8f81fa0efe6c a=ssrc:818575976 mslabel:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ a=ssrc:818575976 label:906550e7-7a71-4c6a-a7ca-8f81fa0efe6c a=ssrc:4014440443 cname:JVCgjci5cC/oQHwi a=ssrc:4014440443 msid:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ 906550e7-7a71-4c6a-a7ca-8f81fa0efe6c a=ssrc:4014440443 mslabel:xlhFA5NOFj9VpQ7Z1ylg9jmfNytu6l7jTKhQ a=ssrc:4014440443 label:906550e7-7a71-4c6a-a7ca-8f81fa0efe6c
[72.765] pc1 setLocalDescription start
[72.772] pc1 setLocalDescription complete
[72.772] pc2 setRemoteDescription start
[72.937] pc2 received remote stream
[72.937] pc2 setRemoteDescription complete
[72.937] pc2 createAnswer start
[72.959] Answer from pc2: v=0 o=- 1094912348166165889 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE 0 1 a=msid-semantic: WMS m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:MIpG a=ice-pwd:yeUliWqTGocmk3MfGp8WnL4z a=ice-options:trickle a=fingerprint:sha-256 94:06:0A:3C:17:86:C7:D3:BB:3F:DE:D6:8D:4A:C6:FC:FE:08:69:86:74:22:B7:7A:58:2D:49:F0:3B:97:83:6B a=setup:active 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=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=recvonly a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:MIpG a=ice-pwd:yeUliWqTGocmk3MfGp8WnL4z a=ice-options:trickle a=fingerprint:sha-256 94:06:0A:3C:17:86:C7:D3:BB:3F:DE:D6:8D:4A:C6:FC:FE:08:69:86:74:22:B7:7A:58:2D:49:F0:3B:97:83:6B a=setup:active 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:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay a=extmap:11 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:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=recvonly 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: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:100 VP9/90000 a=rtcp-fb:100 goog-remb a=rtcp-fb:100 transport-cc a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=fmtp:100 profile-id=2 a=rtpmap:101 rtx/90000 a=fmtp:101 apt=100 a=rtpmap:102 H264/90000 a=rtcp-fb:102 goog-remb a=rtcp-fb:102 transport-cc a=rtcp-fb:102 ccm fir a=rtcp-fb:102 nack a=rtcp-fb:102 nack pli a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f a=rtpmap:121 rtx/90000 a=fmtp:121 apt=102 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=0;profile-level-id=42001f a=rtpmap:120 rtx/90000 a=fmtp:120 apt=127 a=rtpmap:125 H264/90000 a=rtcp-fb:125 goog-remb a=rtcp-fb:125 transport-cc a=rtcp-fb:125 ccm fir a=rtcp-fb:125 nack a=rtcp-fb:125 nack pli a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f a=rtpmap:107 rtx/90000 a=fmtp:107 apt=125 a=rtpmap:108 H264/90000 a=rtcp-fb:108 goog-remb a=rtcp-fb:108 transport-cc a=rtcp-fb:108 ccm fir a=rtcp-fb:108 nack a=rtcp-fb:108 nack pli a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=rtpmap:109 rtx/90000 a=fmtp:109 apt=108 a=rtpmap:124 H264/90000 a=rtcp-fb:124 goog-remb a=rtcp-fb:124 transport-cc a=rtcp-fb:124 ccm fir a=rtcp-fb:124 nack a=rtcp-fb:124 nack pli a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0015 a=rtpmap:119 rtx/90000 a=fmtp:119 apt=124 a=rtpmap:123 H264/90000 a=rtcp-fb:123 goog-remb a=rtcp-fb:123 transport-cc a=rtcp-fb:123 ccm fir a=rtcp-fb:123 nack a=rtcp-fb:123 nack pli a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640015 a=rtpmap:118 rtx/90000 a=fmtp:118 apt=123 a=rtpmap:114 red/90000 a=rtpmap:115 rtx/90000 a=fmtp:115 apt=114 a=rtpmap:116 ulpfec/90000
[72.959] pc2 setLocalDescription start
[72.960] pc1 addIceCandidate success
[72.961] pc1 addIceCandidate success
[72.961] pc1 addIceCandidate success
[72.964] pc1 addIceCandidate success
[72.964] pc1 addIceCandidate success
[72.964] pc1 addIceCandidate success
[72.964] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[72.965] pc1 addIceCandidate success
[73.292] pc2 setLocalDescription complete
[73.294] pc2 ICE state: checking
[73.294] ICE state change event:
[73.295] pc1 ICE state: checking
[73.295] ICE state change event:
[73.295] pc2 ICE state: connected
[73.295] ICE state change event:
[73.297] pc1 ICE state: connected
[73.297] ICE state change event:
[73.297] pc1 setRemoteDescription complete
[73.305] pc2 addIceCandidate success
[73.305] pc2 addIceCandidate success
[73.305] pc2 addIceCandidate success
[73.306] pc2 addIceCandidate success

下次我们再来举一个远程两个位于 NAT 之后的 client 互相连接的例子,好好说说 JSEP 和 ICE/STUN/TURN 协议:


http://www.taodudu.cc/news/show-7236138.html

相关文章:

  • 29、MAix Bit K210开发板进行目标检测
  • 论文阅读:Your Local GAN: Designing Two Dimensional Local Attention Mechanisms for Generative Models
  • Local GAN | 局部稀疏注意层+新损失函数(文末免费送书活动)
  • SQL Server -- 作业(Job) 浅析1
  • Spring MVC注解@Valid、@JsonSerialize、@JsonView等
  • django项目配置
  • win7下安装jdk1.8版本
  • 基于ThreadLocal的日期工具类
  • Local GAN
  • Linux构建高效FTP服务器
  • 怎么看电脑显卡
  • Windows 7 查看GPU显卡使用情况(仅限Nvidia)
  • 网购计算机如何看显卡信息,怎么看电脑配置显卡?看电脑配置显卡方法
  • 如何查看自己的显卡信息?
  • 阿里开源自用 OpenJDK 版本,Java 社区迎来中国力量
  • Android6.0运行时权限解决方案
  • 全文索引Sphinx+binlog日志+Grant用户授权+读写分离和主从复制
  • Zabbix原厂给中国用户的一封信,4大理由消除使用限制的担忧
  • GitHub 开启 2FA 双重身份验证的方法
  • 最适合中国国情的开源授权协议ZPL 1.0发布
  • 中国 Azure 应用程序的开发人员注意事项
  • 2019最新小白玩转投资理财入门+进阶教程 助你提升投资理财技能
  • 理财入门1
  • 2022年理财这个行业怎么样,理财师证书有哪些?
  • 作为一名理财顾问,我为什么考RFP国际理财规划师专业能力认证
  • 使用EasyNVR、easy-player-element.js在web端实现视频直播
  • 监控视频接入网关配置示例
  • 有HDR的功能的显示器和没有HDR的显示器区别大吗
  • 什么是HDR技术?HDR10、HDR400有什么区别?为什么售价3万元的lg的EP950系列显示器只有HDR400认证?
  • 2023年全国最新道路运输从业人员精选真题及答案3

WebRTC 之 PeerConnection: 细说对等连接建立流程一相关推荐

  1. steam游戏上架流程一:使用官方SDK上传游戏

    参考: steamworks.net 官方文档的说明 http://steamworks.github.io/gettingstarted/ steam游戏上架流程一:使用官方SDK上传游戏  htt ...

  2. PCB生产工艺流程一:PCB分类的三大要点

    PCB生产工艺流程一:PCB分类的三大要点 PCB在材料.层数.制程上的多样化以适不同的电子产品及其特殊需求.因此其种类划分比较多,以下就归纳一些通用的区别办法,来简单介绍PCB的分类以及它的制造工艺 ...

  3. 网页直播/点播播放器支持WebRTC/http-flv/rtmp/m3u8等直播流播放

    H5播放器 H5直播/点播播放器,使用简单,功能强大 支持WebRTC播放; 支持MP4播放; 支持m3u8/HLS播放; 支持HTTP-FLV/WS-FLV播放; 支持RTMP播放; 支持直播和点播 ...

  4. tcp/ip 协议栈Linux内核源码分析14 udp套接字接收流程一

    内核版本:3.4.39 前面两篇文章分析了UDP套接字从应用层发送数据到内核层的处理流程,这里继续分析相反的流程,看看数据是怎么从内核送到应用层的. 与发送类似,内核也提供了多个接收数据的系统调用接口 ...

  5. [SPRD CAMERA] 4 HAL Camera open流程一

    前言 最近在搞8581的camera问题,作为一个新手从没有做过camera部分,希望通过这一系列的文章记录自己学习过程.     大量参考大神的文章:[Camera专题]你应该熟悉的Camera驱动 ...

  6. 大数据中台架构以及建设全流程一(Paas层设计)

    目录 设计背景 问题点 中台目标 复用,赋能,降本增效 中台整体架构 Pass层技术选型 实时存储平台----------->KAFKA(未来pulsar也不错) 离线存储平台(Hadoop系列 ...

  7. Iceberg源码学习:flink读iceberg流程一

    实例 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); StreamExec ...

  8. tcp/ip 协议栈Linux内核源码分析12 udp套接字发送流程一

    内核版本:3.4.39 因为过往的开发工作中既包括内核网络层模块的开发,又包括应用层程序的开发,所以对于网络数据的通信有那么一些了解.但是对于网络通信过程中,内核和应用层之间接口是如何运作的不是很清楚 ...

  9. FireStart教程:基于SharePoint的出差报销流程一

    本教程将带领您探索FireStart BPM Suite 产品的使用.读者将从零开始学习建立一个流程模型,并自动执行此工作流的完整过程.FireStart 平台具有极其强大的功能和广泛的适用性,涵盖各 ...

最新文章

  1. 清华大学最新研制的自行车“成精”了!“天机”在全球顶尖期刊“泄露”
  2. 视音频编解码学习工程:FLV封装格式分析器
  3. 让Tee 7.x版本和FastReport 3.x版本共存
  4. [UE4]报错:Material with missing usage flag was applied to skeletal mesh 的解决方法:为材质设置相应的 usage flag
  5. php文件操作(上传文件)1
  6. Hadoop1——创建虚拟机
  7. JQuery使用手册 转载
  8. 力扣 二叉搜索树的最小绝对差
  9. 熊猫分发_熊猫重命名列和索引
  10. 解析ES6箭头函数中的this
  11. Pycharm主题颜色设置
  12. PreferenceFragment和PreferenceActivity
  13. 如何申请并使用 eepromARMtool 工具
  14. 服务器每个月维护要1000元,5月24日服务器例行维护公告(已完成)
  15. 【夏虫语冰】visio2013安装出错,无法打开注册表,错误码1402
  16. php微信授权登录sdk,微信授权登录
  17. 抖音小程序开发教程之初识抖音小程序 (教程含源码)
  18. ESP8266闪存文件系统基本操作-删除文件
  19. 联发科八核芯片MT6599 起步赢高通,辉达NVIDIA
  20. aquarius Java自定义对象池

热门文章

  1. Collection、Set、List、Quene、Map之间的关系
  2. 【MyBatis】各种查询功能
  3. PL/SQL实现POST请求和参数AES加解密
  4. 千宗版权案缠身 三年亏掉21亿 资本会为喜马拉雅买单吗?
  5. opencv 特征提取综述
  6. SAP EWM /SCWM/CHM_LOG - 显示及分析检查日志
  7. 2017年对口招生c语言及答案,2017年对口升学计算机专业试题(含答案)
  8. 野火无刷电机驱动板pcb,原理图,电源电压检测
  9. tensorflow——调试Image_retrain出现问题一
  10. oracle grant view access,oracle中v$access视图介绍