概述
文章目录
- WebRTC源码研究(29)媒体能力协商过程
- 1. RTCPeerConnection
- 2. 媒体协商过程
- 3. 媒体协商状态变换
- 3.1 stable 状态
- 3.2 have-local-offer,have-remote-offer 状态
- 4. 媒体协商方法
- 5 . PeerConnection 事件
WebRTC源码研究(29)媒体能力协商过程
我们要弄清楚媒体协商过程,首先要认识我们WebRTC中最重要的一个类RTCPeerConnection
。
1. RTCPeerConnection
下面我们介绍一下最关键的概念就是RTCPeerConnection
,其实在一开始的时候就给大家做了一个简单的介绍,这个类是整个WebRTC
的一个核心类,它是上层的一个统一的接口,但是在底层做了非常多的复杂逻辑,包括了整个媒体的协商,流和轨道的处理,接收与发送,统计数据,都是由这一个类来处理的,所以对上层来说,你可能简单的调用了这个类或者里面的几个简单的API,但是实际在底层做了大量的工作,所以这个类是整个WebRTC
的一个核心,我们一定要把这个类弄清楚,下面我们看一下这个类它的基本形式是什么样的:pc = new RTCPeerConnection([configuration];
其实也比较简单,在我们创建这个对象的时候就是new一个RTCPeerConnection
,它带一个配置参数,这个参数是可选的,就是可有可无,在我们后面的这个例子里,首先是没有参数的,看它是什么样子的,后面我们在看有参数的时候是什么样子的,但是我们就想用创建这个对象,直接就new一个这个对象就好了,这是它的基本格式,非常简单。
RTCPeerConnection
这个类我们可以将它的方法按分类分成下面的四类:
- 媒体协商:主要是有四个方法,后面会详细介绍,通过这4个方法之后呢,我们就拿到了整个双方之间的媒体信息,然后他们会进行交换,然后去协商那你用的编码器是吗对吧,我用的这个编码器承包你用的音频格式,什么什么我用的音频格式,大概协商一致之后,他才真正地进行数据传输与编解码。
- Stream/Track:在整个
WebRTC
当中,每一路它是一路流,这个流里面有很多轨,有音频轨和视频轨,多路音频轨多路视频轨,在此前MediaStream
那节中已经做过介绍,那在这里是同样的理念,在传输中我们传输的就是Stream
和轨,在轨道中就包含了音频数据和视频数据。 - 传输的相关方法:就是我们用的是什么协议去传输RTP协议,RTCP协议。HTTP协议,那么通过这个链路的质量好坏,通过HTTP,然后进行反馈。再通过这个数据的统计分析,查看你整个链路的质量是怎样的,是不是已经发生拥塞了?还是说本身的你这个链路就是不太好的,都可以通过这个传输相关的方法来获取到去计算。
- 统计相关方法:包括你的这个编解码器,你的这个音频格式,视频格式,你的这个整个传输相关的数据都可以通过这个统计相关的方法。后续也会一一看到怎么通过这些方法来获取到这些数据,来为我们真正的这个产品的质量提供帮助。
2. 媒体协商过程
媒体协商的过程在我们使用RTCPeerConnection
这个方法之前呢,我们要了解他的协议是什么,媒体是怎样协商出来的?
我们再看这些API的时候就知道这个调用是非常简单了,否则的话,你即使拿到了这些API,也不知道怎么用。但是它的协商过程呢,就如这张图所示:
对于这两个端来说,一个假设是A,第二个是B。
A首先是要创建一个offer
,创建offer之后呢,实际就形成了一个SDP
在我们之前的课程都已经给大家做了一个简单介绍,SDP是什么,它是包含了一些媒体信息和编解码信息,包括这个传输的相关的信息。
A创建了一个offer,这个offer就是SDP的一种描述格式去描述的,创建完了之后,它通过云端的信令Channel
让它传给谁呢?
传给B,但是传之前的那他还要调一个方法:
就调setLocalDescription
,那他调这个方法是干什么呢?
他会触发一个非常重要的动作,大家一定要记住,就是setLocalDescription
会触发一个非常重要动作就是去收集candidate
,就是收集候选者。
当我们去创建这个连接之前,我们首先要拿到所有的那种候选者,就收集候选者,那怎么收集呢?
实际我是像这个stun
或者是turn
发送一个请求,咱们在这个请求过程中呢,我会拿到我本地的IP地址,还有通过NAT
之后反射的这个地址以及TURN
服务的中继地址。
那它是什么时候触发的呢?什么时候去触发去获取这些候选者呢?
就是通过这个setLocalDescription
这个方法,当这个方法一调用就开始做这件事儿了,当然是并行的,其实这些事儿就通知去做这件事儿,同时呢,他就将这个offer
通过信令服务发给了这个B端。
B端收到这个offer
之后,就调用了那个setRemoteDescription
,将这个offer
所形成的SDP
的这个数据放到自己远端的这个描述信息的槽里,对他是成为一个槽儿,这样就好了。
但这步做完之后呢,他做什么事儿呢?
他要回一个answer
,也就说他要通过这个PeerConnection
就是通过这个链接调用这个创建answer
这个方法,也就是说创建一个我本身的这个媒体信息,也就offer
的是A这个用户的整个机子的媒体信息,也就是我的编解码器,视频是什么、音频是什么?
我的这个与编解码相关的这个参数也都在里面形成了这个SDP传给B,B收到之后呢,把它放在他的远程的这个描述槽里之后呢,他自己在回个answer
,这个answer
回的是什么呢?
就是我本机我这个B这台机子所有的媒体信息,以及这个网络信息。那它也包括了它的支持的这个音频格式视频格式音频编解码器、视频编解码器,比如音频的参数双声道还是单声道,是这个采样率是一万六一还是三万二还是多少,它也形成这个,它调完之后它也形成这个setLocalDescription
,它也做同样的事情,去触发收集候选者,我的这个网络有多少个候选者都要收集起来形成一个列表,调用这个函数之后它触发这个answer
,通过这个信令服务就转给了A。
A收到这个answer
之后它又把它存到这个setRemoteDescription
,存到它的远程槽里,这样在每一端实际都有两个SDP,
第一个SDP
是我自己的这个媒体信息,第二个SDP
是描述对方的媒体信息。那个拿着这两个媒体信息之后呢,他在内部就进行一个协商,那看我支持的这些格式有没有你支持的,把这一协商之后呢,就取出他们的交集,
你支持的音频最好的是哪个呢?我支持的音频是什么?再取一个交集,同样视频也是,这是视频是什么?我支持的视频是什么?那取出这个交集之后,整个协商过程就算建立好了,这个时候他才能进行后面的操作,进行真正的编解码。
传输都传到对方之后,因为他们协商好了之后,但对方也知道这是我支持的,所以他也能进行解码,让他去渲染这个音频也好,视频就播放那个视频就渲染。这就是整个协商过程。
总的来说有四个步骤:
(1)每一端是有四个步骤:
创建offer,
设置LocalDescription,
然后是接收answer,
当设置RemoteDescription,这个调用方;
(2)被调用方也是一样有四个步骤:
就是先接收offer,
然后设置setRemoteDescription,
然后是创建answer上
设置setLocalDescription。
3. 媒体协商状态变换
我们再来看一下协商的状态的变化,如下图所示:
3.1 stable 状态
当我们一开始创建这个RTCPeerConnection
的时候,它处于stable
状态,就是处于一个稳定状态,
这个时候实际connection
就可以用了,但用的时候它是不能进行这个编解码的,为什么呢?
因为他没有进行数据协商对吧,虽然我这个connection
类是可以用,但是并没有进行数据协商,所以他没法儿进行数据的传输与编解码,怎么才能进行数据传输编解码呢?
那就发生了一个状态的改变,就是比如对于调用者来说,首先创建了connection
之后他要创建这个offer
,创建offer
之后,
通过调用那个setLocalDescription
将这个offer
设进去之后;
他就状态变化了,变成什么呢,变成have-local-offer
,
但是
我设完这个之后, 如果对方没有给我回他的answer
的时候, 那实际我的状态就一直处于have-local-offer
,
无论我在接受多少次这个setLocalDescription
方法仍在处理这个状态,所以他自己对自己的一个循环对我仍然处于这个状态,
那这个状态是不会变的,那什么时候才会变呢?
只有在你远端的answer
回来的时候,像我刚才讲的远端的answer
创建好,然后通过消息传给这个调用者的时候,那它才会调用这个setRemoteDescription
,
那么将answer设进去之后,他又回到了stable
状态,这个时候RTCpeerConnection
又可以用了,而且是已经协商过的了。
这时候他可以进行编解码和传输了,这是对于调用者来说。
那么对于这个被调用者来说呢,同样道理,那当他收到这个offer
之后呢,它要调用setRemote offer
,这个时候,他从那个stable
状态就变成了have-remote-offer
,那同样的,当他自己创建了一个answer
之后,并且调用了setLocalDescription
这个方法将answer
设置进去之后,他又从这个remote-offer
变成了stable
状态,那这个时候他又可以工作了。
3.2 have-local-offer,have-remote-offer 状态
如上图所示,媒体协商状态变化除了stable
状态外,还有have-local-offer
以及have-remote-offer
:
首先在创建offer
之后呢,会调用setLocalDescription
将这个offer
设置进去,那他的状态呢,就变成了have-local-offer
,
那当他收到对端的这个answer
之后呢,它会调用setRemoteDescription
将这个offer
设置进去,这样就完成了一个协商,所以他就从这个have-local-offer
变为了stable
状态,那他就可以继续下面的工作了,
而对于被调用者,他首先呢是从信令服务器收到一个offer
,那他首先调用setRemoteDescription
这个offer
,那它就变成了have-remote-offe
r状态,
这个时候,他在调用自己的这个create answer
, 创建完自己的这个answer
之后,
它调用setLocalDescription
answer
就从这个have-remote-offer
变为了stable
状态,这样的被调用者他也就完成了自己的协商工作,可以继续下面的这个操作了,
但是还是两种情况,会有一种中间的这个状态叫做PRanswer
,就是提前应答,这个状态是什么时候会产生呢?
就是在双方通讯的时候其中被调用者还没有准备好数据的时候,那可以先创建一个临时的这个answer
,这个临时的answer
有一个特点就是:
它没有媒体数据也就是说没有音频流和视频流,并且将这个发送的方向设置成send only
,什么意思呢?
如下图:
对于B来说,他回的这个answer
是一个什么样的answer
呢 ?
就是说,我的媒体流还没有准备好,所以就没有媒体流,但是我呢,只能发送,不能接受,当他发给对方A的时候,A收到这样一个send only
,他就知道,对方还不能进入数据,
所以这时候他们的通讯虽然是做了的协商,但是他们之间还不能进行通讯。
因为第一个是对方没有媒体流,第二个是对方不接受我的数据。处于这样一个状态有什么好处呢?
那就是可以提前建立这个链路的连接,也就是说包括ICE
,包括这个DLS
这些跟链路相关的这个协商其实都已经创建好了,对刚才我们已经介绍了,就是对于B来说,他已经提前准备好了一个answer
,但这个answer
里有没有媒体数据,但是实际是有网络数据的,我收集的各种各种候选者实际都已经有了。
那么就可以提前交给这个A,那A与B之间,实际就是链路层已经协商好了,包括这个DLS
还要进行这个握手,它因为是安全加密,加密所以要进行握手,握手的时间其实还是蛮长的,那在B准备好这个自己的流之前,将所有的链路都准备好,那一旦这个B向那个用户申请:说想开启音频和视频,当用户授权说可以,这个时候呢,他们拿到数据之后,只要将数据传进去,就可以进行这个通讯了。
所以在B没有准备好之前,他可以使用一个PRanswer
,就是提前预定好的一个answer
给这个A发过去,发过去之后呢,它就变成了这个have-remote-offer
这个状态,这是一个中间状态,
在这个状态下,双方的这个链路是可以协商好的,只是没有这个媒体数据,当那个B设置好他自己的媒体流之后,就是一切都准备好之后,然后再给他回一个最终的answer
,当调用者收到它这个最终的answer
之后,他又变成了stable
状态,那双方就可以就真正协商好了。
这时候,实际是减少了底层的这个网络流的这个握手,以及一些其他的逻辑处理工作,这样就节省了时间。
但对于被调用者也是类似的,所以在他回这个真正的answer
之前,它是处于这have-local-PRanswer
的,当真正的这个最终的Answer
,准备好之后呢,再重新设一下setLocalAnswer
,他又变成了stable
状态,这就是一个整个协商完整的一个状态变化。
只有在整个协商完成之后,才能进行我们后边儿的真正的音视频数据的传输以及编解码。
这就是协商状态的变化。
4. 媒体协商方法
上面的媒体协商过程中主要用到了WebRTC中的四个方法:
- createOffer
- createAnswer
- setLocalDescription
- setRemoteDescription
下面我们对这几个方法详细介绍一下:
上面有详细介绍了媒体协商的整个流程:
其中有createOffer
,就创建一个本地的这个媒体信息,音频编解码视频编解码等等。
对于这个对端呢,就是收到offer之后呢,它会创建一个createAnswer
,这是第二个方法,
也就是说我本地的一个信息最终要传给这个调用者。那第三个就是setLocalDescription
,
我把我自己本地的这个SDP的描述信息设置好之后我就可以触发去采集这个收集候选者了 。那第四个就是setRemoteDescription
,
当受到对端的这个描述信息之后,SDP之后呢,将它设的setRemoteDescription
这个槽儿里去,在内部他就做了这个真正的协商,就是媒体协商的方法。
aPromise = myPeerConnection.createOffer([options]);
它有一个可选的options,,它返回的是一个promise,它会创建成功之后有一个逻辑处理。创建失败也会存在另外一个逻辑处理。
aPromise = myPeerConnection.createAnswer([options]);
createAnswer
,他的基本格式什么?
就是PeerConnection
下边有一个方法就createOffer
,那它有一个可选的option,这个option后面我们还会做详细的介绍,还有几个选项,每个选项都有其特殊的意义,他返回的是一个promise,它会创建成功之后有一个逻辑处理。创建失败也会存在另外一个逻辑处理,那这个createAnswer
这个格式其实跟他是类似的,就变成了createAnswer
,还有一个option,这个option其实就是作用不大,主要是createOffer
是这个option
有很多作用,setLocalDescription
的格式就是将createOffer
或者createAnswer
的结果,包括参数设置到这里就设置好了,同样道理,setRemoteDescription
。那这个格式也比较简单,也是刚那个对端的这个sessionDescription
设置进来就好了。
aPromise = myPc.setLocalDescription(sessionDescription);
aPromise = myPc.setRemoteDescription(sessionDescription);
用于将对端的SDP设置进来。
我们都知道视频和音频都是有轨道组成的,下面我们介绍一下一个轨道方法就是Tack
方法。在RTCPeerConnection
里就有两个重要的这个Track
的方法,一个是添加,一个是移除,这个比较简单,
但我们来看看添加方法的格式就是:
第一个是你要添加的这个Track
的是音频的Track
还是视频的Track
,
第二个是stream
,那么这个stream
从哪儿来呢?
实际就是我们之前介绍的getUserMedia
那里我们会拿到一个流,这个流里面可能有音频Track
有视频Track
,那你就要遍历一下,让他们一个个都加入到这个PeerConnection
里去,这样PeerConnection
就可以控制这每一路轨了。就从他们那个里边儿可以获取到数据进行这个发送,这是添加。
那么他有一些参数。这个参数刚才我已经介绍,这Track
就是媒体轨,这个stream
的就是我们getUserMedia
获取的stream
。removeTrack
这个比较简单,就是将Addtrack
里头这个send
放进去,然后他就可以这个移除掉了。
rtpSender = myPc.addTrack(track,stream...);
myPc.remoteTrack(rtpSender);
5 . PeerConnection 事件
PeerConnection还有几个比较重要的事件,我们这里重点介绍两个:
-
第一个是这个协商事件:当我们那个进行媒体协商的时候,就会触发这个,
onnegotiationneeded
就是需要协商,只要协商的时候会触发这个事件。 -
第二个是
onicecandidate
,就是当我们收到一个ICE
的候选者的时候,也会从底层触发这个事件,告诉我们现在有一个候选者来了,那么我们要拿到这个候选者,将它添加到我们的这个ICE
里去,这样就可以进行这个ICE
的这个通讯了。
最后
以上就是俊秀大门为你收集整理的WebRTC源码研究(29)媒体能力协商过程WebRTC源码研究(29)媒体能力协商过程的全部内容,希望文章能够帮你解决WebRTC源码研究(29)媒体能力协商过程WebRTC源码研究(29)媒体能力协商过程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复