Webrtc从理论到实践四:通信

系列文章目录

Webrtc从理论到实践一:初识
Webrtc从理论到实践二: 架构
Webrtc从理论到实践三: 角色



前言

前面一章介绍了Webrtc在P2P通信过程中几个角色的作用,以及STUN和TURN协议在NAT穿越过程中的原理和过程。这一章我们将以一张时序图来介绍两个终端在整个视频通话过程中的细节。


一、p2p通话时序图

在这里插入图片描述

图1.1 P2P通信时序图

二、时序图步骤详解

1.连接信令服务器

在这里插入图片描述

图2.1 连接信令服务器

    前面我们讲过信令服务器主要用于双方的具体的业务信息,网络相关信息和媒体信息的交换,所以两个终端首先要与信令服务器建立连接。
    WebRTC 1.0规范文档中没有对信令做任何定义,这是因为信令与业务逻辑密切相关,不同业务逻辑的信令也会千差万别,所以让大家根据自己的业务定义信令,WebRTC只聚焦于服务质量上。
    WebRTC信令的传输方式通常有两种:HTTP和Websocket

  • HTTP传输:HTTP也可以用于传输WebRTC信令。浏览器可以使用GET或POST方式来获取信令信息。
  • Websocket传输:Websocket是一种全双工通信的协议,会与浏览器开通一个与服务器的双向连接,此连接最初采用HTTP请求的形式,但是之后升级为Websocket请求,只要Websocket服务支持CORS(Cross-Origin Resource Sharing,跨源资源共享),关于Websocket的详细介绍可以参考我写的这篇文章 Websocket协议解析和QT代码示例

2.获取localStream

在这里插入图片描述

图2.2 获取localStream

获取localStream主要有以下两个步骤:

2.1枚举媒体设备

    通过navigater.mediaDevices.enumerateDevice()可以遍历主机上的音视频设备,当执行成功后,会返回一个MediaDeviceInfo数组。 下面介绍一下MediaDeviceInfo的组成。

interface MediaDeviceInfo {
    
    
   readonly attribute DOMString deviceId;     //每个设备的唯一编号,通过该编号可以从webrtc的音视频设备管理中找到该设备
   readonly attribute MediaDeviceKind kind;  //设备种类,分为音频输入设备,音频输出设备和视频输入设备。视频的输出由显示器完成,而显示器是默认设备,所以不需要通过音视频管理器进行管理
   readonly attribute DOMString label;    //设备的别名,相比于设备id更方便于人类的记忆
   readonly attribute DOMString groupId;  //组ID,如果两个设备在同一个硬件上,则它们属于同一组,所以它们的groupId是一致的,例如音频的输入和输出设备就是集成在一起的
};
enum MediaDeviceKind {
    
    
  "audioinput",
  "audiooutput",
  "videoinput"
};

2.2采集音视频数据

    通过nagivator.mediaDevices.getUserMedia(MediaStreamStrains) 可以采集音视频数据,MediaStreamStrains可以用来控制从哪个设备上采集音视频数据以及限制采集到的数据的格式,如限制采集的视频分辨率,音频采样率采样大小等,其结构如下

dictionary MediaStreamConstranis{
    
    
     (boolean) or (MediaTrackConstrains) video = false;
     (boolean) or (MediaTrackConstrains) audio = false;
}

两个属性 video和 audio 既可以是boolean类型也可以是MediaTrackConstrains类型,如果video/audio属性是true,则浏览器会使用默认的设备和默认参数采集音视频数据。否则,浏览器会按照MediaTrackConstrains中的限制来才采集音视频数据,MeidaTrackConstrains结构如下:

dictionary MediaTrackConstraintSet {
    
    
	//视频相关
	ConstrainULong width;
	ConstrainULong height;
	ConstrainDouble aspectRatio;//宽高比
	ConstrainDouble frameRate;
	ConstrainDOMString facingMode; //前置/后置摄像头
	ConstrainDOMString resizeMode; //缩放或裁剪
	//音频相关
	ConstrainULong sampleRate;
	ConstrainULong sampleSize;
	ConstrainBoolean echoCancellation;
	ConstrainBoolean autoGainControl;
	ConstrainBoolean noiseSuppression;
	ConstrainDouble latency;//目标延迟
	ConstrainULong channelCount;
	//设备相关
	ConstrainDOMString deviceId;
	ConstrainDOMString groupId;
};

举个例子

