资讯专栏INFORMATION COLUMN

深入理解WebRTC

sumory / 2406人阅读

摘要:对于接收方来说,则必须实时解码音频和视频流,并适应网络抖动和时延。另外,由于主要是用来解决实时通信的问题,可靠性并不是很重要,因此,使用作为传输层协议低延迟和及时性才是关键。握手记录严格按照协议规定的顺序传输,顺序不对就报错。

Web Real-Time Communication(Web实时通信,WebRTC)由一组标准、协议和JavaScript API组成,用于实现浏览器之间(端到端)的音频、视频及数据共享。

WebRTC使得实时通信变成一种标准功能,任何Web应用都无需借助第三方插件和专有软件,而是通过简单地JavaScript API即可完成。

在WebRTC中,有三个主要的知识点,理解了这三个知识点,也就理解了WebRTC的底层实现原理。这三个知识点分别是:

MediaStream:获取音频和视频流

RTCPeerConnection:音频和视频数据通信

RTCDataChannel:任意应用数据通信

MediaStream

如上所说,MediaStream主要是用于获取音频和视频流。其JS实现也比较简单,代码如下:

</>复制代码

  1. "use strict";
  2. navigator.getUserMedia = navigator.getUserMedia ||
  3. navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
  4. var constraints = { // 音频、视频约束
  5. audio: true, // 指定请求音频Track
  6. video: { // 指定请求视频Track
  7. mandatory: { // 对视频Track的强制约束条件
  8. width: {min: 320},
  9. height: {min: 180}
  10. },
  11. optional: [ // 对视频Track的可选约束条件
  12. {frameRate: 30}
  13. ]
  14. }
  15. };
  16. var video = document.querySelector("video");
  17. function successCallback(stream) {
  18. if (window.URL) {
  19. video.src = window.URL.createObjectURL(stream);
  20. } else {
  21. video.src = stream;
  22. }
  23. }
  24. function errorCallback(error) {
  25. console.log("navigator.getUserMedia error: ", error);
  26. }
  27. navigator.getUserMedia(constraints, successCallback, errorCallback);

在JS中,我们通过getUserMedia函数来处理音频和视频,该函数接收三个参数,分别是音视频的约束,成功的回调以及失败的回调。

在底层,浏览器通过音频和视频引擎对捕获的原始音频和视频流加以处理,除了对画质和音质增强之外,还得保证音频和视频的同步。

由于音频和视频是用来传输的,因此,发送方还要适应不断变化的带宽和客户端之间的网络延迟调整输出的比特率。

对于接收方来说,则必须实时解码音频和视频流,并适应网络抖动和时延。其工作原理如下图所示:

如上成功回调的stream对象中携带者一个或多个同步的Track,如果你同时在约束中设置了音频和视频为true,则在stream中会携带有音频Track和视频Track,每个Track在时间上是同步的。

stream的输出可以被发送到一或多个目的地:本地的音频或视频元素、后期处理的JavaScript代理,或者远程另一端。如下图所示:

RTCPeerConnection

在获取到音频和视频流后,下一步要做的就是将其发送出去。但这个跟client-server模式不同,这是client-client之间的传输,因此,在协议层面就必须解决NAT穿透问题,否则传输就无从谈起。

另外,由于WebRTC主要是用来解决实时通信的问题,可靠性并不是很重要,因此,WebRTC使用UDP作为传输层协议:低延迟和及时性才是关键。

在更深入讲解之前,我们先来思考一下,是不是只要打开音频、视频,然后发送UDP包就搞定了?

当然没那么简单,除了要解决我们上面说的NAT穿透问题之外,还需要为每个流协商参数,对用户数据进行加密,并且需要实现拥塞和流量控制。

我们来看一张WebRTC的分层协议图:

ICE、STUN和TURN是通过UDP建立并维护端到端连接所必需的;SDP 是一种数据格式,用于端到端连接时协商参数;DTLS用于保障传输数据的安全;SCTP和SRTP属于应用层协议,用于在UDP之上提供不同流的多路复用、拥塞和流量控制,以及部分可靠的交付和其他服务。

ICE(Interactive Connectivity Establishment,交互连接建立):由于端与端之间存在多层防火墙和NAT设备阻隔,因此我们需要一种机制来收集两端之间公共线路的IP,而ICE则是干这件事的好帮手。

ICE代理向操作系统查询本地IP地址

如果配置了STUN服务器,ICE代理会查询外部STUN服务器,以取得本地端的公共IP和端口

如果配置了TURN服务器,ICE则会将TURN服务器作为一个候选项,当端到端的连接失败,数据将通过指定的中间设备转发。

WebRTC使用SDP(Session Description Protocol,会话描述协议)描述端到端连接的参数。
SDP不包含媒体本身的任何信息,仅用于描述"会话状况",表现为一系列的连接属性:要交换的媒体类型(音频、视频及应用数据)、网络传输协议、使用的编解码器及其设置、带宽及其他元数据。

DTLS对TLS协议进行了扩展,为每条握手记录明确添加了偏移字段和序号,这样就满足了有序交付的条件,也能让大记录可以被分段成多个分组并在另一端再进行组装。
DTLS握手记录严格按照TLS协议规定的顺序传输,顺序不对就报错。最后,DTLS还要处理丢包问题:两端都是用计时器,如果预定时间没有收到应答,就重传握手记录。
为保证过程完整,两端都要生成自己签名的证书,然后按照常规的TLS握手协议走。但这样的证书不能用于验证身份,因为没有要验证的信任链。因此,在必要情况下,
应用必须自己参与各端的身份验证:

应用可以通过登录来验证用户

