SDL中文论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 15065|回复: 3
打印 上一主题 下一主题

[webrtc] 交互式连接建立(ICE)

[复制链接]

187

主题

346

帖子

2450

积分

版主

Rank: 7Rank: 7Rank: 7

积分
2450
跳转到指定楼层
楼主
发表于 2016-10-21 14:16:09 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 ancientcc 于 2017-2-4 22:56 编辑

  交互式连接建立是一种标准穿透协议,利用Stun和Turn服务器来帮助端点建立连接。市面上已有不少介绍ICE的资料,像《WebRTC权威指南(第三版)》中的“9.2 交互式连接建立”。但看了那些后,有人还是不能理解,这里试着用一个实例来描述整个过程。ICE协议只是制定规范,没规定怎么实现细节,在细节实现上这里参考Google的WebRTC。

图1 ICE呼叫流程
  上图就是《WebRTC权威指南(第三版)》中的图9.1。呼叫要交换两种信息,一是候选地址,二是媒体信息。候选地址用于建立网络连接,它存储着和网络连接相关的参数。媒体信息(SDP)用于描述要在对等连接上传输的数据,包括音频、视频和数据。用路和车来比喻的话,候选地址用于造路,媒体信息于用指定要跑什么车。
  在图中,双方是串行处理媒体、候选地址,但实际中是并发的。举个例子,主叫收到Answer后,它仍可能在收集候选地址,然后通过信令服务器发向被叫。
  除了主叫必须创建Offer才开始收集候选地址、被叫必须创建Answer才开始收集候选地址外,ICE代理是相互独立地处理媒体和候选地址。(这结论细节参考底下的“四:选定候选地址,并启动媒体”)。
  和“9.2 交互式连接建立”一样, 这里也把ICE分为六个步骤。下图是例子使用的网络拓扑结构。

一:收集候选地址
  候选地址是或许可用于接收媒体以建立对等连接的<IP地址, 端口>对,它分四种类型。
类型别名如何传给对端用法
主机候选项host信令服务器从网卡中获取的本地传输地址,如果此地址位于NAT之后,则为内网地址
服务器反射候选项srflx信令服务器从发送给Stun服务器的Binding检查中获取的传输地址。如果此地址位于NAT之后,则为最外层NAT的公网地址
对端反射候选项prflxStun Binding请求从对端发送的Stun Binding请求获取的传输地址。这是一种在连接检查期间新发生的候选项
中继候选项relay信令服务器媒体中继服务器的传输地址。通过使用TURN Allocate请求获取

  具体到例子,以下是此阶段将至少能收集到的候选地址。为简单,不再写A的IP2、B的IP2的服务器反射地址。
别名类型
A$Cand1host192.168.1.105
A$Cand2srflx211.161.240.181(raddr: 192.168.1.105)
A$Cand3host172.16.40.6
B$Cand1host192.168.0.204
B$Cand2srfix11.92.14.8(raddr: 192.168.0.204)
B$Cand3host192.168.0.181

二:交换候选地址
  A通过信令服务器把A$Cand1、A$Cand2、A$Cand3发向B,相应地,B通过信令服务器把B$Cand1、B$Cand2、B$Cand3发向A。对端收到一个候选地址后会做什么?深入它之前让引入两种对象:P2PTransportChannel、Connection。
  ICE代理用P2PTransportChannel管理通道(Component)上的网络传输。什么是通道?Webrtc有个概念叫轨道(Track),常见有视频轨、音频轨,而要发送一条轨道中数据,最多可能使用两个通道,分别是Rtp、Rtcp。肯定会有Rtp,Rtcp则可选。一个P2PTransportChannel对应一条通道,如果当前会话要同时处理音频、视频,每条轨道又都包括Rtp、Rtcp,那会话中就存在四个P2PTransportChannel对象。P2PTransportChannel用维护一张连接状态表来管理网络传输,表中一条记录对应一个Connection对象。这里让具体到A的视频Rtp对应的P2PTransportChannel,看它在收到B$Cand1后会做什么。
  当A收到B发来的B$Cand1后,P2PTransportChannel会向连接状态表新增两条记录,即两个Connection。这时已到通道,地址须是ip:port对。