//设置视频的最低分辨率
getUserMedia({
    
    
  audio: true,
  video: {
    
    
    width: {
    
     min: 1280 },
    height: {
    
     min: 720 }
  }
})

3. 发送join信令加入房间

在这里插入图片描述

图2.3 join信令交互

信令服务器中的信令协议是使用JSON格式来封装的,所以join信令的格式如下:

var msg = {
    
    
  'cmd':'join',
  'roomId':roomId,
  'uid':localUserId,
}

当服务器收到Alice发送的join信令之后,会进行如下处理:

  1. 根据roomId查找room是否已经存在,如果不存在,则创建一个新的room,否则使用已有room。
  2. 判断当前room是否已满(超过两个人),如果已满,则通知客户端房间已满,直接返回。
  3. 否则将发送join信令的人加入到room中
  4. 判断当前room是否超过1人,如果超过一人,则需要通知已在房间的人,发送一个otherjoin信令,然后还要通知新加入房间的人,发送一个joined信令,并携带对方的userId.

以上两个信令的格式如下

var msg = {
    
    
   'cmd':'joined',
   'remoteUid':remoteUserId
}

var msg = {
    
    
   'cmd':'otherJoin',
   'remoteUid':remoteUserId
}

4. 媒体协商

在这里插入图片描述

图2.4 媒体协商

4.1 创建RTCPeerConnection对象

     RTCPeerConnection对象是WebRTC的核心,它是WebRTC暴露给用户的统一接口,其内部由多个模块组成,如网络处理模块,服务质量模块,音视频引擎模块等等,可以把它理解成一个超级的socket,通过它可以轻松完成端到端的数据传输。

4.2 添加一个新的媒体轨道到轨道集合中

    调用RTCPeerConnection.addTrack(track,stream)可以将一个新的媒体轨道添加到轨道集合中,该轨道将被传输到另一个通信方。这里要着重介绍一下addTrack的两个参数track和stream,他们分别对应着webrtc当中的两个重要的概念:

  • MediaStreamTrack,称为“轨”,是一种单一类型的媒体源,比如从摄像头采集的视频数据就是一个视频轨,从麦克风采集的音频数据就是一个音频轨道(如果有用过Pr等剪辑软件的小伙伴对于轨这个概念应该不会陌生)。
  • MediaStream,称为“流” ,可以包含0个或多个MediaStreamTrack.MediaStream有两个重要的作用,一是可以作为录制或者渲染的源,用于制作成文件或者在浏览器中的<video>标签播放。二是在同一个MediaStream中的MediaStreamTrack数据会进行同步,比如音频轨和视频轨会进行时间同步,不同MediaStream中的MediaStreamTrack不会进行时间同步。

4.3 协商

    在双方真正通信之前,首先需要了解双方浏览器各自支持的媒体格式,比如,Alice端支持VP8,H264多种编码格式,而Bob端支持VP9,H26。要保证两端能够正确的编解码,就需要取他们的交集。并且有一个专门的协议SDP(Session Description Protoco,会话描述协议)来组装这些信息。媒体协商遵循一套严格的执行顺序,步骤如下:

  1. 调用createOffer()接口生成SDP格式的本地协商信息Offer
  2. 本地协商信息Offer生成后,调用setLocalDescription()接口,将Offer保存起来
  3. 客户端A通过信令系统将Offer信息发送给远端用户B。
  4. 客户端B通过信令系统收到用户A的Offer信息后,创建RTCPeerConnection对象并调用本地RTCPeerConnection对象的setRemoteDescription()接口,将Offer信息保存下来
  5. 客户端B接着调用createAnswer()接口创建一个SDP格式的Answer消息
  6. Answer消息创建好之后,客户端B调用setLocalDescription()接口,将Answer消息保存起来,至此,客户端B的协商过程已经完成
  7. 客户端B需要将Answer消息发送给客户端A,让A继续完成自己的媒体协商
  8. 客户端A收到B的Answer消息之后,会调用setRemoteDescription接口将Answer消息保存起来,至此整个协商过程才最终完.

5.ICE

在这里插入图片描述

图2.5 ICE建立过程

    当媒体协商完成后,WebRTC就开始建立网络连接,这一过程被称为ICE(Interactive Connectivity Establishment,交互式连接建立),是一种端到端交互的技术,可以让两个终端相互知道对方的公网IP,其操作过程如下:收集Candidate,交换Candidate,按优先级尝试连接

