WebRTC(Web Real-Time Communication)网页即时通讯,标准是WHATWG协议,它通过简单的API使得浏览器和移动应用程序提供实时通信(RTC)的功能;在不借助中间媒体的情况下,建立浏览器之间的点对点(Peer-to-Peer)的连接,实现音视频流或其他任意数据的传输。 WebRTC是一个由Google发起的实时通讯解决方案,其中包含了视频/音频采集、编码器、数据传输、音视频展示等功能,WebRTC项目的原则是API开源、免费、标准化、浏览器内置,比现有的技术更高效; 在设备方面,不受限于传统互联网应用或者浏览器的终端运行环境,实际上,无论终端运行环境是浏览器、桌面应用、移动设备还是IoT设备,只要IP链接可以到达且符合WebRTC规范就可以进行互通通信;
跨平台
可以在Web、Android、IOS、Windows、macOS、Linux环境下运行WebRTC应用
解决了Web端无法捕获音视频的能力,且提供了peer-to-peer(浏览器间)的视频交互
实时传输
传输速度快,低延迟、适合实时性要求较高的应用场景
音视频引擎
强大的音视频处理能力
WebRTC的核心层是没有视频的渲染的,视频的渲染是需要应用层去做
免插件
不需要依赖任何插件,打开浏览器即可使用
强大的打洞能力
WebRTC技术包含了使用STUN、ICE、TURN、RTP-over-TCP的关键NAT和防火墙穿透技术,并支持代理
先进的技术
先进的音视频编解码器(Opus和VP8/9),强制加密协议(SRTP和DTLS)和网络地址转换器(ICE&STUN)
与同级别的FFmepg比较
FFmepg的侧重点是多媒体的编辑、音视频的编解码等对视频文件的处理
WebRTC的优势在于整个网络中实现音视频的传输,对网络的抖动、丢包、网络的评估,在网络中的各种算法优化保证了音视频传输的稳定性,此外还包括一些回音消除、降噪等的优化,当然WebRTC不仅仅局限于音视频,还在文件传输、即时通讯等领域有较大影响力
架构图解
接口层
暴露给业务侧,业务侧可以使用原生的C++ API接口或者Web API 开发音视频实时通信,核心是RTCPeerConnection
Session层
用于控制业务逻辑,如媒体协商、收集Candidate等
引擎层
包括音视频引擎和网络传输
设备层
接口层
可以使用浏览器开发音视频直播客户端,也可以使用Native(C++、Android、OC等)开发音视频直播客户端;
Web应用层
Video Conference、Video Call、Remote Education为应用层,指具体的音视频应用,是应用开发人员最关注的; Web开发者可以基于Web API开发基于视频、音频的实时通讯应用;
Web API
Web API部分是Web应用开发者API层,为应用层提供API服务,是应用开发者调用的接口;W3C官方文档地址:www.w3.org/TR/webrtc/
常用 API
Network Stream API
MediaStream 媒体数据流,如音频流、视频流等;
MediaStreamTrack:在浏览器中表示一个媒体源
RTCPeerConnection包含的核心类
RTCPeerConnection 该类很重要,提供了应用层的调用接口,允许两个浏览器之间的直接通讯
SDP:用来描述当前连接者想要传输的内容,支持的协议类型、支持的编解码类型等,帮助我们描述媒体流的信息
RTCIceCandidate:表示一个ICE协议的候选者-目标节点的IP和端口
RTCIceServer:表示一个ICE Server,主要用于当前主机的IP发现,通过和ICE Server通信,可以得到一组可供连接使用的IP:Port候选值,双方通过交换ICE候选值建立连接
Peer-to-Peer Data API
DateChannel:数据通道接口表示一个在两个节点之间的双向数据通道
RTCDataChannel 传输非音视频数据,如文字、图片等;
RTCPeerConnection调用过程
Stream流:也就是Media Stream,包含了很多轨,包括了视频、音频等
PeerConnection,connection内部有两个线程,一个是work线程,一个是Signaling线程,就是通过PeerConnectionFactoryInterface来创建两个线程,PeerConnectionFactoryInterface实际就是个工厂,可以创建PeerConnection、MediaStream、LocalVideoTrack、localAudioTrack,创建之后会首先创建一个个的轨,然后通过AddTrack添加到MediaStream媒体流中去,
有了多个媒体流后需要通过AddStream将其添加到PeerConnection中,他们复用的是同一个Connection
RTCPeerConnection调用时序图
首先是应用层会触发CreatePeerConnectionFactory
,这样就创造出了PeerConnectionFactory
这个工厂,那么这个工厂呢又触发了CreatePeerConnection
;
然后创建了一个PeerConnection
连接,这个工厂还会创建CreateLocalMediaStream
、CreateLocalVideoTrack
、CreateLocalAudioTrack
这些轨;
之后再通过AddTrack
将这些轨添加到Stream
中去(也可以通过AddStream方法将媒体流添加到RTCPeerConnection对象中,但几乎已经废弃了),添加完了之后在调用这个AddStream
将这个流添加到PeerConnection
连接中去,流提交好了之后会提交这个流的变化CommitStreamChanges
。
当这个流触发变化的时候,就会触发这个事件创建一个offer
的SDP
的描述信息。
有了这个描述 信息之后呢,通过应用层通过信令发送到远端,在这一端 收到这个offer
SDP
,SDP
里面包括的信息有包括哪些视频哪些音频以及音频格式是什么视频格式是什么?你的传输地址是什么?这些信息实际就度过来了;
然后根据这些信息远端会回一个answer
给这个信令,信令与 我们媒体流的信息实际上是两条路,并不是通过TCP
传输的,它是通过UDP
传输的,当这个信令收到这个answer
之后,就会传给这个connection
,然后个连接就拿到了对方这个媒体流信息以及它的传输端口、传输地址。这样他们之间就打通这个通道了,可以相互的传这个媒体数据了。
当远端的数据来了之后,这个Connection
还会将远端的这个流,添加到这个APP
中去,APP
它本身也是一个ConnectionObserver
,这其实是一个观察者,要知道这个连接发生了哪些事件。
RTCPeerConnection内部事件汇总
C++ API(PeerConnection)
底层API使用C++语言编写,使浏览器厂商容易实现WebRTC标准的Web API,抽象的对数字信号过程进行处理,这一层主要实现了P2P连接
Session 层
主要作用是控制业务逻辑,如媒体协商、收集Candidate等; Session组件是基于libjingle(会话协商+NAT穿透组件库)开发传输会话层
Session Management - 信令管理层
Session Management是一个抽象会话层,提供会话建立和管理功能,该层协议留给开发者自己定义实现,对于Web应用,建议使用websocket技术来管理信令session; 信令主要用于转发会话双方的媒体信息和网络信息;
核心引擎层
音频引擎和视频引擎都是相对独立的,但都要与网络传输层打交道(对应数据的发送(自己端的数据)与接收(其他端的数据)),但由于要进行音视频同步,因此音视频引擎间又存在着某种关联
Transport
是WebRTC的传输层,涉及音视频的数据发送、接收、网络打洞等内容,可以通过STUN和ICE组件来建立不同类型的网络间的呼叫连接; 传输层底层用的是UDP协议,网络传输层包括SRTP、网络I/O多路复用、P2P等;
SRTP:一般的正常音视频传输都是用的RTP协议,但由于浏览器对安全性要求较高,因此增加了加密处理模块,采用了SRTP协议,控制流的传输协议是RTCP,对相应的数据发送,接收报告发送给对方,这样对方就可以做流控处理
Multplexing:这里主要是多个流复用同一个通道
P2P(STUN + TURN + ICE),这里主要是P2P的相关技术,是WebRTC最核心的技术
VoiceEngine
音频引擎是包含一系列音频多媒体处理的框架,包括从音频采集到网络传输端等整个解决方案; (Echo Canceler/ Noise Reduction)内部包括回声消除器、音频编解码器、噪声抑制元件等; NetEQ模块是WebRTC语音引擎中的核心模块,一种动态抖动缓冲和错误隐藏算法,用于隐藏网络抖动和数据包丢失的负面影响,保持尽可能低的延迟,同时保证尽可能高的语音质量;
包含模块
iSAC/iLBC Codec:主要是编解码
NetEQ for voice: NetEQ
实际上是一个音频缓冲的buffer,用于做网络的适配。如我们做防止音频抖动,这里面涉及了很多相关的算法
Echo Canceler/ Noise Reduction:包括回声消除器、噪声抑制元件等复杂算法;
VideoEngine
视频处理引擎,包含一系列视频处理的整体框架(视频采集、编解码、加密、图像处理等),从摄像头采集视频到视频信息网络传输再到视频显示,是一个完整过程的解决方案; 内部包括视频图像编解码器(VP8/VP9/H264)、视频抖动缓冲器、图像质量增强器等模块; VP8视频图像编解码器是WebRTC视频引擎的默认编解码器,其适合实时通信应用场景,因为他主要是针对低延时而设计的编解码器;
包含模块
VP8 Codec:主要是音视频编解码器、如VP8、VP9,后续支持的H264,H265、Open H264、xh264(需要安装插件open H264支持)
video jitter buffer:视频JitterBuffer和音频buffer一样也是用来防止视频抖动的
image enhancements:图像处理相关的,如图像增强处理,但WebRTC在图像处理这块比较薄弱,预留了对应的接口,可以在预留的接口中去单独实现,如实现美颜、贴图、人脸视频等接口
设备层
设备层主要与硬件打交道,涉及的内容主要包括:在各终端设备上进行音频的采集和播放,视频的采集、以及网络层等
音视频架构分析
一个及其简单的直播客户端至少应该包括音视频采集模块、音视频编码模块、网络传输模块、音视频解码模块和音视频渲染模块五大部分
音视频采集模块
该模块调用系统的API,从麦克风和摄像头读取设备采集音视频数据,音频采集的是PCM数据、视频采集的是YUV数据
音视频编码模块
该模块负责将音视频设备上采集的原始数据(PCM/YUV)进行压缩编码
网络传输模块
负责将编码后的数据生成RTP包,并通过网络传输给对端;同时在对端接收RTP数据
音视频解码模块
该模块对网络传输模块接收到的压缩数据进行解码,还原为原始数据(PCM/YUV)
音视频渲染模块
该模块拿到解码后的数据后,将音频输出到扬声器,将视频渲染到显示器
拆分音视频模块
拆分音视频模块的原因在于是实际开发中,音频与视频的处理流程是是完全独立的,各有各的处理方式,它们之间是不想交的,如音频有独立的采集设备(声卡)、独立的播放设备(扬声器)、访问音频设备的系统API、多种音频编解码器等;而视频也有对应独立的采集设备(摄像头)、渲染设备(显示器)、各种视频编解码器等
在音视频处理中,一般称每一路音频或每一路视频为一条轨 为了实现跨平台的功能,需要在硬件设备上进行兼容处理,一般除了常规的Windows、Mac、Android和iOS外,最好还可以兼容到liunx和浏览器
插件化管理
对于音视频直播客户端来说,不仅仅希望可以处理音视频数据,还可以处理屏幕共享、播放多媒体文件等功能,而在编解码方面,不仅仅局限于常规的格式,还可以进行多种格式的编解码操作,此时就需要用到插件化管理的方案了,当系统需要支持某一种功能时就可以直接讲对应的插件编写放上去就好了,同样的像是音视频编解码模块也可以做成插件进行插件化管理注入即可; 有了插件管理模块,各终端可以根据接收到的音频数据类型调用不同的音频解码器进行解码,从而实现不同编解码器在同一场直播中互通的场景,这就是插件化管理给我们带来的好处
其他功能点问题
音视频编解码能力沟通、网络传输数据、如何发现对方
通过媒体协商、网络协商、媒体协商+网络协商数据的交换通道、信令服务器的开发(SDP/Candidate的交互、房间维护)
SDP是一个专门的协议,用于描述P2P通信双方支持的媒体格式等信息,在WebRTC中通信双方必须先交换SDP信息,这样才能知根知底,这个交换SDP的过程就称为媒体协商
音视频不同步
音视频数据经过网络传输后,由于网络抖动和延迟等问题,很可能造成音视频不同步的问题,对此可以在音视频直播客户端增加音视频同步模块以保障音视频的同步
回音
指的是与他人进行试试互动时可以听到自己的回声;
在实时音视频通信中,不光有回音问题,还有噪音、声音小等问题,一般将其统称为3A问题,目前只有WebRTC和speex有开源的回音消除算法;
3A是指:Acoustic Echo Cancelling(AEC),即回音消除;Automatic Gain Control(AGC),即自动增益;Active Noise Control(ANC,也称为Noise Cancellation、Noise Suppression),即降噪。
音视频的实时性
在实时通讯中,网络质量尤为重要,但网络的物理层是很难保证网络服务质量的,必须软件层加以控制才行;
常见的TCP是牺牲了实时性来保证网络服务质量的,但实时性在音视频实时传输非常重要,因此TCP不能作为实时传输的最佳选择
一般情况下首选UDP来保证实时性,但就需要我们自己编写网络控制算法来保证网络质量了
其他方面
网络拥塞、丢包、延时、抖动、混音等问题
WebRTC 三角形
WebRTC三角形也是初版WebRTC中的架构模型,两个浏览器都运行在同一个Web服务器下的同一个WebRTC Web应用程序,这三个元素之间的信令路线(三角形的两条侧边)和媒体或数据流动路线(三角形的两条底边)构成了三角形的形状,两个浏览器之间通过对等连接来传输语音、视频媒体和附加的数据;
信令可以通过HTTP或WebSocket传输到向浏览器提供HTML页面的同一Web服务器,也可以传送到只负责信令的一个完全不同的Web服务器
WebRTC 梯形
WebRTC音视频通话通信过程
通信双方内部逻辑类似,具体过程如如下
①调用音视频检测模块,检测终端是否有可用的音视频设备
②调用音视频采集模块,采集用户音视频数据,音频是PCM数据,视频是YUV数据
③根据用户选择,是否开启录制(授权)
④通过信令模块(Signal)交换SDP
⑤创建WebRTC的核心对象RTCPeerConnection,之后添加采集到的音视频数据
⑥RTCPeerConnection向STUN(SessionTraversal Utilities forNAT)/TURN(Traversal Using Relays aroundNAT)服务器发送请求,返回caller的外网ip地址和端口号
⑦通过信令服务器,caller和callee互相传递对方的外网ip地址和端口(媒体协商)
⑧最终P2P链接建立完成,后面就会源源不断的发送音视频数据到对端
泳道图图解
一对一音视频通话解析
主要步骤 - 媒体协商
媒体协商的作用就是让双方找到共同支持的媒体能力,如双方都支持什么编解码器,从而最终实现彼此之间的音视频通信;其协商过程中交换的内容就是SDP协议定的
基本概念
offer:在双方通信中,首先发送媒体协商信息的一方称为呼叫方,呼叫方发送的SDP消息
称为Offer
Answer:在双方通信时,被呼叫方发送的SDP消息
称为Answer
协商基本步骤
通信双方将各自的媒体信息,如编解码器、媒体流的SSRC、传输协议,IP地址和端口等按照SDP格式整理好
通信双方通过信令服务器交换SDP信息,彼此拿到对方的SDP信息后找到他们共同支持
的媒体能力-一般是取交集
最后按照协商好的媒体能力开始音视频通信
通过RTCPeerConnection
创建出来的对象可以做很多事,如NAT穿越
、音视频数据
的接受
和发
送、甚至可以用于非音视频数据
的传输,当然也可以用于端到端间的媒体协商
RTCPeerConnection
除了在端与端之间建立连接、传输音视频数据外,还需要进行两次绑定,一次是与媒体源
进行绑定,解决数据从哪里来
的问题;二是与传输
进行绑定,解决接收到的音视频数据
的显示/播放
的问题
图解
主要就是用户调用RTCPeerConnection对象setRemoteDescription接口,先将各自的ofer消息保存下来然后再发送给另一方
本地的SDP和远端的SDP都设置好后就算协商成功了,媒体协商的具体过程是在webRTC内部实现的
两个客户端想要建立连接,一般来说需要有一个双方都可以访问的服务器来帮助它们
交换
连接所需要的信息,有了交换数据的中间人
后,两个客户端首先要交换的数据就是SDP
,这里面描述了连接双方想要建立怎样的连接
Peer-A与Peer-B通过信令服务器进行媒体协商,如双方使用的音视频编码格式,双方交换的媒体数据由SDP(Session Description Protocol会话描述协议)进行描述;
彼此要了解对方支持的媒体格式,要保证两端都正确的编解码器
最简单的方案就是取交集
有一个专门描述上述信息的协议叫做SDP(Session Description Protocol)协议
在WebRTC中,参与视频通信的双方必须先交换SDP信息
,这样才能知根知底,而交换SDP的过程也称为「媒体协商」
SDP消息的交换
是通过信令服务器
完成的,而SDP信息的填写
是通过RTCPeerConnection
完成的
chrome默认使用vp8协商,在不进行配置操作时,需要进行音视频流的h264 to vp8的转码操作,如需Chrome支持h264则需要进行SDP的修改,将m=video中的多个编码器交换位置,将h264放在最前面,不过webRTC的整体机制没有对h264的特别好的适配,所以容易产生卡顿和花屏
SDP交换过程
信令服务器
可以用来交换
双方的SDP信息
,一般是通过创建Socket链接
进行交换处理
SDP是一个描述多媒体连接内容
的协议
,如分辨率、格式、编码、加密算法
等,便于在数据传输时两端都可以理解彼此的数据,本质上,这些描述内容的元数据并不是媒体流本身
;
从技术上讲,SDP并不是一个真正的协议,而是一种数据格式
,用于描述在设备之间共享媒体的连接;
SDP由一行或多行UTF-8文本组成,每行以一个字符的类型开头,后跟等号,然后是包含值
或描述的结构化文本
,其格式取决于类型,以给定开头的文本行通常称为字母行,如提供媒体描述的行的类型为“m”,因此这些行成为“m行”
//会话的起始时间和结束时间,0代表没有限制 t=0 0 //表示音频传输和data channel传输共用一个传输通道,通过id区分不同的流 a=group:BUNDLE audio data //WebRTC Media Stream a=msid-semantic: WMS //m=audio说明本会话包含音频,9代表音频使用端口9来传输,但是在WebRTC中现在一般不使用,如果设置 为0,代表不传输音频 //使用UDP来传输RTP包,并使用TLS加密。SAVPF代表使用srtcp的反馈机制来控制通信过程 //111 103 104 9 0 8 106 105 13 110 112 113 126表示支持的编码,和后面的a=rtpmap对应 m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 //表示你要用来接收或者发送音频时使用的IP地址,WebRTC使用ICE传输,不使用这个地址 c=IN IP4 0.0.0.0 //用来传输rtcp的地址和端口,WebRTC中不使用 a=rtcp:9 IN IP4 0.0.0.0 //下面是对Data Channel的描述,基本和上面的audio描述类似,使用DTLS加密,使用SCTP传输 m=application 9 DTLS/SCTP 5000 a=setup:active //前面BUNDLE行中用到的媒体标识 a=mid:data //使用端口5000,一个消息的大小是1024b a=sctpmap:5000 webrtc-datachannel 1024 复制代码
注意:SDP描述的内容很多,其中就包含IP和端口的描述,但是WebRTC并没有完全全部使用这些内容;
房间中每进来一个人就发送一次Offer和Answer进行协商
媒体协商代码实现
浏览器API - 对底层webRTC API的封装(JS与C++的互通映射是通过V8引擎实现的)
createOffer:创建Offer
createAnswer: 创建Answer
setLocalDescription:设置本地SDP信息
setRemoteDescription:设置远端的SDP信息
信令的发送数据量不大,可以选择的协议比较多,TCP、HTTP/HTTPS、WS/WSS都可以,底层用的都是socket
呼叫方创建Offer
一般采用队列的方式进行房间管理,避免出现同时进入房间后争抢呼叫方的角色,但如果两端都发Offer那协商必然失败
在呼叫方发起呼叫前,首先要创建Offer类型的SDP信息,即调用RTCPeerConnection的createOffer()
方法
呼叫成功后,通过回调的方式获取到RTCSessionDescription类型的SDP信息
,然后将其保存到webRTC的local域
中,最后通过信令通道
将会话描述发送给被呼叫方
呼叫失败会执行对应的失败的回调函数
function doCall() { console.log("Sending offer to peer"); pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); } function setLocalAndSendMessage(sessionDescription) { pc.setLocalDescription(sessionDescription); sendMessage(sessionDescription); } 复制代码
sendMessage是自己实现的,负责写一个服务将Offer从A端发送给B端,然后B端协商完成后返回给A端,即需要自己实现一个信令服务器(房间服务),进行中间人的作用实现指定用户的呼叫功能。信令服务器上都有一个room的概念,大家需要加入同一个room才可以找到彼此,而信令服务器一般都会和每个用户之间有一个长链
被呼叫方收到Offer
收到Offer后调用setRemoteDescription
方法设置呼叫方发送给它的Offer作为远端描述
socket.on('message', function (message) { if (message.type === 'chat') { // ... } else if (message.type === 'offer') { pc.setRemoteDescription(new RTCSessionDescription(message)); doAnswer(); } // else if (...) { // ... // } // .... }); 复制代码
被呼叫方创建Offer
被呼叫方调用RTCPeerConnection
对象的createAnswer
方法,它会生成一个与远程会话兼容的本地会话
,并最终将该会话描述发送给呼叫方
function doAnswer() { pc.createAnswer().then( setLocalAndSendMessage, onCreateSessionDescriptionError ); } 复制代码
呼叫方收到Answer
当呼叫方收到被呼叫方的会话描述-SDP信息后,会调用SetRemoteDescription
方法,将收到的会话描述设置成一个远程会话
socket.on('message', function (message) { if (message.type === 'chat') { // ... } else if (message.type === 'offer') { pc.setRemoteDescription(new RTCSessionDescription(message)); doAnswer(); } else if (message.type === 'answer') { pc.setRemoteDescription(new RTCSessionDescription(message)); } else if (...) { // ... } // .... }); 复制代码
上述步骤完成后(媒体协商已完成),webRTC底层会收集Candidate
,并进行连通性检测
,最终在通话双方之间建立起一条链路
来,通信双方链路的建立是在设置本地媒体
能力,即调用setLocalDescription
函数之后才进行的
主要步骤 - 网络协商
webRTC之间建立连接比较复杂,原因是需要考虑传输的高效性
,还要保证端与端之间的连通性
通信双方要彼此了解对方的网络状况,这样才可以找到一条通信链路;
优点一:获取外网IP地址映射
优点二:通过信令服务器(Signal server)
交换网络信息
为了解决上述问题,WebRTC引入了NAT、STUN和TURN等概念
STUN协议:规范化拿到自己的公网IP和端口了
在内网的网关上都有 NAT (Net Address Transport) 功能,NAT 的作用就是进行内外网的地址转换。这样当你要访问公网上的资源时,NAT 首先会将该主机的内网地址转换成外网地址,然后才会将请求发送给要访问的服务器;服务器处理好后将结果返回给主机的公网地址和端口,再通过 NAT 最终中转给内网的主机。
NAT:是为了解决IPv4下的IP地址匮乏而出现的一种技术
NAT(Network Address Translation:网络地址转换:可以将私有IP地址转换为公共IP地址,从而实现私有网络与公共网路之间的通信)技术会保护内网地址的安全性,但也导致了在P2P的连接中NAT会阻止外网地址的访问,此时就需要采用NAT穿透技术了
思路参考:借助一个公网IP服务器,Peer-A与Peer-B都往公网IP/PORT发包,公网服务器就可以获知Peer-A与Peer-B的IP/PORT,又由于Peer-A与Peer-B主动给公网IP服务器发包,所以公网服务器可以穿透NAT-A与NAT-B并发送包给Peer-A与Peer-B。所以只要公网IP将Peer-B的IP/PORT发给Peer-A,将Peer-A的IP/PORT发给Peer-B,这样下次Peer-A与Peer-B互相发送消息时,就不会被NAT阻拦了
WebRTC的防火墙穿透技术就是采用了上述的思路实现的,具体是采用了ICE框架来保证RTCPeerCommection能实现NAT穿透
ICE(Interactive Connectivity Establishment,互动式连接建立协议)是一种框架,用于在两个主机之间建立连接,即使他们之间的防火墙阻止了直接连接,使各种NAT穿透技术(如STUN、TURN等)可以实现统一,该技术可以让客户端成功地穿透远程用户与网络之间可能存在的各类防火墙。
Peer-A与Peer-B通过STUN服务器获取到各自的网络信息,如IP和端口。然后通过信令服务器转发,互相交换各种网络信息;
这样双方就知道了对方的网络信息了,即P2P打洞成功建立直连,这个过程涉及NAT及ICE协议
信令服务器
主要用于转发彼此的媒体信息和网络信息,在进行开发APP时,可以将彼此的APP连接到信令服务器,一般搭建在公网或者两端都可以访问到的局域网,借助信令服务器就可以实现SDP(媒体信息包含IP地址、端口、传输协议、类型等,WebRTC将其分为host、srflx、prflx、relay四个类型,优先级依次降低)及Candidate(网络信息)的交换;
当然信令服务器也可以交换其他信息如房间管理,用户管理、用户动作状态管理等功能
Candidate - WebRTC用来描述它可以连接的远端的基本信息
主要步骤 - 建立连接 - 架构图位置 → 创建连接(STUN/TURN、NAT穿越)
当应用需要在公网中使用时,此时在创建RTCPeerConnection对象的时候就需要单独配置参数,这些参数配置中就包含STUN和TURN服务器
STUN:Session Traversal Utilities for NAT,用来帮助获取本地计算机的公网IP地址及端口号
TURN:Traversal Using Relays around NAT,用来帮助穿越NAT网关,实现公网中的WebRTC链接,一般该服务器是兜底方案,当所有方案都无法穿越NAT网关或无法直接连接P2P连接时会用到该服务,此时会作为中继服务器将媒体流进行相关中转
// 公网中使用 const pc = new RTCPeerConnection({ iceServers: [ // 目前我在用的,免费STUN 服务器 { urls: "stun:stun.voipbuster.com ", }, // 谷歌的好像都失效了,不过你们可以试试 { urls: "stun:stun.l.google.com:19301", // urls: 'stun:stun.l.google.com:19302', // urls: 'stun:stun.l.google.com:19303', // ... }, // TURN 服务器,这个对服务器压力太大了,目前没找到免费的,后续我在自己的服务器上弄一个 { urls: "turn:turn.xxxx.org", username: "webrtc", credential: "turnserver", }, { urls: "turn:turn.ap-southeast-1.aliyuncs.com:443?transport=tcp", username: "test", credential: "test", }, ], }); 复制代码
webRTC端与端之间建立连接的三个任务
为连接的每个端创建一个RTCPeerConnection对象,并且给RTCPeerConnection对象添加一个本地流,该流是从getUserMedia()获取的
获取本地媒体描述信息,即SDP信息,并与对端进行交换
获得网络信息,即Candidate(IP地址和端口),并与远端进行交换
webRTC连接的基本步骤
获取本地音视频流 - getUserMedia()获取流信息,然后指定到video的srcObject属性上进行同步展示,同时将音视频流添加到RTCPeerConnection对象中
// 创建 RTCPeerConnection 对象 let localPeerConnection = new RTCPeerConnection(servers); ... // 调用 getUserMedia API 获取音视频流 navigator.mediaDevices.getUserMedia(mediaStreamConstraints). then(gotLocalMediaStream). catch(handleLocalMediaStreamError); // 如果 getUserMedia 获得流,则会回调该函数 // 在该函数中一方面要将获取的音视频流展示出来 // 另一方面是保存到 localSteam function gotLocalMediaStream(mediaStream) { ... localVideo.srcObject = mediaStream; localStream = mediaStream; ... } // 将音视频流添加到 RTCPeerConnection 对象中 localPeerConnection.addStream(localStream) 复制代码
交换媒体描述信息 - 媒体协商
获取到各自信息后需要setLocalDescription
存储到本地然后同时将数据发送给对端
通常使用信令服务器实现
// 当创建 offer 成功后,会调用该函数 function createdOffer(description) { // ... // 将 offer 保存到本地 localPeerConnection.setLocalDescription(description) .then(() => { setLocalDescriptionSuccess(localPeerConnection); }).catch(setSessionDescriptionError); // ... // 远端 pc 将 offer 保存起来 remotePeerConnection.setRemoteDescription(description) .then(() => { setRemoteDescriptionSuccess(remotePeerConnection); }).catch(setSessionDescriptionError); // ... // 远端 pc 创建 answer remotePeerConnection.createAnswer() .then(createdAnswer) .catch(setSessionDescriptionError); } // 当 answer 创建成功后,会回调该函数 function createdAnswer(description) { // ... // 远端保存 answer remotePeerConnection.setLocalDescription(description) .then(() => { setLocalDescriptionSuccess(remotePeerConnection); }).catch(setSessionDescriptionError); // 本端 pc 保存 answer localPeerConnection.setRemoteDescription(description) .then(() => { setRemoteDescriptionSuccess(localPeerConnection); }).catch(setSessionDescriptionError); } 复制代码
端与端建立连接
调用setLocalDescription后就开始收集网络信息了,即开始收集ICE Candidate,收集上来后会触发PC的icecandidate
事件,因此可以通过编写icecandidate
的处理函数(onicecandidate)进行处理
localPeerConnection.onicecandidate= handleConnection(event)
当Candidate变为有效时,handleConnection函数将会被调用,在真实场景中,每当获取到一个新的Candidate后就会通过信令服务器交换给对端,对端再调用 RTCPeerConnection 对象的 addIceCandidate() 方法将收到的Candidate 保存起来,然后按照 Candidate 的优先级进行连通性检测
Candidate连通性检测完成后,就可以在端与端之间建立物理连接了,此时媒体就可以在双方不断地传输了
function handleConnection(event) { // 获取到触发 icecandidate 事件的 RTCPeerConnection 对象 // 获取到具体的 Candidate const peerConnection = event.target; const iceCandidate = event.candidate; if (iceCandidate) { // 创建 RTCIceCandidate 对象 const newIceCandidate = new RTCIceCandidate(iceCandidate); // 得到对端的 RTCPeerConnection const otherPeer = getOtherPeer(peerConnection); // 将本地获到的 Candidate 添加到远端的 RTCPeerConnection 对象中 otherPeer .addIceCandidate(newIceCandidate) .then(() => { handleConnectionSuccess(peerConnection); }) .catch((error) => { handleConnectionFailure(peerConnection, error); }); // ... } } 复制代码
显示远端流
远端接收到数据后需要与本地的video标签进行绑定展示
当远端有数据时,webRTC底层就会调用addstream事件的回调函数(handleRemoteStreamAdded),其入参event中包括了远端的音视频流即MediaStream对象,此时将MediaStream与video的srcObject进行赋值就实现了远端数据的显示
localPeerConnection.onaddstream = handleRemoteStreamAdded; ... function handleRemoteStreamAdded(event) { console.log('Remote stream added.'); remoteStream = event.stream; remoteVideo.srcObject = remoteStream; } 复制代码
优先级:局域网内连接 > NAT穿越 > 中继服务器中转;
Peer-A与Peer-B如果没有建立直连,则通过TURN中转服务器转发音视频数据,最终完成音视频通话
当双方处于不同点位时,一般采用中继的方案进行连接,即双方通信需要依赖第三方,也可以直接通过P2P的方式使得双方直接建立连接,一般情况下如果P2P的方式不通,才会采用中继服务器的方案
当处于同一个网段内时:可以直接通过内网进行连接,但需要提前知道双方处于同一网段(比较麻烦),一般采用通过公网实现双方的通信
ICE Candidate(ICE 候选者),表示webRTC与远端通信时使用的协议、获取IP地址和端口、候选者类型(host/srflx/relay)、优先级、传输协议、访问服务的用户名等的过程
在本机收集所有的 host 类型的 Candidate,通过 STUN协议收集 srflx 类型的 Candidate,使用 TURN 协议收集 relay 类型的 Candidate
host 表示本机候选者 - 本机内网的 IP 和端口 - 优先级最高
srflx 表示内网主机映射的外网的地址和端口(本机 NAT 映射后的外网的 IP 和端口) - 优先级居中 - 双方通过P2P连接
srflx candidate是通过信令方式
向STUN服务器发送binding request,通过该请求找到NAT映射后的地址(server视角);prflx candidate用于链接检查
,当A按照优先级向目标peer B发送binding request,B收到peer A的连通性成功时获得的地址(peer视角)
prflx和srflx都是用来获取内网主机IP
映射的公网IP
,prflx和srflx都是用的STUN协议
,prflx是直接向目的主机发起连接并请求响应的方式
NAT 穿越目的是实现两端直连,两端直连的效率高,占用资源少
两个防火墙后面的客户端通信就需要用到NAT穿越,穿越是指穿越防火墙,不破坏防火墙现有规则的情况下实现通信
在进行NAT穿越时,如果可以进行穿越,则它向对端发送binding request
请求,binding response就会带回 prflx 类型的 IP 地址和端口,它们就形成了 prflx类型的 candidate。如果NAT穿越不过去,就要走STUN
服务,此时就会获 srflx 类型的 candidate
relay 表示中继候选者 - 中继服务器的 IP 和端口 - 优先级最低,连通率最高
relay服务是通过TURN协议实现的,所以relay服务器也可以叫做TURN服务器,都是指中继服务器
relay 型候选者的获取也是通过 STUN 协议完成的,只不过它使用的 STUN 消息类型与获取 srflx 型候选者的 STUN 消息的类型不一样而已
{ IP: xxx.xxx.xxx.xxx, port: number, type: host/srflx/relay, priority: number, protocol: UDP/TCP, usernameFragment: string } 复制代码
webRTC中会先对候选者进行连通性检测,如果可以互通,就直接连接,host类型之间的连通性检测就是内网之间的连通性检测 - 解决了上面说的「提前知道双方处于同一网段(比较麻烦)」的问题
主要步骤 - 总结建立流程步骤
连接双方通过第三方服务器来交换各自的SDP信息
连接双方通过STUN协议从STUN服务器那里获取到自己的NAT结构、子网IP和公网IP、端口,即Candidate信息
连接双方通过第三方服务器(信令服务器)来交换异步发送的各自的对等信息(SDP,Condidate),信令服务器一般搭建在公网或者两端都可以访问到的局域网,借助信令服务器就可以实现SDP(媒体信息)及Candidate(网络信息)的交换
如果仅通过STUN服务器发现的公网Candidate仍然无法建立连接,这就需要寻求TURN服务器提供的转发服务,然后将转发形式的Candidate共享给对方
创建用于连接控制的RTCPeerConnection对象;
const pc = new RTCPeerConnection() 复制代码
RTCPeerConnection作用是在两个对等端建立连接,是直接在两个实体之间通信,不是通过服务器请求通信的;
对于WebRTC来说对等端就是指两个浏览器之间的直接媒体连接,建立连接后可以将任意数量的本地媒体流关联到对等连接,以通过该链接发送至远端浏览器,同样,可以将任意数量的远端媒体流发送至对等连接的本地端,这样本地端也就有了新的媒体流
采集媒体流并添加到RTCPeerConnection实例中;
通过navigator.mediaDevices.getUserMedia
方法获取媒体流,一般是在项目加载完成后就开始获取媒体流,也可以通过手动点击的方式触发采集逻辑
getUserMedia该方法可用于获取单个本地MediaStream。在获取一个或多个媒体流之后,可以使用MediaStream API将其组合到所需要的流中;
采集完成后通过RTCPeerConnection对象的addTrack方法来添加媒体流
//pc.addStream(localStream) // 添加本地媒体流的轨道都添加到 RTCPeerConnection 中 localStream.getTracks().forEach((track) => { pc.addTrack(track, localStream) }) 复制代码
检测远端媒体流是否已经成功添加进来,可以通过WebRTC提供的ontrack事件,当有新的媒体流添加进来时会触发这个事件
pc.ontrack = (event) => { remoteVideo.srcObject = event.streams[0] } 复制代码
建立连接,传输媒体流
创建各自的Offer信息进行本地保存和远端推送
createOffer方法创建本地的SDP描述
setLocalDescription设置本地的SDP描述
setRemoteDescription设置远端的SDP描述
在接收方接收到Offer后需要创建本地的SDP描述,然后创建Answer(发起端会监听Answer),将本地的SDP发送给发送端
发起端发送本地描述信息至远端 - 可以通过信令服务器WebSocket
Socket.send({ type: 'offer', data: offer});
接收端发送Answer到远端
Socket.send({ type: 'answer', data: offer});
音视频采集基本概念
帧率:摄像头
每秒采集图像的次数
称为帧率,帧率越高,视频就越平滑流畅,但占的网络带宽就越多
采集率:麦克风
每秒收集音频数据的次数
,每个采样用几个bit
表示,称为采样位深
或采样大小
轨(Track),WebRTC中的轨借鉴了多媒体的概念,轨在多媒体中的表达就是每条轨数据都是独立的,不会与其他轨相交
,如MP4的音频轨和视频轨在该文件中是分别存储的
流(Stream):可以理解为容器
在WebRTC中,流可以被分为媒体流(MediaStream)和数据流(DataStream)
媒体流可以存放0个或多个音频轨或视频轨
数据流可以存储0个或多个数据轨
视频几个基本概念
分辨率:指图像占用屏幕上像素的多少,对于实时通信而言,图像默认分辨率一般设置为640*480
或640*360
,图像中的像素密度越高,图像分辨率越高,此外分辨率还指明了图像清晰度的最大上限;
帧率:指视频
每秒播放帧(图像)的数量
。播放的帧数越多,视频越流畅;
一般的影片帧率在24帧/秒
以上,高清视频的在60帧/秒
以上,对于实时通信的视频来说,15帧/秒
是一个分水岭;
码率:指视频压缩后,
每秒数据流的大小
,原则上分辨率越大,码率也就越大;当出现分辨率大而码率越小的情况时,说明视频在编码时丢失了大量的图片信息,导致解码时无法将图片完整复原,从而造成
失真
;
音视频实时直播的突破点
提高网络质量
对网络质量产生影响的三种情况
丢包
丢包:是网络传输过程中网络质量好坏的标志,优质网络丢包率不超过2%,对于WebRTC丢包率介于2%~10%是正常的网络
延迟
如果在两端之间数据传输的延迟持续增大,说明网络路线发生了堵塞,影响低于丢包;
抖动
如果抖动很小,可以通过循环队列将其消除,抖动较大时,则将乱序包当做丢包处理
在WebRTC中抖动时长不可以超过10ms,即最多等待乱序包10ms,超过10ms就认为该包丢失了
解决上述问题的方法
NACK/RTX
NACK是RTCP中的一种消息类型,由接收端向发送端报告一段时间内有哪些包丢失了;
RTX是指发送端重传丢失包,并使用新的SSRC(将传输的音视频包与重传包进行对比分析)
FEC向前纠错
使用异或操作传输数据,以便在丢包时可以通过这种机制恢复丢失的包,FEC特别适合随机少量丢包的场景
JitterBuffer
用于防抖动,可以将抖动较小的乱序包恢复成有序包
NetEQ
专用于音频控制,内部包含了JitterBuffer;
还可以利用音频的变速不变调机制将积攒的音频数据快速的播放或将不足的音频拉长播放,以实现音频的防抖动
拥塞控制
超低延时直播技术放弃传统直播的传输模型,借鉴WebRTC通信模型,将传输和播放控制实时反馈联动,形成反馈闭环,通过网络抖动感知来动态调整缓存策略和传输策略,使其达到最优配置
WebRTC面临的挑战
信令流程复杂,难以满足直播首帧要求
标准WebRTC的信令需要通过SDP交换、ICE交互、DTLS(是一种通信协议,允许以一种旨在防窃听、防篡改或信息伪造的方式进行通信)握手之后才可以进行传输流媒体数据,导致首帧耗时严重
原生WebRTC的音视频编码格式受限
例如音频不支持AAC、视频不支持H.265和B帧,不支持私有数据的穿透,不支持可选加密,导致限制了在大型直播场景中的应用
WebRTC初衷是用于低延迟P2P通信
原生WebRTC在重传、发包策略上基于P2P通话和会议模型,不适合一对多的直播场景,为保证低延迟会采用激进的重传策略,但在高码率、高画质的直播场景中此策略会带来较大的宽带压力
信令改造
超低延时直播使用的miniSDP二进制压缩方案(压缩比10%),通过压缩SDP大小,在一个UDP包内完成SDP交互; 信令压缩后,再通过一定的冗余使得在丢包50%下也可以保证首帧成功,在此基础上进一步提出了O-RTT方案,降低70%的延迟; miniSDP和O-RTT的结合,大幅度减少了信令耗时,提升信令交互的成功率,进而降低首帧耗时和提升开播成功率
音视频改造
支持ACC的音频格式
超低延时直播基于WebRTC根据RPC6416和ISO/IEC14496-3,实现对ACC格式的完美支持,已避免WebRTC在Oplus音频格式中的转码,同时附加上的Audio FEC(Forward Error Correction)可以根据网络配置不同的冗余度,使得音频可抵抗50%以上的丢包
支持H.265
H.265比H.264有更高的压缩率,超低延时的H.265避免了H.265到H.264转码带来的成本和消耗,超低延时直播以RPC7798为基础,实现了对H.265的支持,从而避免了转码带来的成本
当然也可以进行自研编码器
如淘宝自研的S265编码器,对比业界开源的编码器都有不小的增益,S265编码从码率控制,编码工具两个方向进行优化编码质量,并从快速算法、工程算法两方面引入速度优化算法
低延时技术解密 - 淘宝直播
支持B帧
B帧在直播中较为常见,其增强了画质,同时降低了码率,标准的WebRTC为了减少编解码时引入的损耗,不支持B帧,但超低延时直播在延迟和画质、压缩率、成本之间取得了较好的平衡,因此支持了B帧;通过客户端SDK配合CDN,实现B帧的支持
传输改造
标准的WebRTC弱网应对策略是通过反馈给推流端使其调整码率来使用网络,但直播场景是一对多模型,不能因为某些观众的网络问题去调整主播推流码率; 超低延时直播通过服务端和客户端配合,WebRTC拓展帧属性和依赖关系,采用柔性分级丢帧的传输策略来渐进式降低码率,以适应弱网情况
自适应码率(Simulcast/ABR)
超低延时直播通过拓展RTCP作为切流信令,客户端和服务端都支持根据网络来无缝切流的目的,服务端通过渐进式的超发来探测网络的承受能力,以此作为切流的依据,达到快速、精准、无缝切流的目的
P2P分发网络
超低延时直播利用WebRTC自带的P2P能力,将观看同一视频流的用户就近的组织成网络,相互分享传输,每个客户端节点一边通过RTCP与CDN协商数据,同时与其他客户端节点约定内容进行共享,在保持低延时的前提下任然取得较好效果
其他改造
可选加密
标准WebRTC设计应用于音视频通信领域,为保证安全,加密为必选项,而部分直播场景,其内容本身是公开的,安全性可以适当的降低,超低延时直播可根据SDP协商选择开关加密,关闭加密可明显减少前后端开销,也进一步节省DTLS握手的耗时,最终减少首帧消耗
URL、URN与URI
URL(Uniform Resource Location):统一资源定位符,是用来定位具体资源的,是一种具体的URL,URL一般由三部分组成:协议(服务方式)、存有该资源的主机IP地址(有时也包括端口号)、主机资源的具体地址,如目录文件名等
URI(Uniform Resource Identifier):统一资源标识符,用来唯一的标识一个资源,URI一般由三部分组成:访问资源的命名机制、存放资源的主机名、资源自身的名称,由路径表示,着重强调于资源
URN(Uniform Resource Name):统一资源命名,是通过名字来标识资源;
URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源
链接:WebRTC → 深入浅出 - 掘金
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