本地网卡地址(Port)对端地址状态
192.168.1.105:60001192.168.0.204:40001未进行过Stun检查
172.16.40.6:60003192.168.0.204:40001未进行过Stun检查

  此时A不知道该用哪个网卡IP才能把数据成功发向192.168.0.204,于是它只要在有可能的地址对就创建Connection。注意Connection只会基于网卡IP,即host,因为对发送源来说,host才可能是源,其它的只是中间转换出的地址,像srflx。当然,创建时会放弃明显不可能的<网卡地址, 对端地址>对,举个例子,网卡地址是ipv4,而对端地址是ipv6。
  当收全B$Cand1、B$Cand2、B$Cand3,状态表中就有6条记录。
本地网卡地址对端地址状态
192.168.1.105:60001192.168.0.204:40001未进行过Stun检查
172.16.40.6:60003192.168.0.204:40001未进行过Stun检查
192.168.1.105:6000111.92.14.8:50002未进行过Stun检查
172.16.40.6:6000311.92.14.8:50002未进行过Stun检查
192.168.1.105:60001192.168.0.181:40003未进行过Stun检查
172.16.40.6:60003192.168.0.181:40003未进行过Stun检查

  表中有一条、或多条、或没有,能够把A的视频Rtp数据发向B的视频Rtp通道,到底怎么个可能性就要执行接下的Stun检查。

三:STUN检查
  在状态表新建一条记录,即一个Connection,很快就会在此Connection上进行Stun检查。Stun检查具体操作是在此Connection上发Stun Binding请求。由于要能支持Stun应答,每个ICE代理必须内置Stun服务器功能。Stun检查具体步骤见下图。

  为什么说Stun检查会发现prflx候选项?假如A和Stun服务器之间连接状态不好,在它收到B发来的srflx(11.92.14.8)之后还没得出自个的srflx(211.161.240.181)。虽然A没得到自个的srflx,但这不妨碍对B的srflx这个候选地址进行Stun检查,于是会向11.92.14.8发Stun请求。B收到这个请求,从请求解析出211.161.240.181。虽然这个地址在值上等于A的srflx,但不是从信令服务器得到,而是来自对端的Stun请求。此时B就会以这个prflx向状态表新建Connection。
  A在之后终于向Stun服务器拿到了自个的srflx,并通过信令服务器发向B。B发现这个srflx值对应的Connection已存在,就不会再创建了。
  到此可得出个结论:两种原因会导致新建Connection,一是从信令服务器收到候选地址,二是Stun检查发现prflx。不同于从信令服务器得到地址而创建的Connection,Stun检查时创建的Connection一开始就基本能确定连接是畅通的。

四:选定候选地址,并启动媒体
  P2PTransportChannel会维护连接状态表,并排序表中记录(SortConnectionsAndUpdateState)。排序指的是计算每条记录的连接“成本”,把成本最低的排在第一条。如何计算成本?这涉及到很多因素,比如发出Stun请求到收到应答经过了的时间,用时越少的“成本”自然会低些。
  当A有视频Rtp数据要发送时,它检查状态表的第一条记录,如果判断出它的状态是发送就绪,就会用此Connection进行发送。否则直接放弃这个发送任务。媒体模块在处理数据的采集、编码任务时,不用考虑候选地址方面进展怎样了,只是要到发送时才关注下,而即使不能发送也不会影响自个进度;同样,候选地址处理模块也不会关注媒体处理模块的进度。这正是之前写的一个结论:“除了主叫必须创建Offer才开始收集候选地址、被叫必须创建Answer才开始收集候选地址外,ICE代理是相互独立地处理媒体和候选地址”。
  维护表任务包括新建、删除记录,以及修改记录中的状态字段。删除记录、修改状态都涉及到“长连接”。