5.1 收集Candidate

    在介绍详细的操作过程之前,我们首先介绍一下什么是Candidate,举个例子,如果我们想用socket连接某台服务器,一定要知道服务器的一些基本信息,比如服务器的IP地址,端口号以及使用的传输协议,才能与服务器建立连接。Candidate里面就包含了可以连接的远端的基本信息,以下是Candidate中的属性:

interface RTCIceCandidate {
    
    
  readonly attribute DOMString candidate;
  readonly attribute DOMString? sdpMid;
  readonly attribute unsigned short? sdpMLineIndex;
  readonly attribute DOMString? foundation;
  readonly attribute RTCIceComponent? component;
  readonly attribute unsigned long? priority;
  readonly attribute DOMString? address;
  readonly attribute RTCIceProtocol? protocol;
  readonly attribute unsigned short? port;
  readonly attribute RTCIceCandidateType? type;
  readonly attribute RTCIceTcpCandidateType? tcpType;
  readonly attribute DOMString? relatedAddress;
  readonly attribute unsigned short? relatedPort;
  readonly attribute DOMString? usernameFragment;
};
  • candidate: 用来描述 RTCIceCandidate一些属性值组成的只读字符串,比如使用的传输协议(TCP/UDP),IP地址,端口号,Candidate类型(type host)等信息,它可以从接收到的SDP中的属性“candidate”中直接取出来的,然后IceCandidate中的其他属性就是从candidate的字符串中解析出来的,比如
a=candidate:4234997325 1 udp 2043278322 192.168.0.56 44323 typ host 
												||
												||
												\/
"candidate:4234997325 1 udp 2043278322 192.168.0.56 44323 typ host" 
(字段值:foundation,component ,protocol,priority,address,port,type)
  • sdpMid:与候选者相关的媒体流的识别标签(代表每一路流,比如我们的视频video就是"0")
  • sdpMLineIndex:在SDP中的 m = 的索引值 (表示第几个m,比如说我们sdp中一共有两个,一个视频一个音频,那么视频是第1个、音频是第2个,那么引入音频的话就是2,视频就是1)
  • foundation:用于标识candidate是从什么地方收集到的,举个例子:设备使用IP192.168.1.15,存在两个stun服务器,一个是18.18.18.18,另一个为39.39.39.39。
    那么在ICE执行的过程中:
    在本机地址上192.168.1.15上收集到的所有candidate将使用同一个foundation,例如1。
    在18.18.18.18上收集到的所有candidate也将使用同一个foundation,例如2。
    在39.39.39.39上收集到的所有candidate也将使用同一个foundation,例如3
  • component:用来标识candidate是使用RTP还是Rtcp,如果是RTP则是1 如果是RTCP则是2
  • priority: 用来描述一个candidate的使用优先级,这个参数将会在组成连通性检查列表进行使用。这个值越大说明使用的优先级越高,在连通性检查中,本地的candidate优先级加上对端的candidate优先级将会决定用于连通性检查的顺序。
  • address:候选人来源的ip地址
  • protocol:标识candidate是使用TCP还是UDP
  • port:candidate数据可达的端口
  • type: 标识了candidate的类型,一共有以下四种类型,按照处理优先级排序如下:
类型 收集方式 优先级
host 直接在本地网卡上获取地址,RTCIceCandidate.address中填写的就是远端真实的ip地址 1
srflx 是server-reflexive的缩写,是通过stun协议获取的公网地址 2
prflx prflx地址的获取流程和其他地址的获取方式不同,其他地址都是在ICE启动阶段的candidate收集流程获取到的,但是prflx地址是在执行ICE连通性检查的时候被动获取到的。具体的流程为,执行ICE的连通性检查时。本地需要向对端发送stun请求从而开始连通性检查,当收到对端的响应之后,可以确定相关的链路可以传输数据。而prflx地址就是在这个流程中发现的。当对端的stun响应中携带的mapped address不和任何一个本地收集的candidate地址相同时,就需要将这个地址视为prflx地址。prflx地址的出现可能存在多种原因:(1). 当前设备的网络发生了改变;(2). 启动阶段的candidate收集流程不够充分;(3). 对端设备的网络发生了改变; 3
relay 是通过turn协议获取的一个公网地址 4
  • tcpType:当protocol是“tcp”时,此属性可以是以下三个值:1.“active”:传输将尝试打开出站连接,但是不会接收传入的连接请求 2.“passive”:传输将接收传入的连接请求,但是不会尝试打开出站连接 3.“so”:传输将尝试同时与其对等方打开连接
  • relatedAddress:如果candidate派生自另一个 candidate, relatedAddress 是包含该host candidate的 IP 地址的字符串。对于host candidate,此值为空
  • relatedPort:如果candidate派生自另外一个candidate,例如relay和reflexive candidate ,releatedPort 标识了他们派生的candidate的端口,对于host candidate,releatedPort 是null (注意:releatedPort和releatedAdress并不直接被ICE本身使用,仅仅用于诊断和分析服务质量时使用。)
  • usernameFragment:一个包含随机生成的用户名片段(“ ICE-ufrag”)的字符串,ICE 使用该片段和随机生成的密码(“ ICE-pwd”)来保证消息的完整性。您可以使用这个字符串来验证几代的 ICE 生成; 相同 ICE 进程的每一代都将使用相同的 usernameFragment,甚至在 ICE 重新启动时也是如此。