每一端也可以在生成SDP提议/应答时指定各自的"身份颁发机构",等对端接收到SDP消息后,可以联系指定的身份颁发机构验证收到的证书

SRTP为通过IP网络交付音频和视频定义了标准的分组格式。SRTP本身并不对传输数据的及时性、可靠性或数据恢复提供任何保证机制,
它只负责把数字化的音频采样和视频帧用一些元数据封装起来,以辅助接收方处理这些流。

SCTP是一个传输层协议,直接在IP协议上运行,这一点跟TCP和UDP类似。不过在WebRTC这里,SCTP是在一个安全的DTLS信道中运行,而这个信道又运行在UDP之上。
由于WebRTC支持通过DataChannel API在端与端之间传输任意应用数据,而DataChannel就依赖于SCTP。

以上讲了这么多,终于到我们的主角RTCPeerConnection,RTCPeerConnection接口负责维护每一个端到端连接的完整生命周期:

RTCPeerConnection管理穿越NAT的完整ICE工作流

RTCPeerConnection发送自动(STUN)持久化信号

RTCPeerConnection跟踪本地流

RTCPeerConnection跟踪远程流

RTCPeerConnection按需触发自动流协商

RTCPeerConnection提供必要的API,以生成连接提议,接收应答,允许我们查询连接的当前状态,等等

我们来看一下示例代码:

</>复制代码

  1. var signalingChannel = new SignalingChannel();
  2. var pc = null;
  3. var ice = {
  4. "iceServers": [
  5. { "url": "stun:stun.l.google.com:19302" }, //使用google公共测试服务器
  6. { "url": "turn:user@turnserver.com", "credential": "pass" } // 如有turn服务器,可在此配置
  7. ]
  8. };
  9. signalingChannel.onmessage = function (msg) {
  10. if (msg.offer) { // 监听并处理通过发信通道交付的远程提议
  11. pc = new RTCPeerConnection(ice);
  12. pc.setRemoteDescription(msg.offer);
  13. navigator.getUserMedia({ "audio": true, "video": true }, gotStream, logError);
  14. } else if (msg.candidate) { // 注册远程ICE候选项以开始连接检查
  15. pc.addIceCandidate(msg.candidate);
  16. }
  17. }
  18. function gotStream(evt) {
  19. pc.addstream(evt.stream);
  20. var local_video = document.getElementById("local_video");
  21. local_video.src = window.URL.createObjectURL(evt.stream);
  22. pc.createAnswer(function (answer) { // 生成描述端连接的SDP应答并发送到对端
  23. pc.setLocalDescription(answer);
  24. signalingChannel.send(answer.sdp);
  25. });
  26. }
  27. pc.onicecandidate = function (evt) {
  28. if (evt.candidate) {
  29. signalingChannel.send(evt.candidate);
  30. }
  31. }
  32. pc.onaddstream = function (evt) {
  33. var remote_video = document.getElementById("remote_video");
  34. remote_video.src = window.URL.createObjectURL(evt.stream);
  35. }
  36. function logError() { ... }
DataChannel

DataChannel支持端到端的任意应用数据交换,就像WebSocket一样,但是是端到端的。
建立RTCPeerConnection连接之后,两端可以打开一或多个信道交换文本或二进制数据。

其示例demo如下:

</>复制代码

  1. var ice = {
  2. "iceServers": [
  3. {"url": "stun:stun.l.google.com:19302"}, // google公共测试服务器
  4. // {"url": "turn:user@turnservera.com", "credential": "pass"}
  5. ]
  6. };
  7. // var signalingChannel = new SignalingChannel();
  8. var pc = new RTCPeerConnection(ice);
  9. navigator.getUserMedia({"audio": true}, gotStream, logError);
  10. function gotStream(stram) {
  11. pc.addStream(stram);
  12. pc.createOffer().then(function(offer){
  13. pc.setLocalDescription(offer);
  14. });
  15. }
  16. pc.onicecandidate = function(evt) {
  17. // console.log(evt);
  18. if(evt.target.iceGatheringState == "complete") {
  19. pc.createOffer().then(function(offer){
  20. // console.log(offer.sdp);
  21. // signalingChannel.send(sdp);
  22. })
  23. }
  24. }
  25. function handleChannel(chan) {
  26. console.log(chan);
  27. chan.onerror = function(err) {}
  28. chan.onclose = function() {}
  29. chan.onopen = function(evt) {
  30. console.log("established");
  31. chan.send("DataChannel connection established.");
  32. }
  33. chan.onmessage = function(msg){
  34. // do something
  35. }
  36. }
  37. // 以合适的交付语义初始化新的DataChannel
  38. var dc = pc.createDataChannel("namedChannel", {reliable: false});
  39. handleChannel(dc);
  40. pc.onDataChannel = handleChannel;
  41. function logError(){
  42. console.log("error");
  43. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88764.html

相关文章

  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!

    摘要:为了使连接起作用,对等方必须获取元数据的本地媒体条件例如,分辨率和编解码器功能,并收集应用程序主机的可能网络地址,用于来回传递这些关键信息的信令机制并未内置到中。所有特定于多媒体的元数据都使用协议传递。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 18 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可以在这里...

    XBaron 评论0 收藏0
  • 开发一个实时音视频通信系统,你需要什么技术储备?

    摘要:实时通讯系统是最近互联网应用的一个新领域。现在的问题是,开发一个优秀的系统需要具备哪些技术储备呢先看终端方面。各个平台,,,底层音频系统也需要深入了解。互联网不是一个可靠的实时音视频传输网络。现在我们知道开发一个系统需要什么技术了。 RTC(real time communication)实时通讯系统是最近互联网应用的一个新领域。RTC系统的应用极其广泛,我们常见的视频电话,会议系统,...

    church 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<