五:长连接
  为确保NAT映射和过滤规则不在媒体会话期间超时,ICE会不断通过使用中的候选项对发送Stun连接检查。具体到P2PTransportChannel,表现出来的是对状态表中所有记录隔段时间就要发送个Stun Binding请求。如果检测到本来是畅通的Connection上Stun应答超时了,那它就会更改该Connection状态,执行表排序时就有可能会向下掉,严重时会从状态表删除该记录。
  一记录被删除后,如果之后那候选地址的连接又恢复了,则会基于该候选地址重新创建Connection。

六:ICE重新启动
  分析长连接时,我们已能得出个结论,如果是网络拥堵或通断导致的状态表变化,P2PTransportChannel内部就能处理。但是,如果基地址发生改变,像一网卡被禁用,这就超出P2PTransportChannel可处理范围了,需重启ICE。
回复

使用道具 举报

187

主题

346

帖子

2450

积分

版主

Rank: 7Rank: 7Rank: 7

积分
2450
沙发
 楼主| 发表于 2017-1-7 21:38:50 | 只看该作者
本帖最后由 ancientcc 于 2017-1-10 11:37 编辑


图1:和网络连接相关的数据结构

建立呼叫要交换两种信息,一是候选地址,二是媒体信息。候选地址用于建立网络连接,它存储着和网络连接相关的参数。媒体信息用于描述要在对等连接上传输的数据,包括音频、视频和数据。用路和车来比喻的话,候选地址用于造路,媒体信息于用指定要跑什么车。

几条结论
  • 候选地址相关工作是在SetLocalDescription时启动,在这之前甚至没有任何网络传输。
  • 交换候选地址和交换Offer、Answer是并发进行。为什么呢?它们都须要进行网络传输,一旦涉及到网络传输,需要的时间就不可预测,为良好UI须要采用并发。
  • 交换Offer、Answer用的是信令通道。为什么要强调这个?有人认为建立网络连接和Offer、Answer无关,因而理论上可以等网络通畅后用A、B之间的对等连接传Offer、Answer,也就是说可以不通过信令通道。——以上认为是对的,但还是为要有好的UI,加之Offer/Answer数据量不大,还是不要等对等连接通畅后再传媒体信息。
  • 为实现并发,webrtc内部有三个相关线程,signaling_thread、worker_thread和network_thread。signaling_thread不是webrtc创建,它就是app主线程,后面两个是webrtc创建的线程,worker_thread用于处理和媒体相关的Offer、Answer,network_thread则用于候选地址。

深入候选地址
在控制台,常能看到类似以下的字符串。
  1. Local candidate{"candidate":"candidate:1289435463 1 udp 2122260223 10.1.1.19 64462 typ host generation 0 ufrag TWCy network-id 2 network-cost 50","sdpMid":"sdparta_0","sdpMLineIndex":0}
  2. Local candidate{"candidate":"candidate:240568271 1 udp 1686052607 174.139.8.82 64462 typ srflx raddr 10.1.1.19 rport 64462 generation 0 ufrag TWCy network-id 2 network-cost 50","sdpMid":"sdparta_0","sdpMLineIndex":0}
复制代码


大括号中字符串格式
  1. candidate:{foundation} {component} {protocol} {priority} {ip} {port} typ {type}
  2. 如果存在related ip:raddr {ip} {port}
  3. generation {generation}
  4. 如果存在用户名:ufrag {username}
  5. 如果network_id不是0:network-id {network_id}
  6. network-cost {network_cost}
复制代码


在说字段意义前让回看图1给出的网络连接涉及到的数据结构。PeerConnection常简写为pc,一次会话会首先建立它,每个pc包含一个WebRtcSession类型的session_。session_中有两种成员,一是TransportController类型的transport_controller_,二是从BaseChannel派生的轨道。当app只传音频时,那只有VoiceChannel有效,transport_controller_中的transports_只有一个tag是“audio”的单元,当要同时传音、视频时,transports_有两个单元,tag分别是“audio”和“video”。channels_是轨道中成员,表示为实现该轨道须要开辟的通道。通道是什么呢?它和传输协议有关,webrtc传输媒体数据用的是RTP协议,在这里一个通道对应或是RTP或是RTCP,也就意味着一轨道有两通道。通道下有本机上的网卡集合:Ports_,一个网卡被抽像成一个从Port派生的类,像UDPPort,对网卡,可理解为等同IP,这个IP可以是ipv4或ipv6。举个例子,本地有一个wifi、一个vpn、一个遂道适配器,那就有三个网卡,即Ports_长度是3。网卡底下有Connection集合,Connection表示一个本地端口和远程端口之间的通信链路,更多的底下会说。