5.2 交换Candidate

    每当发现新的候选对象,就会出发onIceCandidate()函数通知应用程序。应用程序可以决定在发现每个候选者之后,尽快将其转移到远端方或者决定等待ICE收集阶段完成之后,然后立即发送所有候选者。
    candidate信息是通过信令发送的,它在sdp协议中是以a=candidate:4234997325 1 udp 2043278322 192.168.0.56 44323 typ host 形式携带的,当远端收到对方的candidate信息之后,就会触发RTCPeerConnection.addIceCandidate()函数,这个会将Candidate信息设置到remote Description中,如果调用时,candidate字段丢失或者为null,那么被添加的ICE Candidate就是“候选结束”指示符,它表示所有的Candidate都已经交付。然后会将收到的所有的candidate和本地的candidate 按照**component ID 和 transport protocol 相同的组成一个个的可能建立连接的Candidate pair。

5.3 连通性检查

    一旦双方都收集到对方的所有的candidate之后,就会按照优先级从高到底进行排序(优先级有一个具体的算法这里不做过多的介绍),然后开始进行连通性检查。连通性检查遵循以下三个基本原则:

  1. 按照优先级顺序对候选地址进行排序
  2. 对每个候选地址按照优先级发送STUN request 检查
  3. 从另一方可以收到肯定性应答
//针对每一对候选地址,一个完整的连通性检查包含四次握手
                  A                       B
                  -                        -
                  STUN request ->             \  A's
                            <- STUN response  /  check

                             <- STUN request  \  B's
                  STUN response ->            /  check

    作为一个优化, B一旦收到A的检查消息就立即使用相同的候选传输地址对发送它的检查消息给A。这样就加速了发现有效候选传输地址的处理过程。
    在这次握手的最后, A和B都知道它们可以在两个方向上发送(接收)端到端的消息。注意,B一收到A的STUN响应就知道B->A路径正常工作,并且可以立即开始经由该路径发送媒体(如下所示)。这考虑及“早熟媒体”尽快地传输。

   A                        B
   -                        -
   STUN request ->             / A's
             <- STUN response / check
 
              <- STUN request / B's
   STUN response ->            / check
                  <- RTP Data

    一旦任何一个候选传输地址对的指定媒体成分的连通性检查成功, ICE使用该候选传输地址并且立即放弃其它所有的为该媒体成分的连通性检查。注意,由于竞争环境和包丢失,这可能意味着“最好的”候选传输地址没有被选中,但是它保证这个选择是可用的,因为排序过程一般倾向于最合适的。

6.音视频数据传输

在这里插入图片描述

图2.6 音视频数据传输

    当通信双方建立P2P连接之后,双方就可以正式地发送音视频数据了,主要使用了RTP和RTCP协议,简单介绍一下这两种协议:RTP协议是建立在UDP上针对流媒体传输的基础协议,该协议详细说明在互联网上传输音视频的标准数据包格式。RTP协议本身只保证实时数据的传输,RTCP协议则负责流媒体的传输质量保证,提供流量控制和拥塞控制等服务。在RTP会话期间,各参与者周期性彼此发送RTCP报文。报文中包含各参与者数据发送和接收等统计信息,参与者可以据此动态控制流媒体传输质量.

7.总结

    以上只是对一个最简单的P2P通信的过程概括性的总结,每一个环节还可以进一步细分,涉及到许多网络协议相关的知识,比如SDP、RTP和 RTCP等协议的具体格式。都值得好好研究,这一些将会在接下来的代码实现中带大家进一步加深理解。

下一篇:《Webrtc从理论到实践五:编译webrtc源码》

猜你喜欢

转载自blog.csdn.net/qq_39304481/article/details/125606624