每个通道都含有本地所有的网卡,后面逻辑会选定这个通道要基于哪个网卡。

知道了轨道、通道、网卡、Connection,让回头分析以下字符串中大括号中各字段意义。
  1. Local candidate{"candidate":"candidate:240568271 1 udp 1686052607 174.139.8.82 64462 typ srflx raddr 10.1.1.19 rport 64462 generation 0 ufrag TWCy network-id 2 network-cost 50","sdpMid":"sdparta_0","sdpMLineIndex":0}
复制代码

  • foundation(240568271):根据type、ip、protocol、replay_protocol计算出的字符串。一般于比较两个candidate是否相等。
  • component(1):通道码。RTP通道码是1、RTCP是2,它指示这候选地址关联RTP通道。
  • protocol(udp):传输层类型,upd或tcp。往往认为RTP、RTCP是用UDP,但webrtc其实支持用TCP。
  • priority(1686052607):<尚未理解>
  • ip(174.139.8.82):候选IP。它是真正须要的候选地址。当type是反射时,它就是NAT外的公网IP,此时raddr对应内网IP,
  • port(64462):候选IP关连的端口号。
  • type(srflx):候选地址类型。它分本地(local)、反射(srflx),中转(relay)。
  • raddr(10.1.1.19):候选IP基于的IP。对于local类型,它不存在。是反射时,它就是内网IP。
  • rport(64462):raddr关联的端口。
  • generation(0):代数。初始值是0,然后会不断+1,大的代数会覆盖掉低代数的候选地址。
  • ufrag(TWCy):用户名。
  • network-id(2):此网卡IP在网卡集合中的索引,从1始。

小结关于候选地址的FAQ
A:什么时候开始?
Q:SetLocalDescription结束前会调用MaybeStartGathering,它开始搜集候选地址。

A:在哪线程收集?
Q:network_thread_。

A:须要多少个端口(测试连接数)?
Q:端口数=轨道数*(RTCP+RTP)*本地可用网络适配器数。假设要同时传语音和图像、本地有三个可用适配器,那须要分配12个端口,意味着须要12个AllocationSequence对象。创建端口后就立即向stun服务器发12个请求包,以确定对应的公网IP:port(UDPPort::MaybePrepareStunCandidate)。

A:HOST地址的收集方法?
Q:本机网卡IP。对端通过信令服务器得到。

A:服务器反射地址(srflx)的收集方法?
Q:向stun服务器发Binding Reuest,从应答解析出的地址。对端通过信令服务器得到。

A:对等端反射地址(prflx)?
Q:stun检查时解析出的地址,地址就是该Stun Binding Reuest数据包来自的那个IP:port。对端不通过信令服务器得到。

A:中继地址的收集方法?
Q:向turn服务器发Allocate Request,然后解析响应得到的中继地址(XOR-RELAYED-ADDRESS)。对端通过信令服务器得到。

A:如何指定stun、turn的服务器IP:port?
Q:CreatePeerConnection的第一个参数RTCConfiguration中的servers字段。stun服务器必须以stun为前缀,像“stun:stun.l.google.com:19302”,turn服务器必须以“turn”为前缀。

A:什么时候收集结束?
如果要确定结束标准,那只能针对每个通道。通道何时收集结束?我还未完全清楚,但应该和Connection状态有关。

A:什么是绑定(bundle)
为什么要绑定?要同时传视频、音频,意味着要使用两个端口,两个端口不仅占资源,还增加网络不可靠,绑定目的就是要让一个端口发送视、音频。
SDP内容决定了如何使用绑定,以下是和绑定相关字段。
  1. a=group:BUNDLE sdparta_0 sdparta_1
  2. m=audio .....
  3. a=mid:sdparta_0
  4. m=video ......
  5. a=mid:sdparta_1
复制代码
会话级别属性a=group:BUNDLE指示此ICE代理支持绑定。后面的sdparta_0、sdparta_1指示了要绑在一块的媒体流,这个名称是自定义的,但后面必须有对应的a=mid属性。上例中sdparta_0对应audio,sdparta_1对应video,指示把audio、video流绑在一个端口。

Conection
Connection表示一个本地端口和远程端口之间的通信链路。一旦收到一个远程候选地址,找到该地址对应的通道,会在通道下多卡网卡建立Connect!举个例子,本地有一个wifi(ipv4)、一个vpn(ipv4)、一个遂道适配器(ipv6),即通道下有三个网卡,它枚举这三个网卡,如果1)协议、2)AF一致,那会在该网卡上创建Connection。以这个例子说,如果收到的候选地址是ipv4,就会在该通道下的wifi、vpn创建Connection。在stun检查时,会基于这两个Connection向对方发请求包。

在数据结构上,Connection被Port(网卡)所有,它的port_成员指向这个Port。以下是Connection在Port的存放结构:
  1. std::map<rtc::SocketAddress, Connection*> connections_;
复制代码

first是远程端口地址。为什么网卡中的Conection是个集合?如果对方在指定通道有三个候选地址,像本地、反射、中转,那这个集合中就有三个单元,至于最终传输用的是哪个单元,那应该有算法,这个我<尚不清楚>。

何时创建Connection?Stun检查时被创建。对此Port来说,Stun检查就是它收到了对等端发来的一个Binding Request。是第一个Request时,会为此创建Connection。作为Stun检查后续,要用该Connection::HandleBindingRequest回应此个绑定的应答。是第二或更后面Request时,Port已存在此Connection,但还是要用HandleBindingRequest发回应答。举个例子,Port的IP:port是192.168.1.105:59042,它收到一个来自192.168.1.104:60002的Request,那它会创建一个Connection,Port中的connections_将有以下条目。
  1. 192.168.1.104:6002 ===>  Connection1
复制代码

如果此Port还收到从另一个172.16.40.6 60059收到Binding Request,那之后connections_将有两个条目。
  1. 192.168.1.104:6002 ===>  Connection1
  2. 172.16.40.6:60059  ===>  Connection2
复制代码


深入Offer、Answer
(此处和C/C++代码结合较深,只有看过代码才可能理解)
WebRtcSessionDescriptionFactory::certificate_request_state_:指示证书请求态状,初始值是CERTIFICATE_NOT_NEEDED。

RTCCentificate用于描述证书。它可以由CreatePeerConnection时在参数中指定,但一般app都不指定。由于没有指定,在WebRtcSessionDescriptionFactory的构造函数时certificate_request_state_就改到CERTIFICATE_WAITING,指示在等待证书。一旦外界指定了,还是会改到CERTIFICATE_WAITING,不同的是会立即调用signaling_thread_->Post。
  1. signaling_thread_->Post(RTC_FROM_HERE, this, MSG_USE_CONSTRUCTOR_CERTIFICATE, new rtc::ScopedRefMessageData<rtc::RTCCertificate>(certificate));
复制代码

证书其实在WebRtcSessionDescriptionFactory构造函数期间就已生成。在构造期间,发现外部没有指定证书,就调用GenerateCertificateAsync,后者会Post给worker_thread_消息MSG_GENERATE,由于构造线程是singaling_thread_,只要发生线程调度,就会调用worker_thread_,然后执行处理MSG_GENERATE消息,它的功能就是产生证书,产生证书后,它会向signaling_thread_投递MSG_GENERATE_DONE消息。但由于signaling_thread_一直在处理初始化操作,不会处理消息,一直要到执行到初始化完成,即PeerConnection::CreateOffer,CreateOffer会构一个offer请求,并把请求放入create_session_description_requests_。

接下signaling_thread_要处理worker_thread_投递给它的MSG_GENERATE_DONE。以下是处理逻辑。
  • 把certificate_request_state_置为CERTIFICATE_SUCCEEDED。
  • 逐个处理create_session_description_requests_中请求,是Offer的调用InternalCreateOffer,是Answer是调用InternalCreateAnswer,生成完整的Offer/Answer。要注意,这两函数结束前会向signaling_thread_投递MSG_CREATE_SESSIONDESCRIPTION_SUCCESS。

signaling_thread_处理完MSG_GENERATE_DONE,它就处理MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,WebRtcSessionDescriptionFactory默认对它处理是调用CreateSessionDescriptionObserver的OnSuccess这个虚函数,app需重载这函数,至少要把这个Offer发送向信令服务器。
回复 支持 反对

使用道具 举报

187

主题

346

帖子

2450

积分

版主

Rank: 7Rank: 7Rank: 7

积分
2450
板凳
 楼主| 发表于 2017-1-10 11:09:52 | 只看该作者

app编程

本帖最后由 ancientcc 于 2017-1-10 11:18 编辑

配置stun/turn服务器
在控制ICE模块上,app要做的是设置RTCConfiguration,然后把它作为参数传给PeerConnectionFactory::CreatePeerConnection。对RTCConfiguration,其它可用默认值,除了配置stun/turn服务器,即当中的servers字段。servers字段的类型是IceServer。
  1. struct IceServer {
  2.         std::string uri;
  3.         std::vector<std::string> urls;
  4.         std::string username;
  5.         std::string password;
  6.         ......
  7. };
复制代码

IceServer中有两个和服务器域名有关的url:uri、urls。当urls不空时,只解析urls,忽略uri。当urls空时,才解析uri。当urls、uri都是空时,参数错误。看webrtc写的,它是建议用urls,废弃uri。

urls是std::vector,也就是说一次会话可同时指定多个stun服务器,或多个turn服务器。接下说说字符串格式。[]表示可选,<>表示变量。
  1. stun服务器格式:stun[s]:<域名>:<端口号>。以下是两个例子。
  2. stun:133.130.113.73:3478
  3. stuns:133.130.113.73
复制代码

  • 默认端口号3478。
  • stuns是以安全为目的stun,即stun+ssl。

  1. turn服务器格式:turn[s]:[用户名@]<域名>:<端口号>[?transport=<传输类型>]。以下是两个例子。
  2. turns:turn.l.google.com:4378
  3. turn:test@turn.l.google.com:4378?transport=udp
复制代码

  • 默认端口号3478。
  • transport值可以是“upd”或“tcp”,默认“udp”。
  • turns是以安全为目的turn,即turn+ssl。当是turns,内部会强制使用5349端口,transport=tls。
  • 字符串中可指定用户名。当字符串中有用户名时,它会覆盖掉IceServer中的username字段。
  • 字符串不能指定密码,密码只能设在IceServer的password字段。

注:<webrtc>/api/peerconnection.cc的ParseIceServers负责解析IceServer。

查看收集/连接状态
到现在,我不知道app层如何判断正使用的连接是中继还是穿透。
  1. class PeerConnectionObserver {
  2.         ......
  3.         virtual void OnIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) = 0;
  4.         virtual void OnIceGatheringChange(PeerConnectionInterface::IceGatheringState new_state) = 0;
  5. };
复制代码

app可通过重载这两函数查看收集/连接状态。注:以下提到的Channel指P2PTransportChannel。

IceGatheringState指示收集对端候选地址到了什么状态,它有三个值。
状态描述
kIceGatheringNew初始值
kIceGatheringGatheringPeerConnection::SetLocalDescription退出前改到此状态,表示正在收集
kIceGatheringComplete表示所有的Channel都有了有效的对端候选地址。不等于通过了stun检查,当是relay时,只是收到了中继地址而已

不论IceGatheringState,还是IceConnectionState, 改变状态主要发生在TransportController::UpdateAggregateStates_n()。

对gathering_state,它查询P2PTransportChannel::gathering_state_变量,如果有一条Channel的gathering_state_不是kIceGatheringNew(或kIceGatheringGathering,或kIceGatheringComplete),那就是kIceGatheringGathering,如果都是kIceGatheringComplete,则就是kIceGatheringComplete。

Channel是什么时候从kIceGatheringNew到kIceGatheringGathering?PeerConnection::SetLocalDescription退出前。kIceGatheringGathering何时到kIceGatheringComplete?非relay类型,通过了Stun检查;relay时,收到一个中继地址。

IceGatheringState对调试有用,但对使用app的人来说,可能没啥用。它们可能应该关心IceConnectionState。
状态描述
kIceConnectionNew初始值
kIceConnectionChecking对主叫,收到Answer后会进入此状态。对被叫,使用了对端的第一个候选地址后进入此状态。它们共同点是收到了对端发来的数据,证明对端是存在的。
kIceConnectionConnected至少一条Channel有了可用的Connection。连接状态表第一条发送就绪。
kIceConnectionCompleted所有Channel有可用的Connection。
kIceConnectionFailed
kIceConnectionDisconnected
kIceConnectionClosed关闭了会话。
kIceConnectionMax

注:webrtc原码存在两处IceConnectionState定义,<webrtc>/api/peerconnectioninterface.h和<webrtc>/p2p/base/jseptransport.h。它们定义的值不一样,将来应该会统一。

由于webrtc默认开启绑定,这使得整会话就存在一条Channel。一旦该Channel有了可用的Connection,则是所有Channel都有了,这就让kIceConnectionConnected基于等同kIceConnectionCompleted。
状态提示
kIceConnectionNew正等待对方接听
kIceConnectionChecking正尝试和对方建立连接
kIceConnectionConnected建立了连接,启动聊天
kIceConnectionCompleted聊天中
回复 支持 反对

使用道具 举报

187

主题

346

帖子

2450

积分

版主

Rank: 7Rank: 7Rank: 7

积分
2450
地板
 楼主| 发表于 2018-2-13 09:33:59 | 只看该作者

传输数据

  1. MediaConstraintsInterface--->RTCOfferAnswerOptions-->cricket::MediaSessionOptions(开始出现数组)-->cricket::SessionDescription-->JsepSessionDescription
复制代码
JsepSessionDescription是实现SessionDescriptionInterface接口的类。接口中有description()方法返回之前传给它的SessionDescription对象(desc)。SetLocalDescription会以desc为参数调用CreateChannels,后者创建声音、视频、数据轨道。
  1. RTCError PeerConnection::CreateChannels(const SessionDescription& desc);
复制代码

A:如何由RTCOfferAnswerOptions生成cricket::MediaSessionOptions?
  1. void PeerConnection::GetOptionsForPlanBOffer(const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options, cricket::MediaSessionOptions* session_options);
复制代码
在PeerConnection有一个std::vector<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> transceivers_变量,单元是RtpTransceiver,字段media_type_指示了要传输的数据类型,当前也就是三种,MEDIA_TYPE_AUDIO
、MEDIA_TYPE_VIDEO、MEDIA_TYPE_DATA。transceivers_决定了是否要在MediaSessionOptions存在声音、视频。但数据靠的不是这种方法,而是HasDataChannels()。
  1. bool PeerConnection::HasDataChannels() const {
  2.         return !rtp_data_channels_.empty() || !sctp_data_channels_.empty();
  3. }
复制代码
PeerConnection::Initialize会强制创建含两个RtpTransceiver的transceivers_,一个是audio、一个是video。(这是否意味着一次session必须同时传audio、video?)

我现在还没有找到让HasDataChannels()返回true的办法。

传输数据或是rtp(enable_rtp_data_channel=true)或是sctp。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|丽谷软件|libsdl.cn

GMT+8, 2024-4-29 20:30 , Processed in 0.051366 second(s), 22 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表